
In diesem Artikel werde ich darüber sprechen, wie ich die Probleme gelöst habe, auf die ich im vorherigen Teil während der Implementierung des Projekts gestoßen bin.
Erstens müssen Sie bei der Analyse einer transformierbaren Klasse irgendwie verstehen, ob diese Klasse der Nachfolger von Activity
oder Fragment
, damit wir sicher sagen können, dass die Klasse für unsere Transformation geeignet ist.
Zweitens müssen Sie in der transformierten .class
Datei für alle Felder mit der Annotation @State
den Typ explizit bestimmen, um die entsprechende Methode im Bundle aufzurufen, um den Status zu speichern / wiederherzustellen, und Sie können den Typ genau bestimmen, indem Sie alle @State
der Klasse und die von ihnen implementierten Schnittstellen analysieren.
Sie müssen also nur in der Lage sein, den abstrakten Syntaxbaum der transformierten Dateien zu analysieren.
AST-Analyse
Um die Klasse auf Vererbung von einer Basisklasse zu analysieren (in unserem Fall ist es Activity/Fragment
), reicht es aus, den vollständigen Pfad zur untersuchten .class
Datei zu haben. Darüber hinaus hängt alles von der Implementierung des Transformators ab: Laden Sie die Klasse entweder über ClassLoader
oder analysieren Sie sie über ASM mit ClassReader
und ClassVisitor
, um alle erforderlichen Informationen über die Klasse zu erhalten.
Dateizugriff
Beachten Sie, dass sich die benötigte Klasse außerhalb des Projektbereichs befinden kann, jedoch in einigen Bibliotheken (z. B. Activity
befindet sich im Android SDK). Daher müssen Sie vor dem Start der Umwandlung eine Liste der Pfade zu allen verfügbaren .class
Dateien .class
.
Nehmen Sie dazu kleine Änderungen am Transformator vor :
@Override Set<? super QualifiedContent.Scope> getReferencedScopes() { return ImmutableSet.of( QualifiedContent.Scope.EXTERNAL_LIBRARIES, QualifiedContent.Scope.SUB_PROJECTS ) }
Mit der Methode getReferencedScopes
können Sie auf Dateien aus den angegebenen Bereichen zugreifen. Dies ist einfach ein Lesezugriff ohne die Möglichkeit einer Transformation. Genau das, was wir brauchen. Bei der transform
können diese Dateien auf die gleiche Weise wie in den Hauptbereichen abgerufen werden:
transformInvocation.referencedInputs.each { transformInput -> transformInput.directoryInputs.each { directoryInput ->
Und noch etwas: Dateien aus dem Andoid SDK müssen separat empfangen werden:
project.extensions.findByType(BaseExtension.class).bootClasspath[0].toString()
Danke Google, sehr praktisch.
ClassPool Fill
Das Ausfüllen der Liste aller uns zur Verfügung stehenden .class
Dateien mit Ihren Händen ist ziemlich trostlos: Da wir Verzeichnisse oder jar
Dateien als Eingabe erhalten, müssen Sie alle .class
und die .class
Dateien korrekt .class
. Hier habe ich die zuvor erwähnte Javassist- Bibliothek benutzt. Sie macht alles unter der Haube und hat außerdem eine praktische API für die Arbeit mit den erhaltenen Klassen. Am Ende müssen Sie nur den Pfad zu den Dateien übertragen und den ClassPool
:
ClassPool.getDefault().appendClassPath(" ")
Vor dem Start der Umwandlung wird ClassPool
aus allen möglichen ClassPool
gefüllt:
fillPoolAndroidInputs(classPool) fillPoolReferencedInputs(transformInvocation, classPool) fillPoolInputs(transformInvocation, classPool)
Details im Transformator .
Klassenanalyse
ClassPool
der ClassPool
voll ist, muss die Annotation @Stater
. Entfernen Sie dazu das Häkchen in der visitAnnotation
Methode unseres Besuchers und untersuchen Sie einfach die Oberklasse jeder Klasse auf das Vorhandensein von Activity/Fragment
in der Vererbungshierarchie. Es ist sehr einfach, eine Klasse mit Namen aus der Javassist-Pool-Klasse abzurufen:
CtClass currentClass = ClassPool.getDefault().get(className.replace("/", "."))
Und bereits mit CtClass
können Sie currentClass.superclass
oder currentClass.interfaces
. Durch Vergleich der Oberklasse habe ich eine Aktivitäts- / Fragmentprüfung durchgeführt.
Und schließlich habe ich ungefähr das Gleiche getan, um StateType
loszuwerden und nicht den Typ des Feldes anzugeben, das explizit StateType
. Der Einfachheit halber wurde ein Mapper (mit Tests ) geschrieben, der den aktuellen Deskriptor in den vom Bundle unterstützten Typ analysiert.
Infolgedessen hat sich die Codetransformation nicht geändert, sondern nur der Mechanismus zum Bestimmen des Typs einer Variablen hat sich geändert.
Durch die Kombination von zwei Ansätzen für die Arbeit mit .class
Dateien konnte ich die ursprüngliche Idee des Speicherns von Variablen in einem Bundle mit nur einer Anmerkung umsetzen.
Leistung
Um die Leistung zu testen, habe ich dieses Mal das Plug-In mit einem echten Arbeitsprojekt verbunden, da das Füllen der Poolklasse von der Anzahl der Dateien im Projekt und verschiedenen Bibliotheken abhängt.
./gradlew clean build --scan
dies alles durch ./gradlew clean build --scan
. Die Transformationsaufgabe transformClassesWithStaterTransformForDebug
dauert ungefähr 2,5 s. Ich habe mit einer Activity
mit 50 @State
Feldern gemessen und mit 10 solchen Activity
ändert sich die Geschwindigkeit nicht viel.