Android 2中的代码转换。AST分析



在本文中,我将讨论如何解决在项目实施过程中上一部分遇到的问题。


首先,在分析可转换类时,您需要以某种方式了解该类是Activity还是Fragment的后继者,以便我们可以自信地说该类适用于我们的转换。


其次,在具有@State批注的所有字段的转换后的.class文件中, @State需要显式确定类型以便调用捆绑软件上的相应方法来保存/恢复状态,并且可以通过分析该类的所有父级及其实现的接口来确切地确定类型。


因此,您只需要能够分析转换后文件的抽象语法树。


AST分析


为了分析该类的某个基类的继承(在我们的例子中是Activity/Fragment ),具有正在研究的.class文件的完整路径就足够了。 此外,这完全取决于转换器的实现:通过ClassLoader ,或使用ClassReaderClassVisitor通过ASM分析,以获得有关该类的所有必要信息。


档案存取


请记住,我们所需的类可以位于项目范围之外,但可以位于某些库中(例如, Activity在Android SDK中)。 因此,在开始转换之前,您需要获取所有可用.class文件的路径列表。


为此,请对Transformer进行一些小的更改:


 @Override Set<? super QualifiedContent.Scope> getReferencedScopes() { return ImmutableSet.of( QualifiedContent.Scope.EXTERNAL_LIBRARIES, QualifiedContent.Scope.SUB_PROJECTS ) } 

getReferencedScopes方法允许您从指定的范围访问文件,这将被简单地读取访问而没有转换的可能性。 正是我们所需要的。 在transform方法中,这些文件的获取方式与从主要作用域中获得的方式几乎相同:


 transformInvocation.referencedInputs.each { transformInput -> transformInput.directoryInputs.each { directoryInput -> // .  directoryInput.file.absolutePath } transformInput.jarInputs.each { jarInput -> // .  jarInput.file.absolutePath } } 

还有一件事情,来自Andoid SDK的文件需要单独接收:


 project.extensions.findByType(BaseExtension.class).bootClasspath[0].toString() 

谢谢谷歌,非常方便。


类池填充


用您的双手填写所有可用的.class文件的列表相当乏味:由于我们将目录或jar文件作为输入,因此您需要.class所有目录或jar文件并正确获取.class文件。 在这里,我使用了前面提到的javassist库。 她在后台进行所有操作,并且具有方便的api用于处理收到的课程。 最后,您只需要将路径转移到文件并填写ClassPool


 ClassPool.getDefault().appendClassPath("  ") 

在开始转换之前,从所有可能的文件源中填充ClassPool


 fillPoolAndroidInputs(classPool) fillPoolReferencedInputs(transformInvocation, classPool) fillPoolInputs(transformInvocation, classPool) 

变压器中的细节。


类分析


现在ClassPool已满,仍然可以摆脱@Stater批注。 为此,请删除访问者的visitAnnotation方法中的检查,然后简单地检查每个类的超类,以了解继承层次结构中是否存在Activity/Fragment 。 从javassist池类中按名称获取任何类非常简单:


 CtClass currentClass = ClassPool.getDefault().get(className.replace("/", ".")) 

并且已经有了CtClass您可以获得currentClass.superclasscurrentClass.interfaces 。 通过比较超类,我进行了活动/片段检查。


最后,为了摆脱StateType而不指定要显式保存的字段的类型,我做了同样的事情。 为了方便起见,编写了一个映射器 (带有tests ),该映射器将当前描述符解析为捆绑软件支持的类型。


结果,代码转换没有改变;仅用于确定变量类型的机制已经改变。


因此,结合使用两种方法来处理.class文件,我能够实现仅使用一个批注将变量保存在束中的原始想法。


性能表现


这次,为了测试性能,我将插件连接到一个实际的工作项目中,因为pool类的填充取决于项目中文件的数量和各种库。
通过./gradlew clean build --scan检查所有这些。 转换transformClassesWithStaterTransformForDebug大约需要2.5 s。 我用一个带有50个@State字段的Activity和10个此类Activity进行了测量,速度变化不大。

Source: https://habr.com/ru/post/zh-CN470209/


All Articles