
Neste artigo, falarei sobre como resolvi os problemas que encontrei na parte anterior durante a implementação do projeto .
Em primeiro lugar, ao analisar uma classe transformável, você precisa entender de alguma forma se essa classe é a sucessora de Activity ou Fragment , para que possamos dizer com segurança que a classe é adequada para a nossa transformação.
Em segundo lugar, no arquivo .class transformado para todos os campos com a anotação @State , @State precisa determinar explicitamente o tipo para chamar o método correspondente no pacote configurável para salvar / restaurar o estado e pode determinar o tipo exatamente analisando todos os pais da classe e as interfaces que eles implementam.
Portanto, você só precisa analisar a árvore de sintaxe abstrata dos arquivos transformados.
Análise AST
Para analisar a classe quanto à herança de alguma classe base (no nosso caso, é Activity/Fragment ), basta ter o caminho completo para o arquivo .class em estudo. Além disso, tudo depende da implementação do transformador: carregue a classe através do ClassLoader ou analise através do ASM usando ClassReader e ClassVisitor , obtendo todas as informações necessárias sobre a classe.
Acesso a arquivos
Lembre-se de que a classe de que precisamos pode estar localizada fora do escopo do projeto, mas em algumas bibliotecas (por exemplo, Activity está no Android SDK). Portanto, antes de iniciar a transformação, você precisa obter uma lista de caminhos para todos .class arquivos .class disponíveis.
Para fazer isso, faça pequenas alterações no Transformer :
 @Override Set<? super QualifiedContent.Scope> getReferencedScopes() { return ImmutableSet.of( QualifiedContent.Scope.EXTERNAL_LIBRARIES, QualifiedContent.Scope.SUB_PROJECTS ) } 
O método getReferencedScopes permite acessar arquivos dos escopos especificados, e isso será simplesmente acesso de leitura sem a possibilidade de transformação. Apenas o que precisamos. No método de transform , esses arquivos podem ser obtidos da mesma maneira que nos escopos principais:
 transformInvocation.referencedInputs.each { transformInput -> transformInput.directoryInputs.each { directoryInput ->  
E mais uma coisa, os arquivos do Andoid SDK precisam ser recebidos separadamente:
 project.extensions.findByType(BaseExtension.class).bootClasspath[0].toString() 
Obrigado Google, muito conveniente.
Preenchimento de ClassPool
Preencher a lista de todos .class arquivos .class disponíveis para nós com as mãos é bastante sombrio: como obtemos diretórios ou arquivos jar como uma entrada, você precisa contorná-los e obter corretamente exatamente .class arquivos .class . Aqui eu usei a biblioteca javassist mencionada anteriormente. Ela faz tudo sob o capô e tem uma API conveniente para trabalhar com as aulas recebidas. No final, você só precisa transferir o caminho para os arquivos e preencher o ClassPool :
 ClassPool.getDefault().appendClassPath("  ") 
Antes de iniciar a transformação, o ClassPool é preenchido de todas as fontes de arquivos possíveis:
 fillPoolAndroidInputs(classPool) fillPoolReferencedInputs(transformInvocation, classPool) fillPoolInputs(transformInvocation, classPool) 
Detalhes no transformador .
Análise de classe
Agora que o ClassPool cheio, resta eliminar a anotação @Stater . Para fazer isso, remova a verificação no método visitAnnotation do nosso visitante e simplesmente examine a superclasse de cada classe quanto à presença de Activity/Fragment na hierarquia de herança. Obter qualquer classe por nome da classe javassist pool é muito simples:
 CtClass currentClass = ClassPool.getDefault().get(className.replace("/", ".")) 
E já com o CtClass você pode obter currentClass.superclass ou currentClass.interfaces . Através da comparação da superclasse, fiz uma verificação de atividade / fragmento.
E, finalmente, para me livrar do StateType e não especificar o tipo de campo a ser salvo explicitamente, fiz o mesmo. Por conveniência, foi escrito um mapeador (com testes ) que analisa o descritor atual no tipo suportado pelo pacote configurável.
Como resultado, a transformação do código não foi alterada; apenas o mecanismo para determinar o tipo de uma variável foi alterado.
Assim, combinando duas abordagens para trabalhar com arquivos .class , pude implementar a ideia original de salvar variáveis em pacotes configuráveis usando apenas uma anotação.
Desempenho
Dessa vez, para testar o desempenho, conectei o plug-in a um projeto de trabalho real, pois o preenchimento da classe de pool depende do número de arquivos no projeto e de várias bibliotecas.
./gradlew clean build --scan tudo isso através do ./gradlew clean build --scan . A transformClassesWithStaterTransformForDebug leva aproximadamente 2,5 s. Eu medi com uma Activity com 50 campos @State e com 10 dessas Activity , a velocidade não muda muito.