预测:多云

多平台 Windows Azure 存储

Joseph Fultz

下载代码示例

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 和 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

直接使用 XML。

创建 UI

幸运的是,在 Android 和 Silverlight 中定义 UI 的方式与其使用标记的方式相似,因此,理解一个几乎就可以保证能够理解另一个。 此外,这两者均使用 XML 标记来定义 UI。

jQuery 实现只是使用 HTML 并动态生成一些元素。 写标记时,三者之间的最大区别为使用的控件/元素的知识。

从 Windows Phone 7 XAML 开始,打开 MainPage.xaml,然后添加 <Grid/>,其将包含电影胶片的 <ListBox />。 列表框包含用于控制电影胶片数据呈现方式的 <ItemTemplate/> 和 <DataTemplate />。 此外在网格中还有一个 <Image/> 控件,用于操纵图像的缩放视图。

对于 Android,我将使用包含以下两个小组件的 LinearLayout 建立一个类似的 UI 构造:Gallery 和 ImageView。 对于熟悉 Silverlight 或 Windows Presentation Foundation (WPF) 的人而言,LinearLayout 类似于 StackPanel。 小组件即为控件,这大致可以等同于包含以下两个元素的 StackPanel:图像的水平 ListBox 和图像控件。 请注意,这也几乎完全是 Silverlight 示例中所使用的。 Gallery 提供缩略图的电影胶片样式视图,ImageView 显示所选缩略图的缩放视图。

对于 jQuery 示例,我将有一个空的 <p/>,id 名称为“output”;在这里将通过 jQuery 动态添加缩略图视图。

因为要处理标记内的绑定和呈现,Silverlight 标记要长一些。 对于 Android,则需要略微多一些的代码来处理数据加载。 三者的标记如图 4 所示。

图 4 标记的区别

元素 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 客户定义体系结构和设计解决方案。