Microsoft Azure

Dados conectados ocasionalmente nos aplicativos móveis entre plataformas

Kevin Ashley

Baixar o código de exemplo

A maioria dos aplicativos móveis precisa permanecer offline por alguns períodos de tempo, e os usuários móveis esperam que seus aplicativos trabalhem graciosamente em ambos os estados conectado e desconectado. Centenas de milhões de usuários de dispositivos móveis podem não estar cientes das necessidades que os aplicativos têm com relação aos estados online e offline; eles simplesmente esperamque os aplicativos funcionem sob quaisquer circunstâncias. Neste artigo, vou lhe mostrar formas de ativar seu aplicativo móvel para trabalhar em ambos os estados e sincronizar dados graciosamente com a nuvem no Windows, iOS e Android usando ferramentas Xamarin entre plataformas e Serviços Móveis do Microsoft Azure.

Como um desenvolvedor de aplicativo móvel, eu encontrei a necessidade de sincronizar dados offline desde o início. Para o meu aplicativo de esqui Winter Sports (winter-sports.co) e aplicativo de acompanhamento de atividade Active Fitness (activefitness.co), espera-se um pouco que você não possa obter uma conexão decente nas pistas ou na corrida. Assim, os aplicativos precisam ser capazes de sincronizar dados coletados offline, sem impacto significativo na vida útil da bateria e confiabilidade. Em outras palavras, os aplicativos precisam funcionar de forma eficiente em quaisquer condições.

Há mais do que se aparenta quando se considera o armazenamento persistente. Para começar, há várias abordagens para a sincronização, o que significa que pode ser feito a partir de um aplicativo ou como um processo em segundo plano no sistema operacional. Além disso, existem diferentes tipos de armazenamentos de dados, tais como dados semi-estruturados a partir de sensores e dados potencialmente relacionais armazenados no SQLite. Também é importante implementar uma política de resolução de conflitos para que a perda de dados e a degradação sejam minimizadas. Finalmente, há uma grande variedade de formatos de dados para gerenciar, como binário, JSON, XML e personalizado.

Os aplicativos móveis tipicamente armazenam vários tipos de dados. Dados estruturados, tais como JSON ou XML, são normalmente usados para as configurações locais, arquivos locais ou cache. Além de armazenar dados em arquivos, você também tem a opção de mecanismos de armazenamento, como SQLite, para armazenar e consultar dados. Os aplicativos móveis também podem armazenar blobs, arquivos de mídia e outros tipos de dados binários grandes. Para estes tipos de dados, demonstrarei técnicas que tornam a transferência de dados mais confiáveis em relação aos dispositivos conectados ocasionalmente. Fornecerei uma visão geral de várias tecnologias. Por exemplo, em vez de focar estritamente na sincronização offline para dados estruturados, eu lhe mostrarei um quadro mais amplo de ambas as técnicas de sincronização de dados estruturados e não estruturados (blob). Eu também usarei uma abordagem entre plataformas em todos os lugares ao longo destes exemplos.

A abordagem entre plataformas

Pelo fato de ele estar se tornando cada vez mais popular para conectar dispositivos compatíveis com sensores para a nuvem, eu incluí os dados de sensores de dispositivos no meu projeto para demonstrar vários métodos para sincronizá-los com a nuvem. Eu discutirei três cenários: sincronização de dados offline, sincronização de dados manual, e sincronização de mídia grande e dados de binário. O exemplo de código que acompanha é completamente entre plataforma, com 100% de reutilização para Android, iOS e Windows. Para conseguir isso, eu usei Xamarin.Forms, ferramenta XAML/C# entre plataformas que funciona bem no iOS, Android e Windows e está sendo integrado com as ferramentas do Visual Studio (consulte o vídeo do Microsoft Channel 9, "Desenvolvimento móvel entre plataformas usando o Visual Studio", em bit.ly/1xyctO2).

No exemplo de código são duas classes que gerenciam os modelos de dados entre plataforma: SensorDataItem e SensorModel. Esta abordagem pode ser usada por muitos aplicativos de acompanhamento de esportes e fitness, tais como o Active Fitness, ou aplicativos que precisam sincronizar dados estruturados de armazenamento local com a nuvem. Eu adicionei latitude, longitude, velocidade e distância para a classe SensorDataItem como um exemplo de dados coletados pelos sensores, como GPS, para ilustrar a idéia. É claro que a estrutura de dados no aplicativo real pode ser mais complicado - e inclui dependências - mas o meu exemplo lhe dará uma idéia do conceito.

Sincronizando dados estruturados com sincronização offline

A sincronização offline é um novo recurso poderoso nos Serviços Móveis do Azure. Você pode fazer referência do pacote de Serviços Móveis do Azure em seu projeto do Visual Studio usando NuGet. Mais importante, ele também é suportado em aplicativos entre plataformas com a nova versão do SDK dos Serviços Móveis do Azure. Isso significa que você pode usar esse recurso em seus aplicativos do Windows, iOS e Android que ocasionalmente precisam se conectar à a nuvem e sincronizar seus estados.

Começarei com alguns conceitos.

Tabela de sincronização Este é um novo objeto no SDK dos Serviços Móveis do Azure criado para distinguir as tabelas que suportam a sincronização das tabelas "locais". As tabelas de sincronização implementam a interface IMobileServiceSyncTable<T> e incluem métodos de "sincronização" adicionais, como PullAsync, PushAsync e Purge. Se você quiser sincronizar os dados do sensor offline com a nuvem, você precisa usar tabelas de sincronização em vez de tabelas padrão. No meu exemplo de código, eu inicializei minha tabela do sensor de sincronização de dados usando a chamada GetSyncTable<T>. No portal de Serviços Móveis do Azure, eu criei uma tabela regular chamada SensorDataItem e acrescentei o código na Figura 1 à inicialização do meu cliente (você pode baixar o código-fonte completo em bit.ly/11yZyhN).

O Contexto de sincronização Este é responsável por sincronizar dados entre armazenamentos locais e remotos. Os Serviços Móveis do Azure enviam com o SQLiteStore, que é baseado na biblioteca SQLite popular. O código da Figura 1 faz agumas coisas. Ele verifica se um contexto de sincronização já foi inicializado e, se não, ele cria uma nova instância de armazenamento do SQLite do arquivo local.db, define a tabela com base na classe SensorDataItem e inicializa o armazenamento. Para lidar com as operações pendentes, o contexto de sincronização utiliza uma fila, acessível através da propriedade PendingOperations. O contexto de sincronização fornecido pelos Serviços Móveis do Azure também é "inteligente" o suficiente para distinguir as operações de atualização acontecendo no armazenamento local. A sincronização é feita automaticamente pelo sistema, para que você não tenha que chamar manualmente e desnecessariamente a nuvem para manter os dados. Isso é bom porque reduz o tráfego e aumenta a vida útil da bateria do dispositivo.

Figura 1 Aproveitando o objeto MobileServiceSQLiteStore para Sincronização

// Initialize the client with your app URL and key
client = new MobileServiceClient(applicationURL, applicationKey);
// Create sync table instance
todoTable = client.GetSyncTable<SensorDataItem>();
// Later in code
public async Task InitStoreAsync()
{
  if (!client.SyncContext.IsInitialized)
  {
    var store = new MobileServiceSQLiteStore(syncStorePath);
    store.DefineTable<SensorDataItem>();
    await client.SyncContext.InitializeAsync(store,
      new MobileServiceSyncHandler   ());
  }
}

A Operação por Push Isso permite que você sincronize dados explicitamente entre o armazenamento local e o armazenamento em nuvem, empurrando dados locais para o servidor. É importante ressaltar que, na versão atual do SDK dos Serviços Móveis do Azure, você precisa invocar o push e pull explicitamente para sincronizar o contexto. A operação por push é executada em todo o contexto de sincronização para ajudá-lo a preservar as relações entre tabelas. Por exemplo, se eu tiver relações entre as tabelas, a minha primeira inserção me dará uma identificação do objeto, e inserções subsequentes preservarão a integridade referencial:

 

await client.SyncContext.PushAsync();

A Operação por Pull Isso permite que você sincronize dados explicitamente puxando dados de um armazenamento remoto para o armazenamento local. Você pode usar o LINQ para especificar um subconjunto de dados, ou qualquer consulta OData. Ao contrário de operações por push, que acontecem em um contexto inteiro, o pull é executado no nível da tabela. Se os itens estão pendentes na fila de sincronização, esses itens são empurrados primeiro - antes de executar as operações por pull - para evitar perda de dados (ainda outro benefício do uso dos Serviços Móveis do Azure para sincronização de dados). Neste exemplo, eu puxo os dados que tem uma velocidade diferente de zero (coletados pelo meu sensor de GPS, por exemplo), armazenados no início do servidor:

var query = sensorDataTable.Where(s => s.speed > 0);
await sensorDataTable.PullAsync(query);

A Operação de limpeza Isso apaga dados especificados a partir de tabelas locais e remotas, causando a sincronização. Semelhante ao pull, você pode usar o LINQ para especificar um subconjunto de dados, ou qualquer consulta OData. Neste exemplo, eu limpo os dados que tem distância sero (que também podem vir do meu sensor GPS) das minhas tabelas:

var query = sensorDataTable.Where(s => s.distance == 0);
await sensorDataTable.PurgeAsync(query);

Processamento adequado dos conflitos Esta é uma parte importante de sua estratégia de sincronização de dados quando os dispositivos se tornam online e offline. Os conflitos acontecerão, e o SDK dos Serviços Móveis do Azure fornece maneiras de lidar com os conflitos. Para a resolução de conflitos funcionar, eu ativei a coluna propriedade da Versão no objeto SensorDataItem. Além disso, eu criei a classe ConflictHandler, que implementa a interface IMobileServiceSyncHandler. Quando você precisa resolver um conflito, você tem três opções: manter o valor do cliente, manter o valor do servidor ou abortar a operação por push.

No meu exemplo, verifique a classe ConflictHandler. Quando é inicializada, no construtor eu a defini com uma das três políticas de resolução de conflitos:

public enum ConflictResolutionPolicy
{
  KeepLocal,
  KeepRemote,
  Abort
}

Dependendo do método, cada vez que ocorre um conflito, no método ExecuteTableOperationAsync, eu aplico automaticamente a minha política de resolução de conflitos. Quando eu inicializo o meu contexto de sincronização, eu passo a minha classe ConflictHandler ao contexto de sincronização com a minha política de resolução de conflitos padrão:

await client.SyncContext.InitializeAsync(
  store,
  new ConflictHandler(client, ConflictResolutionPolicy.KeepLocal)
);

Para ler mais sobre a resolução de conflitos, consulte o exemplo MSDN, "Serviços Móveis do Azure - Processamento dos conflitos com WP8 offline", em bit.ly/14FmZan e o artigo de documentação do Azure, "Processamento dos conflitos com sincronização de dados offline nos Serviços Móveis", em bit.ly/1zA01eo.

Sincronizando dados serializados manualmente

Antes dos Serviços Móveis do Azure começar a oferecer sincronização offline, os desenvolvedores tinham que implementar a sincronização de dados manualmente. Então, se você desenvolver um aplicativo que, ocasionalmente, precisa sincronizar os dados e você não usar o recurso de sincronização offline dos Serviços Móveis do Azure, você pode fazê-lo manualmente (embora eu recomendo olhar para o recurso de sincronização offline). Você pode usar tanto a serialização de objeto direta (como um serializador JSON) nos arquivos, ou os mecanismos de armazenamento de dados, como o SQLite. A principal diferença entre o mecanismo de sincronização offline e sincronização manual é que você precisa fazer a maior parte do trabalho sozinho no segundo. Um dos métodos para detectar se os dados foram sincronizados é usar a propriedade Id de qualquer objeto em seu modelo de dados. Por exemplo, consulte a classe SensorDataItem usada no meu exemplo anterior, observe os campos Id e Versão, mostrado na Figura 2.

Figura 2 Estrutura de dados para sincronização de dados

public class SensorDataItem
{
  public string Id { get; set; }
  [Version]
  public string Version { get; set; }
  [JsonProperty]
  public string text { get; set; }
  [JsonProperty]
  public double latitude { get; set; }
  [JsonProperty]
  public double longitude { get; set; }
  [JsonProperty]
  public double distance { get; set; }
  [JsonProperty]
  public double speed { get; set; }
}

Quando um registro é inserido em um banco de dados remoto, os Serviços Móveis do Azure criam automaticamente um Id e o atribui ao objeto, de modo que o Id será um valor não nulo quando o registro foi inserido, e nulo quando o registro nunca foi sincronizado com o banco de dados:

// Manually synchronizing data
if (item.Id == null)
{
  await this.sensorDataTable.InsertAsync(item);
}

Sincronizar exclusões e atualizações manualmente é um processo muito mais difícil e complicado e está fora do escopo deste artigo. Se você está procurando uma solução de sincronização abrangente, procure por recursos de sincronização offline do SDK dos Serviços Móveis do Azure. É claro que, neste exemplo, é simples a comparação com situações da vida real, mas se você estiver procurando implementar a sincronização de dados manual, isto pode lhe dar uma idéia de por onde começar. Certamente, pelo fato do SDK dos Serviços Móveis do Azure oferecer uma solução bem pensada testada para a sincronização de dados, eu recomendo tentar a abordagem de sincronização offline, especialmente em aplicativos que requerem um método testado sólido para manter os dados locais e remotos em sincronia.

Transferência de dados binários, Fotos e Mídia para a nuvem

Além de dados estruturados, os aplicativos muitas vezes precisam sincronizar dados ou arquivos binários ou não estruturados. Pense em um aplicativo móvel de fotos ou um aplicativo que precisa fazer carregamento de um arquivo binário para a nuvem, como uma foto ou vídeo. Pelo fato de eu explorar esse assunto em um contexto entre plataformaa, plataformas diferentes têm capacidades diferentes. Mas eles realmente são tão diferentes? A sincronização dos dados do blob pode ser alcançada de diversas maneiras, como com um serviço em processo ou aproveitando um serviço de transferência de segundo plano específico da plataforma fora do processo. Para gerenciar os downloads, eu também forneci uma classe TransferQueue simples, baseada em ConcurrentQueue. Toda vez que eu preciso enviar um arquivo para carregar ou baixar, eu adiciono um novo objeto Job à fila. Este também é um padrão comum na nuvem, onde você coloca o trabalho inacabado em uma fila e, em seguida, tem algum outro processo de segundo plano lendo a partir da fila e concluindo o trabalho.

Transferência do arquivo em processo Às vezes você precisa transferir arquivos diretamente de dentro de seu aplicativo. Esta é a maneira mais óbvia de lidar com as transferências blob, mas como mencionado anteriormente, tem desvantagens. Para proteger o UX, o sistema operacional coloca um limite de utilização sobre o uso de largura de banda e recursos do aplicativo. No entanto, isso pressupõe que o usuário esteja usando o aplicativo para trabalhar. No caso de aplicativos ocasionalmente desconectados, pode não ser a melhor ideia. A vantagem de transferir arquivos diretamente de um aplicativo é que ele tem o controle total sobre a transferência de dados. Com o controle total, um aplicativo pode tirar vantagem de métodos de assinatura de acesso compartilhado para gerenciar carregamentos e downloads. Você pode ler sobre as vantagens da publicação do Blog da Equipe do Microsoft Azure Storage, "Introducing Tabela SAS (Shared Acesso Assinatura), Queue SAS e Update para Blob SAS," no bit.ly/1t1Sb94. Embora nem todas as plataformas ofereçam essa funcionalidade integrada, se você estiver disposto a usar uma abordagem baseada em REST, certamente você pode tirar vantagem das teclas SAS dos Serviços do Azure Storage. A desvantagem para a transferência de arquivos diretamente de um aplicativo é dupla. Primeiro, você deve escrever mais código. Segundo, o aplicativo deve estar em execução, potencialmente drenando a vida da bateria e limitando o UX. As melhores soluções alavancam alguma elegância das técnicas de sincronização de dados integrados.

Eu forneci o código fonte para uma operação de carrregamento e download de base em um aplicativo Xamarin entre plataforma em BlobTransfer.cs (veja o download do código que acompanha). Este código deve funcionar em iOS, Android e Windows. Para utilizar o armazenamento de arquivos independente de plataforma, eu usei o pacote PCLStorage NuGet (Pacote de instalação PCLStorage), que me permite abstrair operações de arquivo sobre iOS, Android e Windows.

Para iniciar uma transferência em processo, eu chamo o meu método TransferQueue AddInProcessAsync:

var ok = await queue.AddInProcessAsync(new Job {
  Id = 1, Url = imageUrl, LocalFile = String.Format("image{0}.jpg", 1)});

Isso programa uma operação de download em processo típica, definida no objeto BlobTransfer, conforme mostrado na Figura 3.

Figura 3 Operação de Download (Código entre plataforma)

public static async Task<bool> DownloadFileAsync(
  IFolder folder, string url, string fileName)
{
  // Connect with HTTP
  using (var client = new HttpClient())
  // Begin async download
  using (var response = await client.GetAsync(url))
  {
    // If ok?
    if (response.StatusCode == System.Net.HttpStatusCode.OK)
    {
      // Continue download
      Stream temp = await response.Content.ReadAsStreamAsync();
      // Save to local disk
      IFile file = await folder.CreateFileAsync(fileName,
        CreationCollisionOption.ReplaceExisting);
      using (var fs =
        await file.OpenAsync(PCLStorage.FileAccess.ReadAndWrite))
      {
        // Copy to temp folder
        await temp.CopyToAsync(fs); 
        fs.Close();
        return true;
      }
    }
    else
    {
      Debug.WriteLine("NOT FOUND " + url);
      return false;
    }
  }
}

É claro que, se você quiser fazer carregamento de um arquivo, você pode fazê-lo em processo usando o método mostrado na Figura 4.

Figura 4 Operação de Carregamento (Código entre plataforma)

public static async Task UploadFileAsync(
  IFolder folder, string fileName, string fileUrl)
{
  // Connect with HTTP
  using (var client = new HttpClient()) 
  {
    // Start upload
    var file = await folder.GetFileAsync(fileName);
    var fileStream = await file.OpenAsync(PCLStorage.FileAccess.Read);
    var content = new StreamContent(fileStream);
    // Define content type for blob
    content.Headers.Add("Content-Type", "application/octet-stream");
    content.Headers.Add("x-ms-blob-type", "BlockBlob");
    using (var uploadResponse = await client.PutAsync(
      new Uri(fileUrl, UriKind.Absolute), content))
    {
      Debug.WriteLine("CLOUD UPLOADED " + fileName);
      return;
    }
  }
}

Transferência de arquivo fora do processo usando o serviçod e transferência específica do SO Baixar e carregar serviços de transferência de arquivos integrados tem muitas vantagens. A maioria das plataformas oferecem um serviço que pode transferir arquivos grandes (tanto carregamento quanto download) de forma autônoma como um serviço de segundo plano. Você deve aproveitar tais serviços, sempre que possível, porque eles correm fora de processo, isto é, o seu aplicativo não está limitado na transferência de dados reais, o que pode ser bastante caro em termos de recursos consumidos. Além disso, o aplicativo não tem que ficar na memória o tempo todo para transferir arquivos, e o SO normalmente fornece um mecanismo de resolução de conflitos (repetição) para reiniciar carregamentos e downloads. Outras vantagens incluem menos código para escrever, o aplicativo não precisa estar ativo (o SO gerencia sua própria fila de carregamentos e downloads) e o aplicativo é mais eficiente em memória/recurso. O desafio, no entanto, é que este método requer a implementação específica da plataforma: O iOS, Windows Phone e assim por diante têm suas próprias implementações de transferência de segundo fundo.

Conceitualmente, um carregamento confiável para os arquivos em um aplicativo móvel usando um serviço fora do processo específico do SO é semelhante à implementação no aplicativo. Mas o gerenciamento da fila de carregamento/download real é terceirizado para o serviço de transferência do SO. Para os aplicativos do Windows Phone Store e Windows Store, os desenvolvedores podem usar objetos do BackgroundDownloader e BackgroundUploader. Para iOS 7 e superior, o NSUrlSession fornece métodos CreateDownloadTask e CreateUploadTask para iniciar downloads e carregamentos.

Usando o meu exemplo anterior, agora eu preciso chamar meu método fora de processo para invocar uma chamada usando um serviço de transferência de segundo plano específico do SO. Na verdade, pelo fato do serviço ser tratado pelo OS, eu marcarei 10 downloads para demonstrar que o aplicativo não está bloqueando e a execução é tratada pelo OS (neste exemplo, eu usei o serviço de transferência de segundo plano do iOS):

for (int i = 0; i < 10; i++)
{
  queue.AddOutProcess(new Job { Id = i, Url = imageUrl,
    LocalFile = String.Format("image{0}.jpg", i) });
}

Para um exemplo de um serviço de transferência de segundo plano para iOS, verifique BackgroundTransferService.cs. No iOS primeiro você precisa inicializar uma sessão de segundo plano com CreateBackgroundSessionConfiguration (observe que isso só funciona no iOS 8 ou posterior):

using (var configuration = NSUrlSessionConfiguration.
  CreateBackgroundSessionConfiguration(sessionId))
{
  session = NSUrlSession.FromConfiguration(configuration);
}

Então você pode enviar uma longa operação de carregamento ou download e o SO lidará com isso independentemente do seu aplicativo:

using (var uri = NSUrl.FromString(url))
using (var request = NSUrlRequest.FromUrl(uri))
{
  downloadTask = session.CreateDownloadTask(request);
  downloadTask.Resume();
}

Você também precisa pensar em um mecanismo de enfileiramento para fazer o carregamento e download de blobs de forma confiável.

Código de exemplo e Próximas etapas

Todo o código de amostra para este artigo está disponível no GitHub em bit.ly/11yZyhN. Para usar este código-fonte, você pode usar o Visual Studio com Xamarin ou Xamarin Studio, que está disponível em xamarin.com. O projeto usa Xamarin.Forms entre plataformas e a biblioteca de Serviços Móveis do Azure com sincronização offline. Para as próximas etapas, seria interessante ver um serviço fora do processo adicionado a bibliotecas comunitárias, tais como Xamarin Labs, bem como capacidades de enfileiramento e resolução de conflitos semelhantes ao que está atualmente previsto para os dados estruturados no SDK sincronizado offline dos Serviços Móveis do Azure.

Para recapitular, os Serviços Móveis do Microsoft Azure fornecem uma maneira poderosa e eficiente de sincronizar dados offline. Você pode usar esses serviços em um cenário entre plataforma no Windows, Android e iOS. A Microsoft também fornece SDKs nativos de fácil utilização que trabalham em todas as plataformas. Você pode melhorar a confiabilidade de seu aplicativo em cenários desconectados ao integrar estes serviços e ao adicionar sincronização offline em seus aplicativos.


Kevin Ashley é um arquiteto evangelista da Microsoft. Ele é co-autor de "Professional Windows 8 Programming" (Wrox, 2012) e um desenvolvedor dos melhores aplicativos e jogos, a maioria notavelmente de Active Fitness (activefitness.co). Ele apresenta frequentemente sobre tecnologia em vários eventos, apresentações da indústria e Webcasts. Ele trabalha com startups e parceiros, consultoria sobre design de software, estratégia de negócios e tecnologia, arquitetura e desenvolvimento. Siga ele em seu blog em kevinashley.com e no Twitter em twitter.com/kashleytwit.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Greg Oliver e Bruno Terkaly