
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.