Desenvolvimento

9 mar, 2017

Colocando Santa Tracker em forma

Publicidade

Artigo de Sam Stern, publicado originalmente pelo Android Developers Blog. A tradução foi feita pela Redação iMasters com autorização.

***

Santa Tracker é uma tradição de feriado no Google. Além de levar alegria sazonal para milhões de usuários ao redor do mundo, é um teste anual para as mais recentes APIs e técnicas de desenvolvimento de aplicativos. É por isso que todo código do aplicativo é lançado no GitHub todos os anos.

Em 2016, a equipe do Santa desafiou-se a introduzir novos conteúdos para o aplicativo, enquanto também o tornou menor e mais eficiente do que nunca. Neste artigo, você pode ler sobre o caminho para um Santa Tracker mais fino e mais rápido.

APK BLOAT

Santa Tracker cresceu ao longo dos anos para incluir os recursos visuais e de áudio para mais de uma dúzia de jogos e cenas interativas. Em 2015, o tamanho da APK Santa Tracker foi de 66,1 MB.

O analisador Android Studio APK é uma ótima ferramenta para investigar o que fez o app de 2015 tão grande.

Primeiro, enquanto o tamanho da APK é 66.1 MB, vemos que o tamanho do download é 59.5MB! A maior parte desse tamanho está na pasta de recursos, mas os recursos e as bibliotecas nativas contribuem com uma parte considerável.

O aplicativo de 2016 contém tudo o que estava no aplicativo de 2015, adicionando quatro jogos completamente novos. No início, nós assumimos que tornar o app menor e adicionar tudo isso seria impossível, mas (alerta spoiler!) aqui estão os resultados finais para 2016:

O tamanho do download para o aplicativo agora é quase 10MB menor, apesar da adição de quatro novos jogos e uma atualização visual. O resto desta seção irá explorar como chegamos lá.

Suporte APK múltiplo no Google Play com APK splits

O aplicativo 2015 de adicionou o jogo “Snowdown” da equipe do Google Fun Propulsion Labs. Esse jogo é escrito em C++, por isso está incluído no Santa Tracker como uma biblioteca nativa. A equipe nos deu bibliotecas compiladas para arquiteturas armv5, armv7 e x86. Cada versão tinha aproximadamente 3,5MB, o que corresponde aos 10,5MB que você vê na entrada lib para a APK 2015.

Uma vez que cada dispositivo está usando apenas uma dessas arquiteturas, dois terços das bibliotecas nativas podem ser removidas para economizar espaço – o tradeoff aqui é que vamos publicar várias APKs. O sistema de compilação gradle do Android tem suporte nativo para a construção de uma APK para cada arquitetura (ABI) com apenas algumas linhas de configuração no arquivo build.gradle do aplicativo:

ext.abiList = ['armeabi', 'armeabi-v7a', 'x86']
android {
    
    // ...
    splits {
        abi {
            // Enable ABI splits
            enable true
            // Include the three architectures that we support for snowdown
            reset()
            include(*abiList)
            // Also build a "universal" APK that will run on any device
            universalApk true
        }
    }
}

Uma vez que os splits estão ativados, cada um deles precisa receber um código de versão exclusivo para que eles possam coexistir na Play Store:

 // Generate unique versionCodes for each APK variant: ZXYYSSSSS
//   Z is the Major version number
//   X is the Minor version number
//   YY is the Patch version number
//   SSSS is information about the split (default to 0000)
// Any new variations get added to the front
import com.android.build.OutputFile;
android.applicationVariants.all { variant ->
    variant.outputs.each { output ->
        // Shift abi over by 8 digits
        def abiFilter = output.getFilter(OutputFile.ABI)
        int abiVersionCode = (abiList.indexOf(abiFilter) + 1)
        // Merge all version codes
        output.versionCodeOverride = variant.mergedFlavor.versionCode + abiVersionCode
    }
}

Na versão mais recente do Santa Tracker, publicamos versões para armv5, armv7 e x86, respectivamente. Com essa mudança no local, 10,5 MB de bibliotecas nativas foram reduzidos para aproximadamente 4 MB por variante sem perder qualquer funcionalidade.

Otimizar imagens

A maioria da APK Santa Tracker é recursos de imagem. Cada jogo tem centenas de imagens e cada imagem vem em vários tamanhos para diferentes densidades de tela. Quase todas essas imagens são PNGs. Portanto, nos últimos anos, rodamos PNGCrush em todos os arquivos e percebemos que nosso trabalho estava feito. Aprendemos em 2016 que houve avanços na compressão de PNG sem perda, e a ferramenta zopfli do Google é atualmente o estado da arte.

Ao executar zopflipng em todos os ativos PNG, reduzimos sem perdas o tamanho da maioria das imagens em 10% e algumas em até 30%. Isso resultou em uma redução de tamanho de quase 5MB em todo o aplicativo sem sacrificar qualquer qualidade. Por exemplo, a imagem abaixo foi reduzida sem perdas de 10KB para apenas 7KB. Não se preocupe em tentar detectar as diferenças, não há nenhuma!

Recursos não utilizados

Ao trabalharem no Santa Tracker, os engenheiros estão constantemente refatorando o aplicativo, adicionando e removendo peças de lógica e interface do usuário de anos anteriores. Embora a revisão e a análise de código ajudem a encontrar códigos não utilizados, recursos não utilizados são muito mais propensos a escorregar por passarem despercebidos. Além disso, não há ProGuard para recursos. Por isso, não podemos ser salvos por nosso toolchain e imagens não utilizadas e outros recursos, muitas vezes, infiltram-se no aplicativo.

O Android Studio pode ajudar a encontrar recursos que não estão sendo usados e, portanto, estão inchando a APK. Ao clicar em Analyze > Run Inspection by Name > Unused Resources, o Android Studio identificará recursos que não são usados por nenhum caminho de código conhecido. É importante primeiro eliminar todo o código não utilizado, pois os recursos que são “usados” pelo código morto não serão detectados como não utilizados.

Depois de alguns ciclos de análise com as ferramentas úteis do Android Studio, conseguimos encontrar dezenas de arquivos não utilizados e eliminar mais alguns MB de recursos do aplicativo.

Uso de memória

Santa Tracker é popular em todo o mundo e tem usuários em milhares de dispositivos Android exclusivos. Muitos desses dispositivos têm alguns poucos anos e têm 512 MB de RAM ou menos. Por isso, historicamente, encontramos OutOfMemoryErrors em nossos jogos.

Enquanto as otimizações acima fizeram nossos PNGs menores no disco, quando carregados em um Bitmap, sua pegada de memória permanece inalterada. Uma vez que cada jogo no Santa Tracker carrega dezenas de imagens, rapidamente entramos em um território de memória perigoso.

Em 2015, seis dos nossos dez principais crashes foram relacionados à memória. Devido às otimizações abaixo (e outras), movemos os crashes de memória para fora dos dez primeiros.

Backoff de carregamento de imagem

Ao inicializar um jogo no Santa Tracker, costumamos carregar todos os Bitmaps necessários para a primeira cena na memória para que o jogo possa ser executado sem problemas. A abordagem ingênua se parece com isto:

private LruCache<Integer, Drawable> mMemoryCache;
private BitmapFactory.Options mOptions;
public void init() {
  // Initialize the cache
  mMemoryCache = new LruCache<Integer, Drawable>(240);
  // Start with no Bitmap sampling
  mOptions = new BitmapFactory.Options();
  mOptions.inSampleSize = 1;
}
public void loadBitmap(@DrawableRes int id) {
    // Load bitmap
    Bitmap bmp = BitmapFactory.decodeResource(getResources(), id, mOptions);
    BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bmp);
    
    // Add to cache
    mMemoryCache.put(id, bitmapDrawable);
}

No entanto, a função decodeResource irá lançar um OutOfMemoryError se não tivermos RAM suficiente para carregar o Bitmap na memória. Para combater isso, pegamos esses erros e, em seguida, tentamos recarregar todas as imagens com uma taxa de amostragem maior (dimensionando por um fator de 2 cada vez):

private static final int MAX_DOWNSAMPLING_ATTEMPTS = 3;
private int mDownsamplingAttempts = 0;
private Bitmap tryLoadBitmap(@DrawableRes int id) throws Exception {
    try {
        return BitmapFactory.decodeResource(getResources(), id, mOptions);
    } catch (OutOfMemoryError oom) {
        if (mDownSamplingAttempts < MAX_DOWNSAMPLING_ATTEMPTS) {
            // Increase our sampling by a factor of 2
            mOptions.inSampleSize *= 2;
            mDownSamplingAttempts++;
        }
    }
    throw new Exception("Failed to load resource ID: " + resourceId);
}

Com essa técnica, dispositivos de baixa memória irão agora ver mais gráficos pixelados, mas, ao fazer esse tradeoff, eliminamos quase completamente os erros de memória do carregamento de Bitmap.

Pixels transparentes

Como mencionado acima, o tamanho de uma imagem no disco não é um bom indicador do quanto de memória ele usará. Um exemplo flagrante são as imagens com grandes regiões transparentes. PNG pode comprimir essas regiões para quase zero tamanho do disco, mas cada pixel transparente ainda exige a mesma RAM.

Por exemplo, no jogo “Dasher Dancer”, as animações foram representadas por uma série de quadros PNG 1280×720. Muitos desses quadros foram dominados pela transparência, como quando o objeto animado deixou a tela. Nós escrevemos um script para cortar todo o espaço transparente e gravar um “offset” para exibir cada quadro para que ele ainda parecesse ser 1280×720 ao todo. Em um teste, isso reduziu o tempo de execução do uso RAM do jogo em 60MB! E agora que não estávamos desperdiçando memória em pixels transparentes, precisávamos de menos downscaling e poderíamos usar imagens de alta resolução em dispositivos com pouca memória.

Explorações adcionais

Além das principais otimizações descritas acima, nós exploramos algumas outras vias para tornar o aplicativo menor e mais rápido com vários graus de sucesso.

Telas de Splash

O aplicativo de 2015 mudou-se para uma estética Material Design, na qual os jogos foram lançados a partir de uma lista central de “cartões”. Percebemos que metade dos jogos faria com que o efeito ‘ripple’ do cartão fosse de qualidade inferior no lançamento, mas não conseguimos encontrar a causa raiz e não conseguimos corrigir o problema.

Quando trabalhamos na versão 2016 do aplicativo, estávamos determinados a corrigir o jogo de qualidade inferior no lançamento. Depois de horas de investigação, percebemos que apenas os jogos fixados para a orientação de paisagem causaram qualidade inferior quando lançados. Os quadros perdidos foram devidos à mudança de orientação forçada. Para criar uma experiência de usuário suave, nós introduzimos splash screens entre o launcher Activity e o jogo Activities. A splash screen detectaria a orientação atual do dispositivo e a orientação necessária para reproduzir o jogo sendo carregado e girando em si mesmo em tempo de execução para corresponder. Isso removeu imediatamente qualquer qualidade inferior notável dos lançamentos do jogo e fez o aplicativo inteiro parecer mais suave.

SVG

Quando originalmente assumimos a tarefa de reduzir o tamanho de nossos recursos, usar imagens SVG parecia ser uma otimização óbvia. As imagens vetoriais são dramaticamente menores e só precisam ser incluídas uma vez para suportar densidades múltiplas. Devido à estética “plana” no Santa Tracker, fomos capazes de converter muitos de nossos PNGs para SVGs minúsculos sem muita perda de qualidade. No entanto, carregar esses SVGs era completamente impraticável em dispositivos mais lentos, onde eles seriam dezenas ou centenas de vezes mais lentos do que um PNG dependendo da complexidade do caminho.

No final, decidimos seguir a recomendação limitando tamanhos de imagem vetorial em 200×200 dp e SVG usando apenas para ícones pequenos no aplicativo ao invés de grandes gráficos ou ativos de jogo.

Conclusões

Quando começamos a construir o Santa Tracker 2016, fomos confrontados com um problema assustador: como podemos tornar o aplicativo menor e mais rápido e, ao mesmo tempo, adicionar conteúdo novo e excitante? As otimizações acima foram descobertas desafiando constantemente uns aos outros para fazer mais com menos e considerando restrições de recursos com cada mudança que fizemos. No final, fomos capazes de, incrementalmente, tornar o aplicativo Santa Tracker tão saudável como ele nunca foi… Nosso próximo trabalho será ajudar o Sr. Santa a trabalhar fora todo esse peso extra cookie.

***

Este artigo é do Android Developers Blog. Ele foi escrito por Sam Stern. A tradução foi feita pela Redação iMasters com autorização. Você pode acessar o original em: https://android-developers.googleblog.com/2017/03/getting-santa-tracker-into-shape.html