Cache com PersistenceManager para aplicações para dispositivos móveis em Flex

{lang: 'pt-BR'}

Recentemente passei por um problema de performance em uma aplicação para Android, feita em Flex. No projeto em questão estávamos usando um arquivo XML como fonte de dados e fazíamos algum processamento com os dados, para serem exibidos em gráficos. A cada vez que a tela da aplicação era recriada, uma nova consulta ao serviço de dados era feita, e todo o processamento pesado refeito.

Em aplicações Flex para dispositivos móveis, leia-se Android, a classe de aplicação MobileApplication controla a navegação das telas por meio do componente ViewNavigator.  Uma característica da classe ViewNavigator é que, por padrão, ela cria uma nova instância da tela a cada vez que a tela é exibida. Tudo para economizar a escassa memória dos dispositivos móveis. Para quem quiser se aprofundar neste mecanismo de navegação, sugiro que leia este artigo <http://opensource.adobe.com/wiki/display/flexsdk/View+and+ViewNavigator>.

A primeira idéia que veio a mente foi mudar o comportamento padrão do ViewNavigator e configurá-lo para que reutilizasse as instâncias já criadas das telas. Surgiu a pergunta: será que vale guardar em memória uma tela repleta de gráficos, ou apenas os valores que estão sendo manipulados?  Eis que surgiu a idéia de criar um cache das chamadas de serviço. O objetivo era minimizar as chamadas remotas fazendo um cache em memória de todas as respostas vindo da camada Delegate. Apesar de estarmos usando XML, esse é um problema bem recorrente em chamadas de serviço remotas.

A arquitetura ficou mais ou menos assim:

Uma vez definida a arquitetura, como implementar o cache? A estratégia usada foi, não só armazenar na memória da sessão corrente, mas persistir este dado usando Shared Object. Para quem nunca ouviu falar em Shared Object, entenda que isto é uma espécie de cookie do flash/flex, mas com o requinte de poder armazenar tipos complexos, como classes que você mesmo criou.

Indo um pouco além, decidi usar a API IPersistenceManager. Esta é uma interface introduzida pelo framework Flex Mobile, que prevê a criação de uma API bem simples para persistência. Baseia-se unicamente no mecanismo de (chave x valor) e a única implementação que existe atualmente utiliza SharedObjects. Nada impede que, no futuro, esta mesma API seja usada para armazenamento em um banco de dados, ou cartão de memória.

A classe PersistenceManager está disponível no pacote mobilecomponents.swc, que já é adicionado à biblioteca padrão quando criamos um projeto do tipo Flex Mobile (File > New > Flex Mobile Project).

var persistenceManager:IPersistenceManager = new PersistenceManager();
persistenceManager.initialize();

// Gravando um dado
persistenceManager.setProperty("nome", "Ricardo");

// Lendo um dado
var nome:String = persistenceManager.getProperty("nome");

Observação: ao gravar qualquer objeto de tipo complexo usando SharedObject, é preciso ‘registrar’ a classe, para que a serialização do objeto seja feita de maneira correta, do contrário, todos os objetos são convertidos para simples Object.

import flash.net.registerClassAlias;
// ...
var currentUser:User = new User();
currentUser.name="Ricardo";
currentUser.email="ricardo@example.com";

registerClassAlias("User", User);

persistenceManager.setProperty("user", currentUser);

// ...
var loadedUser:User = persistenceManager.getProperty("user") as User;

Neste caso, a classe User foi registrada para que o objeto, ao ser recuperado, mantenha todas as suas características originais.

Em minha camada de cache, foi criada uma assinatura para toda chamada de método, composta pelo nome do método + os parâmetros. Assim, a chamada do método getMedia(5, 9) geraria uma chave “getMedia,5,9”.

Agora que resolvemos o problema da persistência, como inserir uma camada de cache entre o delegate e o controller, de forma relativamente transparente? Quero dizer, ninguém está disposto a ter que reescrever todos os métodos com as rotinas de controle de cache. A saída é usar uma classe de proxy.

A  classe flash.utils.Proxydo ActionScript tem um comportamento bem interessante. Ao estendê-la, a sua classe terá a capacidade de de exceutar qualquer chamada de método, independente se ele foi definido ou não. Ou seja, é possível definir qual será o comportamento de um método com qualquer nome, mesmo que ele não tenha sido criado. Vamos ao nosso exemplo:

package example{
    public class DelegateProxy extends Proxy{

        private var delegate:Object;

        public function DelegateProxy(delegate:Object){
              super();
	      this.delegate = delegate
        }

        override flash_proxy function callProperty(methodName:*, ... args):*{
              if (isInCache()){
                   return getFromCache();
              } else {
                  var returnValue:Object = delegate[methodName].apply(delegate, args);
                  saveValueInCache(resturnValue);
                  return returnValue;
              }
        }
    }

}
// ...
var proxy:DelegateProxy  = new DelegateProxy( new ProductDelegate() );
proxy.loadAllProducts();

No código acima uma classe de proxy está sendo criada com o nome DelegateProxy. É possível chamar qualquer método desta classe, mesmo que ele não exista, pois todas estas chamadas serão manipuladas pelo método callProperty(). Este método recebe o nome do método e seus parâmetros. Em nosso caso, estamos verificando se o dado está no cache, pelo método isInCache() (que não está implementado no exemplo). Caso esteja, retorna o valor armazenado no cache através do método getFromCache() (também não implementado no exemplo). Se o dado não estiver no cache, o método correspondente será chamado no objeto de delegate.

Em nosso exemplo, a classe DelegateProxy recebe a instância do Delegate no construtor. Ao chamar um método de nosso proxy, estaremos chamando o método de mesmo nome no objeto de delegate. Assim sendo, a chamada proxy.loadAllProducts() irá, consequentemente, invocar o método delegate.loadAllProducts();

Convido você para abrir o arquivo do projeto com a implementação de um exemplo totalmente funcional. Não utilizei nenhum framework MVC para não dificultar o entendimento. A arquitetura empregada é compatível com a de aplicação enterprise, mesmo que o exemplo seja extremamente simples. Também fiz uso de metadata para definir quais métodos deveriam figurar no cache , mas essa explicação eu deixo para um próximo post.

Código fonte do projeto de exemplo: DelegateCacheMobile.zip

{lang: 'pt-BR'}