Previsão: nublado

Armazenamento multiplataforma do Windows Azure

Joseph Fultz

Baixar o código de exemplo

Joseph FultzO Armazenamento do Windows Azure está longe de ser uma tecnologia específica para dispositivos, e isso é algo positivo. Este mês, observarei o desenvolvimento em três plataformas móveis: o Windows Phone 7, o jQuery e o Android.

Para esse fim, criarei um aplicativo simples para cada um que fará uma chamada REST para obter uma lista de imagens de um contêiner predeterminado do Armazenamento do Windows Azure e exibir as miniaturas em uma película fotográfica, com o balanço da tela exibindo a imagem selecionada, como visto na Figura 1.

Side-by-Side Storage Image Viewers

Figura 1 Visualizadores de imagem de armazenamento lado a lado

Preparação do contêiner de armazenamento

Precisarei de uma conta de Armazenamento do Windows Azure e da chave primária de acesso para a ferramenta que uso para carregamentos. No caso de acesso seguro do cliente que estou desenvolvendo, também precisaria desses itens. Essas informações podem ser encontradas no Portal de Gerenciamento da Plataforma Windows Azure.

Peguei algumas imagens aleatórias da Internet e do meu computador para carregar. Em vez de escrever um código de carregamento, usei o Windows Azure Storage Explorer, encontrado em azurestorageexplorer.codeplex.com. Por razões explicadas mais tarde, as imagens devem ter menos do que 1MB. Se estiver usando exatamente este código, o melhor é usar imagens de 512KB ou menos. Criei um contêiner nomeado Filecabinet; assim que o contêiner estiver criado e as imagens forem carregadas, o Windows Azure estará pronto.

Paradigmas das plataformas

Cada uma das plataformas traz determinadas construções, habilitadores e restrições. Para o cliente do Silverlight e do Android, usei o modelo familiar de marshaling de dados e coleção de objetos para consumo da interface do usuário. Embora o jQuery ofereça suporte para modelos, neste caso, achei mais fácil simplesmente buscar o XML e gerar o HTML necessário diretamente via jQuery, tornando a implementação do jQuery bastante simples. Não entrarei em detalhes sobre seu fluxo, mas para os outros dois exemplos, quero fornecer mais informações básicas.

Windows Phone 7 e Silverlight

Se estiver familiarizado com o desenvolvimento do Silverlight, não terá problemas para criar um novo projeto de aplicativo do Windows Phone 7 no Visual Studio 2010. Se não estiver, as coisas que você precisará entender para este exemplo são as coleções observáveis (bit.ly/18sPUF), os controles gerais XAML (como StackPanel e ListBox) e o WebClient. 

No início, a página principal do aplicativo faz uma chamada REST para o Armazenamento do Windows Azure, que é assíncrona por design. O Windows Phone 7 força este paradigma como um meio de garantir que nenhum aplicativo acabará bloqueando e travando o dispositivo. Os dados recuperados da chamada serão colocados no contexto de dados e serão ObservableCollection<> type.

Isso permite que o contêiner seja notificado e selecione as alterações quando os dados na coleção forem atualizados, sem que eu tenha que explicitamente executar uma atualização. Embora o Silverlight possa ser muito complexo para interfaces do usuário complexas, ele também oferece uma barreira baixa de entrada para tarefas relativamente simples, como esta. Com suporte interno para vinculação e chamadas de serviço adicionado ao suporte para toque e física na interface do usuário, a película fotográfica para visualização com zoom não é mais difícil do que escrever uma página ASP.NET com uma grade de vinculação de dados.

Activity do Android

O Android apresenta seu próprio paradigma de plataforma. Felizmente, não é algo ilógico ou difícil de entender. Para aqueles que são basicamente desenvolvedores .NET, é fácil mapear para construções, termos e tecnologias familiares. No Android, praticamente tudo que você quiser fazer com o usuário é representado como um objeto Activity. O Activity poderia ser considerado um dos quatro elementos básicos que podem ser combinados para criar um aplicativo: Activity, Service, Broadcast Receiver e Content Provider.

Para simplificar, mantive todos os códigos em Activity. Em uma implementação mais realista, o código para buscar e manipular as imagens do Armazenamento do Windows Azure seria implementado em um Service.

Este exemplo se assemelha mais a colocar o acesso ao banco de dados no código por trás do formulário. Da perspectiva do Windows, você pode pensar em um Activity como um Windows Forms que tem um meio — na verdade, a necessidade — de salvar e restaurar o estado. Um Activity tem um ciclo de vida muito semelhante ao do Windows Forms; um aplicativo poderia ser composto de Activities um-para-muitos, mas apenas um interagirá com o usuário de cada vez. Para obter mais informações sobre os conceitos básicos dos aplicativos do Android, acesse bit.ly/d3c7C

Este exemplo de aplicativo consistirá em um único Activity (bit.ly/GmWui), um BaseAdapter (bit.ly/g0J2Qx) e um objeto personalizado para conter informações sobre as imagens.

Criação das interfaces do usuário

As tarefas comuns em cada um dos paradigmas da interface do usuário incluem fazer a chamada REST, realizar marshaling do retorno para um tipo de dados útil, vincular e exibir, e manipular o evento de clique para mostrar a visualização com zoom da imagem. Primeiro, quero revisar os dados que voltam do REST Get, e o que é necessário em cada exemplo para transformar os dados em algo mais facilmente consumível.

Os dados

Dentro do código do aplicativo, prefiro trabalhar com objetos e não ter de manipular XML constantemente, por isso, criei um objeto para corresponder ao XML retornado da chamada de armazenamento. É parecido com o código na Figura 2.

Figura 2 Objeto para corresponder ao XML retornado da chamada de armazenamento

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

Em seguida, crio um objeto representativo para conter as informações que desejo. Em grande parte, me preocupo com os campos Name e URL. A Figura 3 apresenta uma visão da declaração para os objetos usados para cada exemplo.

Figura 3 Declaração para os objetos usados em cada exemplo de cliente

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;}

Nenhuma

O XML é consumido diretamente.

Criação das interfaces do usuário

Felizmente, a maneira como a interface do usuário é definida no Android e no Silverlight é semelhante quando se trata do uso de marcação, por isso, o entendimento de um praticamente garante o entendimento do outro. Além disso, ambos usam uma marcação XML para definir a interface do usuário. 

A implementação do jQuery simplesmente irá usar o HTML e gerar alguns elementos dinamicamente. A grande diferença entre os três quando se trata de escrever a marcação é o conhecimento dos controles/elementos que serão usados.

Começando pelo XAML do Windows Phone 7, abri MainPage.xaml e adicionei um <Grid/>, que conterá uma <ListBox /> para a película fotográfica. A caixa de listagem conterá um <ItemTemplate/> e um <DataTemplate /> que controlarão a maneira como os dados serão renderizados para a película fotográfica. Além disso, haverá um controle de <Image/> na grade para visualização com zoom da imagem. 

Para o Android, configurarei uma construção de interface do usuário semelhante usando um LinearLayout que contém dois widgets: Gallery e ImageView. Para aqueles que estão familiarizados com o Silverlight ou o Windows Presentation Foundation (WPF), um LinearLayout é como um StackPanel. Widgets são controles, e isso seria praticamente equivalente a um StackPanel que contém dois elementos: uma ListBox de imagens horizontal e um controle Image. Observe que isso é quase exatamente a mesma coisa usada no exemplo do Silverlight. O Gallery fornecerá uma visualização de miniaturas no estilo película fotográfica, e o ImageView mostrará uma visualização com zoom da miniatura selecionada.

Para o exemplo do jQuery, terei um <p/> vazio com o nome “output” atribuído à ID; será nesse local que as visualizações de miniaturas serão adicionadas dinamicamente via jQuery.

A marcação do Silverlight é um pouco mais longa devido à manipulação da vinculação e da renderização na marcação. Um pouco mais de códigos no lado do Android manipula o carregamento de dados. As marcações para cada um são exibidas na Figura 4.

Figura 4 Diferenças de marcação

Elemento Silverlight Android jQuery
Película fotográfica <ListBox /> <Gallery /> <p />
Visualização com zoom <Image /> <ImageView /> <img />

Marcação de início: 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>

Marcação de início: 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>

Marcação de início: jQuery

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

O código principal

A próxima etapa é criar o código que contata o Armazenamento do Windows Azure para obter informações sobre os blobs. A solicitação de busca assume a forma de http://[your storage account].blob.core.windows.net/[your container]?comp=list; my request looks like http://jofultz.blob.core.windows.net/filecabinet?comp=list. 

Como os resultados são XML, no Silverlight, usei o LINQ para fornecer um bom gráfico de objeto e padrão de acesso. Queria algo desse tipo para Java, mas no final, querendo ignorar a depuração de tecnologia desconhecida, optei por simplesmente trabalhar com a interface no estilo Document Object Model (DOM) fornecida pela API do DocumentBuilder. Como essa era uma operação somente leitura em alguns elementos, não foi grande coisa. Para o jQuery, é apenas uma consulta no estilo seletor para a qual o caminho está simplesmente lidando com o DOM de forma bastante semelhante.

Os métodos, mostrados na Figura 5 para busca são muito simples: como o contêiner é público, não preciso lidar com um proxy ou com alguma autenticação especial.

Figura 5 Métodos de busca:

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
  });
});

Para o Silverlight e para o jQuery, forneço um retorno de chamada para manipular a resposta, pois a solicitação é feita de forma assíncrona e a resposta é passada para o retorno de chamada para ser manipulada. Para o Android, a solicitação é emitida de forma síncrona. Para o jQuery e o Silverlight, precisarei de mais um método de suporte em cada um deles para manipular os dados e transformá-los em um formato consumível ou, no caso do jQuery, renderizá-los diretamente. 

Em seguida, preciso manipular a resposta em cada exemplo. Explicarei cada um deles para deixar cada exemplo o mais claro possível. Começando pelo exemplo do Windows Phone 7, implementei o método GetBlobsCompleted. Usei a sintaxe LINQ para separar o XML em uma lista enumerável de meu objeto personalizado, como mostrado na Figura 6.

Figura 6 Método 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);

}

Preciso mover os objetos para uma ObservableCollection<StorageImage>. Primeiro, limpo a lista, em seguida, executo um loop no resultado do LINQ e adiciono os objetos em minha ObservableCollection. Como a coleção está no DataContext e a ListBox está vinculada ao DataContext, ela automaticamente seleciona as alterações e atualiza os conteúdos da ListBox. 

Passando para o Java/Android, não tenho nada tão bom quanto o LINQ para usar, por isso, sou obrigado a trabalhar mais e separar os dados do DOM manualmente. O método MarshalToBlobInfos irá utilizar um objeto do documento XML e analisá-lo, criando uma ArrayList de BlobInfos.

Isso é muito semelhante a qualquer acesso no estilo DOM no .NET, JavaScript e assim por diante, como você pode ver na Figura 7.

Figura 7 O método 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;

  }

Por fim, escrevo o jQuery usado para trabalhar com o resultado e renderizar o HTML necessário. Esse é, de longe, o trecho mais curto do código. Mas, dependendo do nível de conforto, pode ser o mais obtuso:

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"));

      });

  });

}

Isso solicita cada elemento “Blob” do XML; para cada um, escrevo uma marca <img /> no elemento, cuja ID recebe o valor “output”. Uso a construção de seletor semelhante para extrair o valor de cada elemento dentro do elemento “Blob” atual necessário para renderizar adequadamente a marca <img />. Em seguida, crio um seletor para cada elemento <img /> resultante e adiciono um manipulador de cliques a ele. 

Até agora, meu código do Silverlight faz uma chamada REST para o Armazenamento do Windows Azure, analisa o resultado e exibe a película fotográfica. A etapa final é adicionar um manipulador de cliques para que a película fotográfica mostre a visualização com zoom da imagem. Adiciono um manipulador para SelectionChanged, que captura a StorageImage selecionada atualmente e atribui o valor de propriedade Image da propriedade Source do controle Image.

private void ImageList_SelectionChanged(object sender, SelectionChangedEventArgs e)

{

  ListBox lb = (ListBox)sender;

  StorageImage img = (StorageImage) lb.SelectedItem;

  ImageViewer.Source = img.Image;

}

Isso é tudo para o Silverlight no Windows Phone 7. O Android exigirá mais trabalho.

Graças à interface REST do Armazenamento do Windows Azure e à resposta XML direta, é simples fazer a chamada e analisar os resultados quando estou usando .NET, JavaScript ou, neste caso, Java no Android. Com os dois principais métodos de suporte disponíveis, adicionarei o código ao método onCreate do Activity. Dentro do Activity, após a linha para restaurar o estado existente, adicionarei as linhas de código para fazer a chamada para o Armazenamento do Windows Azure e analisar os resultados:

InputStream blobstream = GetBlobs();

DocumentBuilder docBuilder = 

  DocumentBuilderFactory.newInstance().newDocumentBuilder();

Document doc = docBuilder.parse(blobstream);

BlobList = MarshalToBlobInfos(doc);

Se o código fosse ser executado neste momento, não teria uma interface do usuário, mas quero a película fotográfica do exemplo do Windows Phone 7. A próxima etapa é vincular os elementos da interface do usuário. Chamarei setContentView, passando uma ID de recurso. Neste caso, é a ID de recurso do main.xml.

Se precisasse alterar a visualização de uma atividade, poderia chamá-la e passar um recurso diferente. No exemplo anterior da marcação main.xml, o Gallery e o ImageView têm as IDs de Thumbnails e ZoomView, respectivamente. Usarei essas IDs para referenciar os widgets (controles) e atribuir uma variável local para cada referência. Colocarei o código a seguir logo após o código anterior:

setContentView(R.layout.main);    

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

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

Agora, preciso criar um adaptador para ajudar a recuperar, formatar e esquematizar cada item na coleção. Antes de poder definir o adaptador para o Thumbnails Gallery, preciso criar um. Adiciono uma nova classe ao projeto nomeada ImageAdapter, que estende o BaseAdapter. Quando a defino como o adaptador para o Activity, ela deve fazer a contagem de chamadas e, posteriormente, chamar getView para cada item passando na posição do item, que usarei como o índice da ArrayList. Passarei um ponteiro para o Activity na ImageAdapter, pois precisarei de informações contextuais — em particular, precisarei da ArrayList que representa as imagens. A declaração e o construtor para a ImageAdapter ficariam assim:

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;



  }

No método getView, implementado como parte do adaptador, busco a imagem, crio uma miniatura e salvo a imagem original no objeto BlobInfo relacionado. No caso de uma implementação real, é preciso apenas pré-buscar uma série de imagens e limpá-las à medida que o usuário rola a tela, mantendo uma janela móvel com as imagens disponíveis com base na posição da película fotográfica. As miniaturas precisariam ser buscadas por um thread de segundo plano muito semelhante ao padrão necessário nos exemplos do Silverlight e do jQuery.

Além disso, o código de busca para o item especificamente pode ser mais bem colocado na substituição getItem. Com essas armadilhas, a Figura 8 mostra a implementação do getView.

Figura 8 A implementação do 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;    

}

O estado inicial da visualização é realizado pela primeira instrução condicional ao configurá-lo ou usar uma visualização passada para a função. Quando o if-else estiver passado, a visualização estará pronta — mas ainda não tenho a imagem. Para ajudar com isso, uso a posição parâmetro para buscar o objeto BlobInfo adequado e, em seguida, a propriedade URL para buscar a imagem (em meu caso, são todas .jpgs), como mostrado na Figura 9.

Figura 9 Buscando a imagem

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; 

}

Para buscar a imagem, crio uma nova conexão com base na URL e, em seguida, crio um BufferedStream para passar para o método BitmapFactory.decodeStream. No entanto, parece haver um problema com o decodeStream e com o uso do objeto BufferedStream. Para este exemplo, irei prosseguir e usá-lo, sabendo que nenhuma imagem com 1MB ou mais que testei funcionou; o que é pior, falha de forma silenciosa. 

A melhor forma de evitar esses problemas é escrever o código para ler manualmente o fluxo de entrada e, em seguida, passá-lo para o objeto BitmapFactory. Depois que o BitmapFactory concluiu seu trabalho, retorno o bitmap, defino o ImageBitmap e atribuo o bitmap para a propriedade ImageBitmap do objeto BlobInfo relacionado. Se ele já tiver sido buscado, simplesmente uso o valor atual no objeto BlobInfo. 

Quando for executado, devo visualizar uma película fotográfica de imagens; contudo, por enquanto, não consigo dar zoom em uma imagem, e nada acontece quando uma miniatura é selecionada. Portanto, adicionarei um ouvinte de cliques para as miniaturas e, com base na posição do item selecionado, referenciar a ArrayList do BlobInfo e usar a propriedade ImageBitmap resultante do BlobInfo como o parâmetro para a chamada setImageBitmap do ZoomView (do tipo ImageView):

mThumbnails.setOnItemClickListener(new OnItemClickListener()

{

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

    {

      BlobInfo CurrentBlob = BlobList.get(position);

      mZoomView.setImageBitmap(CurrentBlob.ImageBitmap);

    }

  });

Doador universal

O exemplo do Android foi a implementação mais complicada, em comparação aos exemplos rápidos e simples do Windows Phone 7 e do jQuery. Isso pode ser devido ao meu nível de familiaridade, mas observando o código que tive de escrever nos três, acho que há algo além do nível de conforto. O fato é que o uso do Windows Azure dá vida e potência para os aplicativos, independentemente da plataforma do dispositivo.

A interface com o Armazenamento do Windows Azure não poderia ser muito mais simples sem ter alguém para fazer isso por você. Neste caso, o acesso se resumiu a buscar uma lista via REST e, em seguida, emitir um HTTP Get subsequente com base no conteúdo do retorno para buscar o item diretamente.

Joseph Fultz é arquiteto de software na AMD, ajudando a definir a arquitetura geral e estratégia para infraestrutura e implementações de portais e serviços. Anteriormente, era arquiteto de software na Microsoft, trabalhando com seus clientes empresariais e ISV de camada superior, definindo soluções de arquitetura e design.