Prático ProGuard regras de exemplos
No meu artigo anterior expliquei por que todos devem usar o ProGuard para seus aplicativos do Android, como ativá-lo e que tipo de erros que você pode encontrar ao fazer isso. Havia muita teoria envolvida, como eu acho que é importante entender os princípios subjacentes, a fim de estar preparado para lidar com quaisquer problemas potenciais.
I also talked in a separate article about the very specific problem of configurating ProGuard for an Instant App build.
nesta parte, eu gostaria de falar sobre os exemplos práticos de regras progressivas em um aplicativo de amostra de tamanho médio: Xadrez por Nick Butcher.
Plaid na verdade acabou por ser um grande assunto para a pesquisa de problemas progressivos, uma vez que contém uma mistura de bibliotecas de terceiros que usam coisas como processamento de anotações e geração de código, reflexão, carregamento de recursos java e código nativo (JNI). Eu extraídos e anotei alguns conselhos práticos que devem aplicar-se a outros aplicativos em geral:
- Dados de classes
- código Java chamado de lado nativo (JNI)
- Abertura de recursos de JAR/APK
- Como vir acima com regras para bibliotecas de terceiros
- depuração e stack traces
- Só a compilação de depuração do tipo
- anotações em tempo de execução, digite introspecção
- Moving everything to the default package
- ProGuard otimizações
- Quando usar @Manter e manter
- Estats Plaid
Dados de classes
public class User {
String name;
int age;
...
}
Provavelmente, cada aplicativo tem algum tipo de classe de dados (também conhecido como DMOs, modelos, etc. dependendo do contexto e onde eles se sentam na arquitetura do seu aplicativo). A coisa sobre os objetos de dados é que normalmente em algum momento eles serão carregados ou salvos (serializados) em algum outro meio, como rede (um pedido HTTP), um banco de dados (através de um ORM), um arquivo JSON no disco ou em uma loja de dados Firebase.muitas das ferramentas que simplificam a serialização e a deserialização destes campos dependem da reflexão. GSON, Retrofit — Firebase-todos inspecionam nomes de campos em classes de dados e os transformam em outra representação (por exemplo: {"name”: "Sue”, "age”: 28}
), seja para transporte ou armazenamento. A mesma coisa acontece quando eles lêem dados em um objeto Java-eles vêem um par de valores de chave "name”:”John”
e tentam aplicá-lo a um objeto Java, Procurando um campo String name
.
Conclusion: We cannot let ProGuard rename or remove any fields on these data classes, as they have to match the serialized format. É uma aposta segura para adicionar um @Keep
anotação de toda a classe, ou uma regra de curinga em todos os seus modelos:
-keep class io.plaidapp.data.api.dribbble.model.** { *; }
Aviso: É possível cometer um erro ao testar se o seu aplicativo é suscetível a este problema. Por exemplo, se você serializar um objeto para JSON e gravá-lo para o disco na versão N do seu aplicativo sem as regras de manutenção adequadas, Os dados gravados podem ser assim:
{"a”: "Sue”, "b”: 28}
. Porque ProGuard renomeado seus campos paraa
eb
, tudo parece funcionar, os dados serão salvos e carregados corretamente.no entanto, quando construir a sua aplicação de novo e lançar a versão n+1 da sua aplicação, a ProGuard poderá decidir mudar o nome dos seus campos para algo diferente, como
c
ed
. Como resultado, os dados gravados anteriormente não serão carregados.Você deve garantir que você tem as regras de manutenção adequadas em primeiro lugar.
código Java chamado de lado nativo (JNI)
Android padrão do ProGuard arquivos (você sempre deve incluir, eles têm algumas regras úteis) já contêm uma regra para os métodos que são implementados no lado nativo (-keepclasseswithmembernames class * { native <methods>; }
). Infelizmente não há nenhum catch – all way para manter o código invocado na direção oposta: de JNI para Java.
com JNI é inteiramente possível construir um objeto JVM ou encontrar e chamar um método em um cabo JVM a partir de código C/C++ e, na verdade, uma das bibliotecas usadas no Plaid faz isso.
conclusão: como ProGuard só pode inspecionar classes Java, ele não vai saber sobre quaisquer usos que acontecem em código nativo. Nós devemos explicitamente manter tais usagens de classes e membros via a @Keep
annotation ou -keep
rule.
-keep, includedescriptorclasses
class in.uncod.android.bypass.Document { *; }
-keep, includedescriptorclasses
class in.uncod.android.bypass.Element { *; }
Abertura de recursos de JAR/APK
Android tem seus próprios recursos e sistema de ativos que normalmente não deve ser um problema para a ProGuard. No entanto, em Java simples, há um outro mecanismo para carregar recursos diretamente de um arquivo JAR e algumas bibliotecas de terceiros podem estar usando-o mesmo quando compilado em aplicativos Android (nesse caso, eles vão tentar carregar a partir do APK).
o problema é que normalmente estas classes irão procurar recursos sob o seu próprio nome de pacote (que se traduz para um caminho de arquivo no JAR ou APK). ProGuard pode renomear nomes de pacotes quando ofuscando, então depois de compilação pode acontecer que a classe e seu arquivo de recursos não estão mais no mesmo pacote na APK final.
para identificar os recursos de carregamento desta forma, poderá procurar chamadas para Class.getResourceAsStream / getResource
e ClassLoader.getResourceAsStream / getResource
no seu código e em quaisquer bibliotecas de terceiros de que dependa.conclusão: devemos manter o nome de qualquer classe que carrega recursos da APK usando este mecanismo.
No Xadrez, há, na verdade, dois — um na OkHttp biblioteca e um no checkbox, radio buttons:
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-keepnames class org.jsoup.nodes.Entities
Como vir acima com regras para bibliotecas de terceiros
Em um mundo ideal, todos os dependência utilizar iria fornecer os seus necessária ProGuard regras da AAR. Às vezes eles se esquecem de fazer isso ou apenas publicar frascos, que não têm uma maneira padronizada de fornecer regras progressivas.
nesse caso, antes de iniciar a depuração do seu aplicativo e chegar a regras, lembre-se de verificar a documentação. Alguns autores de bibliotecas fornecem regras progressivas recomendadas (como Retrofit usado no Plaid) que podem lhe poupar muito tempo e frustração. Infelizmente, muitas bibliotecas não o fazem (como é o caso do Jsoup e do Bypass mencionado neste artigo). Também esteja ciente de que, em alguns casos, a configuração fornecida com a biblioteca só funcionará com otimizações desativadas, então se você estiver ativando-as Você pode estar em território desconhecido.como criar regras quando a biblioteca não as fornece?
I can only give you some pointers:
- Read the build output and logcat!
- Criar avisos irá dizer-lhe que
-dontwarn
regras para adicionar -
ClassNotFoundException
MethodNotFoundException
eFieldNotFoundException
irá dizer-lhe que-keep
regras para adicionar
Você deve ficar muito feliz quando seu aplicativo falha com ProGuard habilitado — você vai ter um lugar para começar sua investigação 🙂
A pior classe de problemas de depuração quando você app funciona, mas, por exemplo, não mostra uma tela ou não carregar dados a partir da rede.
é aí que você precisa considerar alguns dos cenários que eu descrevi neste artigo e sujar as mãos, mesmo mergulhando no código de terceiros e entendendo por que ele pode falhar, como quando ele usa reflexão, introspecção ou JNI.
depuração e stack traces
ProGuard irá, por omissão, remover muitos atributos de código e metadados escondidos que não são necessários para a execução do programa . Alguns desses são realmente úteis para o desenvolvedor, por exemplo, você pode querer manter arquivo de origem de nomes e números de linha para rastreamentos de pilha para tornar mais fácil depuração:
-keepattributes SourceFile, LineNumberTable
Você também deve se lembrar de guardar o ProGuard mapeamentos de ficheiros produzidos quando você cria uma versão e enviá-los para Jogar em de-ofuscado rastreamentos de pilha a partir de qualquer falha experimentado por seus usuários.
Se você estiver indo para anexar um depurador a passo através do código de método em um ProGuarded de compilação do aplicativo, você também deve ter os seguintes atributos de reter algumas informações de depuração sobre variáveis locais (só tem essa linha no seu debug
tipo de compilação):
-keepattributes LocalVariableTable, LocalVariableTypeTable
Só a compilação de depuração do tipo
O padrão de tipos de compilação de são configurados de tal forma que a depuração não executar ProGuard. Isso faz sentido, porque nós queremos iterar e compilar rapidamente ao se desenvolver, mas ainda queremos que a compilação release use o ProGuard para ser o mais pequeno e otimizado possível.
Mas, a fim de testar e depurar qualquer ProGuard problemas, é bom configurar um separado, minified compilação de depuração, como este:
buildTypes {
debugMini {
initWith debug
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
matchingFallbacks =
}
}
Com este tipo de compilação, você vai ser capaz de ligar o depurador, executar testes de INTERFACE do usuário (também em um CI server) ou macaco testar seu aplicativo para possíveis problemas em uma compilação que está tão perto de seu lançamento construir possível.conclusão: Quando você usa ProGuard você deve sempre QA seu lançamento constrói completamente, seja por ter testes de fim a fim ou manualmente passando por todos os ecrãs em seu app para ver se alguma coisa está faltando ou quebrando.
anotações em tempo de execução, digite introspecção
ProGuard irá, por padrão, remover todas as anotações e até mesmo alguma informação do tipo excedente do seu código. Para algumas bibliotecas que não são um problema — aquelas que processam anotações e geram código em tempo de compilação (como Dagger 2 ou Glide e muitas outras) podem não precisar dessas anotações mais tarde quando o programa executar.
Existe outra classe de ferramentas que realmente inspecionam anotações ou olham para informações de tipo de parâmetros e exceções no tempo de execução. Retrofit, por exemplo, faz isso interceptando as chamadas do seu método usando umProxy
objeto, em seguida, olhando para anotações e informações do tipo para decidir o que colocar ou ler a partir do pedido HTTP.
conclusão: às vezes é necessário reter informações de tipo e anotações que são lidas em tempo de execução, ao contrário do tempo de compilação. Você pode verificar a lista de atributos no manual ProGuard.
-keepattributes *Annotation*, Signature, Exception
Se estiver a utilizar o ficheiro de configuração predefinido do Android ProGuard (
getDefaultProguardFile('proguard-android.txt')
), são especificadas para si as duas primeiras opções — anotações e assinatura. Se você não está usando o padrão você tem que se certificar de adicioná-los você mesmo (também não faz mal apenas duplicá-los se você sabe que eles são um requisito para o seu aplicativo).
Moving everything to the default package
The -repackageclasses
option is not added by default in the ProGuard config. Se você já está ofuscando o seu código e tiver corrigido quaisquer problemas com as regras de manutenção adequadas, você pode adicionar esta opção para reduzir ainda mais o tamanho DEX. Ele funciona movendo todas as classes para o pacote padrão (root), essencialmente liberando o espaço ocupado por strings como “com”.exemplo.myapp.someepackage”.
-repackageclasses
ProGuard otimizações
Como eu mencionei antes, ProGuard pode fazer 3 coisas para você:
- livra-o de código não utilizado,
- renomeia identificadores para tornar o código de menores,
- executa todo o programa de otimizações.
da maneira que eu vejo, todos devem tentar configurar a sua compilação para obter 1. e 2. trabalho.
para desbloquear 3. (otimizações adicionais), você tem que usar um arquivo de configuração ProGuard padrão diferente. Altere o proguard-android.txt
parâmetro proguard-android-optimize.txt
o build.gradle
:
release {
minifyEnabled true
proguardFiles
getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
Isso vai fazer a sua versão de compilação mais lento, mas potencialmente irá fazer o seu aplicativo seja executado mais rapidamente e reduzir o tamanho do código, ainda mais, graças a otimizações, como método de inlining, classe de fusão e mais agressivo código de remoção. Esteja preparado no entanto, que ele pode introduzir novos e difíceis de diagnosticar bugs, então use-o com cautela e se algo não está funcionando, certifique-se de desativar certas otimizações ou desativar o uso da configuração otimização completamente.
no caso de xadrez, otimizações progressivas interferiram com a forma como Retrofit usa objetos Proxy sem implementações concretas, e removeram alguns parâmetros do método que eram realmente necessários. Tive de adicionar esta linha à minha configuração.:
-optimizations !method/removal/parameter
você pode encontrar uma lista de possíveis otimizações e como desativá-las no manual ProGuard.
Quando usar @Manter e manter
@Keep
suporte na verdade, é implementado como um monte de -keep
regras no padrão do Android ProGuard arquivo de regras, de modo que eles são essencialmente equivalentes. Especificação -keep
regras é mais flexível, pois oferece curingas, você pode também usar diferentes variantes que fazer coisas ligeiramente diferentes (-keepnames
-keepclasseswithmembers
e muito mais).
sempre que é necessária uma regra simples de “manter esta classe” ou “manter este método”, Contudo, eu prefiro a simplicidade de adicionar um@Keep
anotação na classe ou membro, uma vez que permanece perto do Código, quase como documentação.
Se algum outro desenvolvedor vem depois de mim quer para refatorar o código, eles saberão imediatamente que uma classe/membro marcados com @Keep
requer tratamento especial, sem ter que se lembrar de consultar o ProGuard de configuração e o risco de quebrar algo. Também a maioria dos refactores de código no IDE deve reter o @Keep
anotação com a classe automaticamente.
Estats Plaid
Aqui estão algumas estatísticas do Plaid, que mostram quanto código eu consegui remover usando ProGuard. Em um aplicativo mais complexo com mais dependências e um DEX maior a economia pode ser ainda mais substancial.