Guia para iniciantes sobre como usar o cache do aplicativo

Introdução

Está se tornando cada vez mais importante que os aplicativos baseados na Web possam ser acessados off-line. Sim, todos os navegadores podem armazenar páginas e recursos em cache por longos períodos, se instruídos a fazer isso, mas o navegador pode retirar itens individuais do cache a qualquer momento para abrir espaço para outras coisas. O HTML5 resolve alguns dos inconvenientes de ficar off-line com a interface ApplicationCache. Usar a interface de cache dá ao seu aplicativo três vantagens:

  1. Navegação off-line: os usuários podem navegar em todo o site quando estão off-line
  2. Velocidade: os recursos vêm direto do disco, sem viagem à rede.
  3. Resiliência: se o seu site cair por "manutenção", por exemplo, se alguém quebra tudo acidentalmente, seus usuários terão a experiência off-line

O cache do aplicativo (ou AppCache) permite que um desenvolvedor especifique quais arquivos o navegador deve armazenar em cache e disponibilizar para usuários off-line. O app será carregado e funcionará corretamente, mesmo que o usuário pressione o botão "Atualizar" enquanto estiver off-line.

O arquivo de manifesto em cache

O arquivo de manifesto de cache é um arquivo de texto simples que lista os recursos que o navegador precisa armazenar em cache para acesso off-line.

Como fazer referência a um arquivo de manifesto

Para ativar o cache, inclua o atributo de manifesto na tag html do documento:

<html manifest="example.appcache">
  ...
</html>

O atributo manifest precisa ser incluído em todas as páginas do aplicativo da Web que você quer armazenar em cache. O navegador não armazenará uma página em cache se ela não tiver o atributo manifest, a menos que ela esteja explicitamente listada no próprio arquivo de manifesto. Isso significa que qualquer página acessada pelo usuário que inclua um manifest será implicitamente adicionada ao cache do aplicativo. Portanto, não é necessário listar todas as páginas no manifesto. Se uma página apontar para um manifesto, não há como impedir que ela seja armazenada em cache.

Para ver os URLs controlados pelo cache do aplicativo, acesse about://appcache-internals/ no Chrome. Aqui, é possível limpar os caches e visualizar as entradas. Existem ferramentas para desenvolvedores semelhantes no Firefox.

O atributo manifest pode apontar para um URL absoluto ou um caminho relativo, mas um URL absoluto precisa estar na mesma origem que o aplicativo da Web. Um arquivo de manifesto pode ter qualquer extensão, mas precisa ser disponibilizado com o tipo MIME correto (confira abaixo).

<html manifest="http://www.example.com/example.mf">
  ...
</html>

Um arquivo de manifesto precisa ser disponibilizado com o tipo MIME text/cache-manifest. Talvez seja necessário adicionar um tipo de arquivo personalizado ao servidor da Web ou à configuração do .htaccess.

Por exemplo, para veicular esse tipo MIME no Apache, adicione esta linha ao arquivo de configuração:

AddType text/cache-manifest .appcache

Ou no seu arquivo app.yaml no Google App Engine:

- url: /mystaticdir/(.*\.appcache)
  static_files: mystaticdir/\1
  mime_type: text/cache-manifest
  upload: mystaticdir/(.*\.appcache)

Esse requisito foi removido da especificação há algum tempo e não é mais exigido pelas versões mais recentes do Chrome, Safari e Firefox, mas você precisa do tipo MIME para funcionar em navegadores mais antigos e no IE11.

Estrutura de um arquivo de manifesto

O manifesto é um arquivo separado ao qual você vincula por meio do atributo de manifesto no elemento html. Um manifesto simples é mais ou menos assim:

CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js
http://cdn.example.com/scripts/main.js

Este exemplo armazenará em cache quatro arquivos na página que especifica esse arquivo de manifesto.

Algumas observações:

  • A string CACHE MANIFEST é a primeira linha e é obrigatória.
  • Os arquivos podem ser de outro domínio
  • Alguns navegadores restringem a quantidade de cota de armazenamento disponível para seu app. No Chrome, por exemplo, o AppCache usa um pool compartilhado de armazenamento TEMPORÁRIO que outras APIs off-line podem compartilhar. Se você estiver criando um app para a Chrome Web Store, o uso de unlimitedStorage remove essa restrição.
  • Se o manifesto retornar um erro 404 ou 410, o cache será excluído.
  • Se o download do manifesto ou de um recurso especificado nele falhar, todo o processo de atualização do cache falhará. O navegador vai continuar usando o cache do aplicativo antigo em caso de falha.

Vamos analisar um exemplo mais complexo:

CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
*

# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg

As linhas que começam com '#' são linhas de comentários, mas também podem servir para outros fins. O cache de um aplicativo só é atualizado quando seu arquivo de manifesto é alterado. Por exemplo, se você editar um recurso de imagem ou alterar uma função JavaScript, essas alterações não serão armazenadas novamente em cache. Modifique o arquivo de manifesto para solicitar que o navegador atualize os arquivos armazenados em cache.

Evite usar um carimbo de data/hora com atualização contínua ou uma string aleatória para forçar as atualizações todas as vezes. O manifesto é verificado duas vezes durante uma atualização, uma no início e outra após a atualização de todos os arquivos armazenados em cache. Se o manifesto tiver sido alterado durante a atualização, é possível que o navegador tenha buscado alguns arquivos de uma versão e outros de outra versão, fazendo com que ele não aplique o cache e tente de novo mais tarde.

Embora o cache seja atualizado, o navegador não usará esses arquivos até que a página seja atualizada, já que as atualizações acontecem depois que a página é carregada a partir da versão atual do cache.

Um manifesto pode ter três seções distintas: CACHE, NETWORK e FALLBACK.

CACHE:
Esta é a seção padrão para entradas. Os arquivos listados nesse cabeçalho (ou imediatamente após CACHE MANIFEST) serão armazenados em cache explicitamente após o primeiro download. NETWORK:
Os arquivos listados nesta seção poderão vir da rede se não estiverem no cache. Caso contrário, a rede não será usada, mesmo que o usuário esteja on-line. Você pode colocar URLs específicos na lista de permissões aqui ou simplesmente "", que permite todos os URLs. A maioria dos sites precisa de "". FALLBACK:
Uma seção opcional que especifica páginas substitutas se um recurso estiver inacessível. O primeiro URI é o recurso. O segundo será o substituto usado se a solicitação de rede falhar ou se houver erros. Os dois URIs precisam ter a mesma origem que o arquivo de manifesto. É possível capturar URLs específicos, mas também prefixos de URL. "images/large/" captura falhas de URLs como "images/large/whatever/img.jpg".

O manifesto abaixo define uma página "pega-tudo" (off-line.html) que será exibida quando o usuário tentar acessar a raiz do site off-line. Ela também declara que todos os outros recursos (por exemplo, os de um site remoto) exigem uma conexão de Internet.

CACHE MANIFEST
# 2010-06-18:v3

# Explicitly cached entries
index.html
css/style.css

# offline.html will be displayed if the user is offline
FALLBACK:
/ /offline.html

# All other resources (e.g. sites) require the user to be online.
NETWORK:
*

# Additional resources to cache
CACHE:
images/logo1.png
images/logo2.png
images/logo3.png

Como atualizar o cache

Quando um aplicativo fica off-line, ele permanece armazenado em cache até que uma das seguintes situações aconteça:

  1. O usuário limpa o armazenamento de dados do navegador do seu site.
  2. O arquivo de manifesto é modificado. Observação: atualizar um arquivo listado no manifesto não significa que o navegador vai armazenar novamente esse recurso em cache. O arquivo de manifesto precisa ser alterado.

Status do cache

O objeto window.applicationCache é seu acesso programático ao cache de apps do navegador. A propriedade status dela é útil para verificar o estado atual do cache:

var appCache = window.applicationCache;

switch (appCache.status) {
case appCache.UNCACHED: // UNCACHED == 0
return 'UNCACHED';
break;
case appCache.IDLE: // IDLE == 1
return 'IDLE';
break;
case appCache.CHECKING: // CHECKING == 2
return 'CHECKING';
break;
case appCache.DOWNLOADING: // DOWNLOADING == 3
return 'DOWNLOADING';
break;
case appCache.UPDATEREADY:  // UPDATEREADY == 4
return 'UPDATEREADY';
break;
case appCache.OBSOLETE: // OBSOLETE == 5
return 'OBSOLETE';
break;
default:
return 'UKNOWN CACHE STATUS';
break;
};

Para verificar se há atualizações no manifesto de forma programática, primeiro chame applicationCache.update(). Essa ação tentará atualizar o cache do usuário, o que exige que o arquivo de manifesto tenha sido alterado. Por fim, quando o applicationCache.status estiver no estado UPDATEREADY, chamar applicationCache.swapCache() trocará o cache antigo pelo novo.

var appCache = window.applicationCache;

appCache.update(); // Attempt to update the user's cache.

...

if (appCache.status == window.applicationCache.UPDATEREADY) {
appCache.swapCache();  // The fetch was successful, swap in the new cache.
}

A boa notícia é que isso é possível. Para atualizar os usuários para a versão mais recente do site, defina um listener para monitorar o evento updateready no carregamento da página:

// Check if a new cache is available on page load.
window.addEventListener('load', function(e) {

window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
    // Browser downloaded a new app cache.
    if (confirm('A new version of this site is available. Load it?')) {
    window.location.reload();
    }
} else {
    // Manifest didn't changed. Nothing new to server.
}
}, false);

}, false);

Eventos do AppCache

Como esperado, outros eventos são expostos para monitorar o estado do cache. O navegador dispara eventos para itens como progresso de download, atualização do cache do app e condições de erro. O snippet a seguir configura listeners de eventos para cada tipo de evento de cache:

function handleCacheEvent(e) {
//...
}

function handleCacheError(e) {
alert('Error: Cache failed to update!');
};

// Fired after the first cache of the manifest.
appCache.addEventListener('cached', handleCacheEvent, false);

// Checking for an update. Always the first event fired in the sequence.
appCache.addEventListener('checking', handleCacheEvent, false);

// An update was found. The browser is fetching resources.
appCache.addEventListener('downloading', handleCacheEvent, false);

// The manifest returns 404 or 410, the download failed,
// or the manifest changed while the download was in progress.
appCache.addEventListener('error', handleCacheError, false);

// Fired after the first download of the manifest.
appCache.addEventListener('noupdate', handleCacheEvent, false);

// Fired if the manifest file returns a 404 or 410.
// This results in the application cache being deleted.
appCache.addEventListener('obsolete', handleCacheEvent, false);

// Fired for each resource listed in the manifest as it is being fetched.
appCache.addEventListener('progress', handleCacheEvent, false);

// Fired when the manifest resources have been newly redownloaded.
appCache.addEventListener('updateready', handleCacheEvent, false);

Se o download do arquivo de manifesto ou de um recurso especificado nele falhar, toda a atualização vai falhar. O navegador continuará a usar o cache antigo do aplicativo caso ocorra uma falha.

Referências