本文章是由機器翻譯。

預測:雲端

多平台 Windows Azure 儲存空間

Joseph Fultz

下載代碼示例

Windows Azure 存儲絕非是設備特定技術,這是件好事。本月我將看一看以下三個移動平臺上的開發:Windows Phone 7、jQuery 和 Android。

為此,我將為每個平臺創建一個簡單的應用程式,該程式將進行 REST 調用,以從預定的 Windows Azure 存儲容器獲得圖像清單,並以電影膠片的形式顯示縮略圖,同時使顯示所選圖像的螢幕保持平衡,如圖 1所示。

圖 1 並排顯示的存儲圖像檢視器

準備存儲容器

我需要一個 Windows Azure 存儲帳戶,以及所用上載工具的主訪問金鑰。如果要從正在開發的用戶端進行安全訪問,我同樣需要該金鑰。這些資訊可在 Windows Azure 平臺管理門戶中找到。

我從 Internet 上隨意找了幾張圖像,並用自己的電腦上載。我沒有寫上載代碼,而是使用了 Windows Azure 存儲流覽器,該流覽器可在azurestorageexplorer.codeplex.com上找到。出於後面說明的原因,圖像應小於 1 MB。如果正確使用此代碼,則圖像最好為 512 KB 或更小。我將創建一個名為 Filecabinet 的容器,創建容器並上載圖像後,Windows Azure 便準備好了。

平臺模式

每個平臺都有其特定的構造、啟用程式和約束。對於 Silverlight 和 Android 用戶端,我採用了封送處理資料的熟悉模型和供 UI 使用的物件集合。由於 jQuery 支援範本,在這種情況下,我發現直接通過 jQuery 提取 XML 和生成所需的 HTML 要容易得多,這使得 jQuery 實現非常直接。我將不詳細介紹它的流程,但是對於另外兩個示例,我將會多介紹一些背景知識。

Windows Phone 7 and Silverlight

如果您熟悉 Silverlight 的開發,那麼對在 Visual Studio 2010 中創建新的 Windows Phone 7 應用程式專案將不會有問題。如不熟悉,則對於該示例,您需要瞭解可觀察的集合 (bit.ly/18sPUF)、常用 XAML 控制項(如 StackPanel 和 ListBox)以及 WebClient。

開始時,應用程式的主頁會對 Windows Azure 存儲進行 REST 調用,調用有意設計為非同步的。Windows Phone 7 強制使用此模式來確保任何應用程式最終不會阻塞和鎖定設備。從調用檢索到的資料將置於資料上下文中,類型為 ObservableCollection<>。

這樣,容器就可以獲得通知,並在集合中的資料更新時獲得更改,而無需我明確執行刷新。使用 Silverlight 創建複雜的 UI 會很複雜,但對於相對簡單的任務(例如此任務)則提供了較低的入門門檻。由於在 UI 中的觸摸和物理支援中添加了內置的綁定和服務調用支援,電影膠片到縮放視圖的變化如同使用資料綁定網格編寫 ASP.NET 頁面一樣簡單。

Android 活動

Android 推出了自己的平臺模式。幸運的是,這個模式並非不可思議或難以理解。對於主要從事 .NET 開發的人員而言,映射到熟悉的結構、術語和技術是件輕而易舉的事。在 Android 中,您想與使用者進行的幾乎任何交互都以活動物件表示。可將活動視為以下可組合生成應用程式的四大基本元素之一:活動、服務、廣播接收器和內容提供程式。

為簡單起見,我保留了活動中的所有代碼。在更逼真的實現中,用以提取和操作 Windows Azure 存儲中的圖像的代碼需要在服務中實現。

此示例更像是將資料庫訪問置於表單後面的代碼中。從 Windows 的角度看,您可以將活動視為 Windows 表單,其有方法 - 事實上,是有需要來保存和還原狀態。活動和 Windows 表單具有很類似的生命週期;應用程式可以由一對多的活動構成,但是一次只能有一個活動與使用者進行交互。有關 Android 應用程式的更多基礎知識,請轉至 bit.ly/d3c7C

此示例應用程式將由一個活動 (bit.ly/GmWui)、一個 BaseAdapter (bit.ly/g0J2Qx) 和一個用以存放圖像相關資訊的自訂物件構成。

創建 UI

每個 UI 模式下的常見任務包括:進行 REST 調用、將返回值封送到有用的資料類型、綁定和顯示,以及處理按一下事件以顯示圖像的縮放視圖。首先,我要檢查從 REST Get 返回的資料,以及在每個示例中讓資料變得更易於使用所需的內容。

資料

在應用程式碼內,我更喜歡處理物件且無需經常操作 XML,因此,我創建了一個與存儲調用返回的 XML 相匹配的物件。這類似于圖 2中的代碼。

圖 2 與存儲調用返回的 XML 相匹配的物件

<EnumerationResults ContainerName="http://jofultz.blob.core.windows.
net/filecabinet">

  <Blobs>

  <Blob>

  <Name>2010-12-12+10.40.06.jpg</Name> 

  <Url>http://jofultz.blob.core.windows.
net/filecabinet/2010-12-12+10.40.06.jpg</Url> 

  <LastModified>Sun, 02 Jan 2011 02:24:24 GMT</LastModified> 

  <Etag>0x8CD783B4E56116C</Etag> 

  <Size>263506</Size> 

  <ContentType>image/jpeg</ContentType> 

  <ContentEncoding /> 

  <ContentLanguage /> 

  </Blob>

  <Blob>

  <Name>cookie-monster.jpg</Name> 

  <Url>http://jofultz.blob.core.windows.
net/filecabinet/cookie-monster.jpg</Url> 

  <LastModified>Mon, 27 Dec 2010 09:40:18 GMT</LastModified> 

  <Etag>0x8CD73C13542256C</Etag> 

  <Size>265995</Size> 

  <ContentType>image/jpeg</ContentType> 

  <ContentEncoding /> 

  <ContentLanguage /> 

  </Blob>

  <Blob>

之後,我創建了一個有代表性的物件來存放我想要的資訊。 通常,我會關注名稱和 URL 欄位。 圖 3提供對每個示例所用物件的相關聲明的描述。

圖 3 每個用戶端示例中所用物件的物件聲明

Silverlight Android jQuery
public static ObservableCollection<StorageImage>
  ImageInfoList {get;set;}
 
...
 
public class StorageImage
{
  public string Name{get;set;}
  public string Url {get;set;}
  public BitmapImage Image {get;set;}
}
public ArrayList<BlobInfo>
  BlobList= null;
 
...
 
public class BlobInfo {
  public String Name;
  public String Url;
  public Bitmap ImageBitmap;}

None

The XML is consumed directly.

創建 UI

幸運的是,在 Android 和 Silverlight 中定義 UI 的方式與其使用標記的方式相似,因此,理解一個幾乎就可以保證能夠理解另一個。 此外,這兩者均使用 XML 標記來定義 UI。

jQuery 實現只是使用 HTML 並動態生成一些元素。 寫標記時,三者之間的最大區別為使用的控制項/元素的知識。

從 Windows Phone 7 XAML 開始,打開 MainPage.xaml,然後添加 <Grid/>,其將包含電影膠片的 <ListBox />。 清單方塊包含用於控制電影膠片資料呈現方式的 <ItemTemplate/> 和 <DataTemplate />。 此外在網格中還有一個 <Image/> 控制項,用於操縱圖像的縮放視圖。

對於 Android,我將使用包含以下兩個小元件的 LinearLayout 建立一個類似的 UI 構造: Gallery and ImageView. 對於熟悉 Silverlight 或 Windows Presentation Foundation (WPF) 的人而言,LinearLayout 類似于 StackPanel。 小元件即為控制項,這大致可以等同于包含以下兩個元素的 StackPanel:圖像的水準 ListBox 和圖像控制項。 請注意,這也幾乎完全是 Silverlight 示例中所使用的。 Gallery 提供縮略圖的電影膠片樣式視圖,ImageView 顯示所選縮略圖的縮放視圖。

對於 jQuery 示例,我將有一個空的 <p/>,id 名稱為“output”;在這裡將通過 jQuery 動態添加縮略圖視圖。

因為要處理標記內的綁定和呈現,Silverlight 標記要長一些。 對於 Android,則需要略微多一些的代碼來處理資料載入。 三者的標記如圖 4所示。

圖 4 標記的區別

Element Silverlight Android jQuery
電影膠片 <ListBox /> <Gallery /> <p />
縮放視圖 <Image /> <ImageView /> <img />

開始標記: Silverlight

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox Name="ImageList" ItemsSource="{Binding}" MaxWidth="400"
Background="#00C23838" Margin="28,16,28,577" BorderBrush="#32D4BA29"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
SelectionChanged="ImageList_SelectionChanged">
    <ListBox.ItemsPanel>
      <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" >
        </StackPanel>              
      </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
      <DataTemplate>
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Image}" Height="110" Width="150"
Name="{Binding Name}" ></Image>
        </StackPanel>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
<Image Height="447" HorizontalAlignment="Left" Margin="28,123,0,0"
Name="ImageViewer" Stretch="Fill" VerticalAlignment="Top" Width="400" />
  </Grid>
</Grid>

開始標記: Android

<LinearLayout android:id="@+id/LinearLayout01"android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<Gallery xmlns:android="http://schemas.android.com/apk/res/android"
 
android:id="@+id/Thumbnails" android:layout_width="fill_parent"
 
android:layout_height="wrap_content" />
<ImageView android:id="@+id/ZoomView"
 
android:layout_width="wrap_content" android:layout_height="wrap_content"/>
</LinearLayout>

開始標記: jQuery

<p id="output">
 
</p>
<p>
<img id="zoomview" height="480" width="320"  />
</p>

主代碼

下一步是創建可聯繫 Windows Azure 存儲以獲取 blob 資訊的代碼。 提取請求採用 http://[your storage account].blob.core.windows. net/[your container]?comp=list 形式;我的請求是 http://jofultz.blob.core.windows. net/filecabinet?comp=list.

由於結果為 XML,因此,我在 Silverlight 中使用 LINQ 來提供很好的物件圖和訪問模式。 我本想將一些類似的東西用於 Java,但最終為跳過對不熟悉技術的調試,我選擇了只使用 DocumentBuilder API 提供的文檔物件模型 (DOM) 樣式的介面。 由於這是針對幾個元素的唯讀操作,因此並非難事。 對於 jQuery,這只是一個選擇器樣式的查詢,查詢路徑與處理 DOM 類似。

圖 5 中顯示的提取方法非常簡單:由於容器是公共的,因而我無須處理代理或任何特殊的身份驗證。

圖 5 提取方法:

Silverlight

WebClient StorageClient = new WebClient();
          StorageClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(GetBlobsCompleted);
          StorageClient.DownloadStringAsync(new Uri(AZURE_STORAGE_URL, UriKind.Absolute));

Android

public InputStream GetBlobs()        {
         HttpURLConnection uc;
         InputStream returnStream=null;
try{
URL u = new URL(AZURE_STORAGE_URL);
uc = (HttpURLConnection) u.openConnection(); //u.openConnection(proxy);
uc.setRequestProperty("Accept", "*/*");
uc.setRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
uc.setRequestProperty("Accept-Language", "en-us,en;q=0.5");
uc.setRequestProperty("Keep-Alive", "300");    
uc.setRequestProperty("ucection", "keep-alive");   
returnStream = uc.getInputStream();

jQuery

$(document).ready(function () {
  $.ajax({
    type:"GET",
url:"http://jofultz.blob.core.windows. net/filecabinet?comp=list",
    dataType:"xml",
    success: RenderData
  });
});

對於 Silverlight 和 jQuery,由於請求是非同步的,而回應將傳遞給回檔以進行處理,因此我提供了用於處理回應的回檔。 對於 Android,請求是同步發出的。 在 jQuery 和 Silverlight 中,我還需要使用一個支援方法來處理資料並將資料變為可用格式,而在 jQuery 中,將直接呈現資料。

接下來,我需要在每個示例中處理回應。 我將討論每個示例,使其盡可能清晰明瞭。 從 Windows Phone 7 示例開始,我將實現 GetBlobsCompleted 方法。 我使用 LINQ 語法將 XML 梳理為我的自訂物件的可枚舉清單,如圖 6 所示。

圖 6 GetBlobsCompleted 方法

IEnumerable<StorageImage> ImageList = from Blob in xDoc.Descendants("Blob")

select new StorageImage()

  {

    Name = Blob.Descendants("Name").First().Value,

    Url = Blob.Descendants("Url").First().Value,

    Image = new BitmapImage(

      new Uri(Blob.Descendants("Url").First().Value, UriKind.Absolute)) 

  };



MainPage.ImageInfoList.Clear();

foreach (StorageImage CurImg in ImageList)

{

  MainPage.ImageInfoList.Add(CurImg);

}

我需要將物件移動到 ObservableCollection<StorageImage>。 我首先清除了清單,然後遍歷了 LINQ 結果並將物件添加至我的 ObservableCollection。 由於集合在 DataContext 中,而 ListBox 已綁定到 DataContext,因此,集合會自動接收更改並更新 ListBox 的內容。

讓我們繼續瞭解 Java/Android,由於沒有像 LINQ 這樣的東西可用,我只好多費點事,手動將資料梳理出 DOM。 MarshalToBlobInfos 方法將使用 XML 文檔物件並對其進行解析,以構建 BlobInfo 的 ArrayList。

這同 .NET、JavaScript 等中的任何其他 DOM 樣式的訪問相似,如圖 7所示。

圖 7 MarshalToBlobInfos 方法

public ArrayList<BlobInfo> MarshalToBlobInfos(Document doc)

  {

    ArrayList<BlobInfo> returnBlobs = new ArrayList<BlobInfo>();



  try {

    Element root = doc.getDocumentElement();

    NodeList items = root.getElementsByTagName("Blob");

    for (int idxBlobs=0;idxBlobs<items.getLength();idxBlobs++){

      BlobInfo blob = new BlobInfo();

      Node item = items.item(idxBlobs);

      NodeList blobProperties = item.getChildNodes();

      for (int idxProps=0;idxProps<blobProperties.getLength();idxProps++){

        Node property = blobProperties.item(idxProps);

        String name = property.getNodeName();

          if (name.equalsIgnoreCase("Name"))

            blob.Name = property.getFirstChild().getNodeValue();

          } else if (name.equalsIgnoreCase("Url")){

              blob.Url = property.getFirstChild().getNodeValue();

          }

      }

      returnBlobs.add(blob);

    }

  } catch (Exception e) {

      throw new RuntimeException(e);

  } 

  return returnBlobs;

  }

最後,我寫了用於執行結果和呈現所需 HTML 的 jQuery。 這是目前為止位數最少的代碼。 但是,就便捷程度而言,它可能是最愚鈍的:

function RenderData(xml) {



  $(xml).find("Blob").each(

    function () {

      $("#output").append("<img margin=50 height=70 width=60 src=" + 

      $(this).find("Url").text() +

      " title=" + $(this).find("Url").text() + " id=" + 

      $(this).find("Name").text() + ") /> ");



  });   

$("img").each(

  function () {

    $(this).click(

      function () {

        $("#zoomview").attr("src", $(this).attr("src"));

      });

  });

}

它需要 XML 的每個“Blob”元素,我為每個元素都寫入了 <img /> 標記,並為 id 指定值“output”。我使用了相似的選擇器結構來提取當前所需“Blob”元素中每個元素的值,以正確呈現 <img /> 標記。 然後,我為每個生成的 <img /> 元素創建了一個選擇器,並添加了按一下處理常式。

到目前為止,我的 Silverlight 代碼對 Windows Azure 存儲進行了 REST 調用,解析了結果並顯示了電影膠片。 最後一步是為電影膠片添加按一下控制程式以顯示圖像的縮放視圖。 我為獲取當前所選 StorageImage 並為圖像控制項源屬性指定其圖像屬性值的 SelectionChanged 添加了處理常式:

private void ImageList_SelectionChanged(object sender, SelectionChangedEventArgs e)

{

  ListBox lb = (ListBox)sender;

  StorageImage img = (StorageImage) lb.SelectedItem;

  ImageViewer.Source = img.Image;

}

這就是 Windows Phone 7 上的 Silverlight。 Android 則需要進行更多的工作。

由於有了 Windows Azure 存儲的 REST 介面和直接的 XML 回應,因此無論是使用 .NET、JavaScript,還是本示例中針對 Android 的 Java,我都可以輕鬆進行調用和解析結果。 兩個主要支援方法就緒後,我將向活動的 onCreate 方法添加代碼。 在活動中,在恢復現有狀態的行之後,我將添加代碼行以調用 Windows Azure 存儲並解析結果:

InputStream blobstream = GetBlobs();

DocumentBuilder docBuilder = 

  DocumentBuilderFactory.
newInstance().
newDocumentBuilder();

Document doc = docBuilder.parse(blobstream);

BlobList = MarshalToBlobInfos(doc);

如果代碼在此刻運行,我將不會得到 UI,但是我想要 Windows Phone 7 示例中的電影膠片。 下一步是掛接 UI 元素。 我將調用 setContentView,傳入源 id。 在本示例中,為 main.xml 的源 id。

如果需要更改活動的視圖,我可以調用它並傳入不同的源。 在上一個 main.xml 標記示例中,Gallery 和 ImageView 各自具有縮略圖和 ZoomView 的 id。 我將使用這些 id 來引用小元件(控制項),並為每個引用指定一個區域變數。 我將把下列代碼直接放在之前代碼的後面:

setContentView(R.layout.main);    

mZoomView = (ImageView)findViewById(R.id.ZoomView);    

mThumbnails = (Gallery) findViewById(R.id.Thumbnails);

現在,我需要創建適配器以説明檢索、格式化和排列集合中的每個專案。 需要先為縮略圖庫創建一個適配器,然後才可對該適配器進行設置。 我在專案中添加了一個新類 ImageAdapter,其擴展了 BaseAdapter。 當我將其設置為活動的適配器時,它應該調用 count,並在隨後針對傳入專案位置的每個專案調用 getView,我會將這用作 ArrayList 索引。 我會將指向活動的指標傳遞給 ImageAdapter,因為我需要上下文資訊,特別是代表圖像的 ArrayList。 ImageAdapter 的聲明和構造函數如下所示:

public class ImageAdapter extends BaseAdapter 

{    

  private Context mContext;    

  private ArrayList<BlobInfo> mBlobList=null;

  public ImageAdapter(Context c) 

  {      

    mContext = c; 

    mBlobList = ((AzureImageViewer) mContext).BlobList;

   

  }    

  public int getCount() 

  {        

    int count = mBlobList.size();

    return count;



  }

在 getView 方法中,有關適配器的實現如下:提取圖像、創建縮略圖並將原始圖像保存到相關的 BlobInfo 物件。 對於實際的實現,人們只想預先提取一些圖像並在使用者滾動時清除這些圖像,同時根據電影膠片的位置保持可用圖像的移動視窗。 縮略圖需要通過後台執行緒提取,同 Silverlight 和 jQuery 示例中要求的模式類似。

此外,專案的提取代碼最好放于 getItem override 中。 瞭解了這些注意事項後,圖 8顯示了 getView 的實現。

圖 8 getView 實現

public View getView(int position, View convertView, ViewGroup parent) 

{

  ImageView imageView;

  if (convertView == null) 

  {  

    imageView = new ImageView(mContext);

    imageView.setLayoutParams(new Gallery.LayoutParams (85, 70));

    imageView.setScaleType(ImageView.ScaleType.FIT_XY);

    imageView.setPadding(4, 4, 4, 4);

  } 

  else 

  {

    imageView = (ImageView) convertView;

  }

  BlobInfo CurrentBlob = mBlobList.get(position);

        

  if (CurrentBlob.ImageBitmap == null)

  {

    Bitmap bm = getImageBitmap(CurrentBlob.Url);

    imageView.setImageBitmap(bm);

    CurrentBlob.ImageBitmap = bm;

  }

  else

  {

    imageView.setImageBitmap(CurrentBlob.ImageBitmap)

  }     

  return imageView;    

}

視圖的初始狀態由第一個條件陳述式完成,方法為配置狀態或使用傳遞給函數的視圖。 執行 if-else 後,視圖就準備好了,但是我仍沒有圖像。 為解決該問題,我使用了位置參數來提取合適的 BlobInfo 物件,然後用 URL 屬性來提取圖像(在我的示例中,圖像均為 .jpg),如圖 9所示。

圖 9 提取圖像

private Bitmap getImageBitmap(String url) { 

  Bitmap ImageBitmap = null; 

  try { 

    URL ImageURL = new URL(url); 

    URLConnection ImageConnection = ImageURL.openConnection(); 

    ImageConnection.connect(); 

    InputStream ImageStream = ImageConnection.getInputStream(); 

    BufferedInputStream BufferedStream = new BufferedInputStream(ImageStream); 

    Log.e("Available buffer: ", String.valueOf(BufferedStream.available()));

    ImageBitmap = BitmapFactory.decodeStream(BufferedStream); 

    BufferedStream.close(); 

    ImageStream.close(); 

 } catch (Exception e) { 

    Log.e("Error getting bitmap", e.getMessage()); 

 } 

 return ImageBitmap; 

}

為提取圖像,我根據 URL 創建了新的連接,然後創建了 BufferedStream 以傳遞給 BitmapFactory.decodeStream 方法。 但是,decodeStream 和使用 BufferedStream 物件似乎有問題。 在此示例中,我將繼續使用它,因為我知道所測試的大小為 1 MB 或更大的圖像將不會正常工作;更糟的情況是其失敗卻沒有提示。

避免這些問題的最好方法是編寫代碼來手動讀取輸入流,然後將其傳遞給 BitmapFactory 物件。 BitmapFactory 完成相應任務後,我將返回點陣圖,設置 ImageBitmap 並將點陣圖指定到相關的 BlobInfo 物件的 ImageBitmap 屬性。 如果已經提取,我只需使用 BlobInfo 物件中的當前值即可。

運行時,我應該看到圖像的電影膠片,但是,到目前為止,我還不能放大圖像,選中縮略圖也沒有發生任何變化。 因此,我將為縮略圖添加了一個按一下偵聽程式,並根據所選項目的位置引用 BlobInfo ArrayList 並使用生成的 BlobInfo ImageBitmap 屬性作為 ZoomView(類型為 ImageView)setImageBitmap 調用的參數:

mThumbnails.setOnItemClickListener(new OnItemClickListener()

{

    public void onItemClick (AdapterView<?> parent, View v, int position, long id)

    {

      BlobInfo CurrentBlob = BlobList.get(position);

      mZoomView.setImageBitmap(CurrentBlob.ImageBitmap);

    }

  });

通用供應者

與快捷簡單的 Windows Phone 7 和 jQuery 示例相比,Android 示例是最繁雜的實現。 這可能歸因於我的熟悉程度,但是觀察我需跨這三者編寫的代碼,我認為原因不僅僅局限于便捷程度。 事實是,無論使用哪種設備平臺,使用 Windows Azure 均可為應用程式注入生命和能量。

如果沒有他人幫忙的話,與 Windows Azure 存儲的交互將沒那麼簡單了。 在本示例中,訪問可歸結為通過 REST 提取清單,然後根據返回的內容發出後續 HTTP Get 以直接提取專案。

Joseph Fultz 是 AMD 的一名軟體架構師,説明定義門戶、服務基礎結構和實現的整體體系結構和策略。之前,他是 Microsoft 的軟體架構師,協助 Microsoft 頂層企業和 ISV 客戶定義體系結構和設計解決方案。