AGP8 的变更应该很多人都知道了,移除了Transform API,所以很多 class 操作类的插件代码都需要改了。TheRouter在开发的时候就支持了AGP8,使用的也是Gradle提供的标准 API。详细可见官方示例仓库:https://github.com/android/gradle-recipes/blob/agp-8.7/transformAllClasses/build-logic/plugins/src/main/kotlin/CustomPlugin.kt
project.plugins.withType(AppPlugin::class.java) { // Queries for the extension set by the Android Application plugin. // This is the second of two entry points into the Android Gradle plugin val androidComponents = project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java) // Registers a callback to be called, when a new variant is configured androidComponents.onVariants { variant -> val taskProvider = project.tasks.register("${variant.name}ModifyClasses")
/** * Transform the current version of the [type] artifact into a new version. The orderin which * the transforms are applied is directly setby the orderof this method call. First come, * first served, last one provides the final version of the artifacts. * * @param type the [ScopedArtifact] to transform. * @param inputJars lambda that returns a [ListProperty] or [RegularFile] that will be used to * set all incoming files for this artifact type. * @param inputDirectories lambda that returns a [ListProperty] or [Directory] that will be used * toset all incoming directories for this artifact type. * @param into lambda that returns the [Property] used by the [Task] to save the transformed * element. The [Property] value will be automatically setby the Android Gradle Plugin and its * location should not be considered part of the API and can change in the future. */ fun toTransform( type: ScopedArtifact, inputJars: (T) -> ListProperty, inputDirectories: (T) -> ListProperty, into: (T) -> RegularFileProperty)
重点在这一句:last one provides the final version of the artifacts。这个方法是整个构建最后一步执行,会提供一个最终的输出,并且输出类型是一个 RegularFileProperty 他只能输出一个单独的 File。这也就意味着你需要把 前期构建的所有产物,包括全部的 jar 依赖、源码编译后的 class 依赖,都在这个Task中聚合到一个产物内返回,这就是耗时的原因。并且由于需要将所有的 jar 和 class 聚合到一个 jar 内,所以也没办法使用增量编译,这就又进一步拖慢了编译速度。
而对应的老版本操作是,拿到所有的 jar 和 class,需要字节码操作的就做操作,不需要的直接复制一遍到输出路径就可以。很显然,新版本使用的方案还不如旧版本,把所有的class聚合成一个新的jar是很耗时的,而且没办法做增量操作。
3解决思路1
既然耗时间的原因是将全部的 class 聚合成一个 jar,这一步太慢。那么我们的想法肯定是怎么避免这样做,同时又能在编译期改变 class 内容。在老版本Transform API的替代类还提供了一个 AsmClassVisitorFactory,是对一个指定class做字节码操作,而我们的TheRouter路由恰好是只需要改TheRouterServiceProvideInjecter这一个类就足够了,那这样就可以大幅降低编译时间了。但是问题是在于, AsmClassVisitorFactory 是对所有类遍历的过程中执行的,而路由的代码需要所有类遍历一遍以后才知道应该要把哪些类放到 TheRouterServiceProvideInjecter中。如果使用AsmClassVisitorFactory,就有可能要记录的类还没有遍历完,就已经轮到要ASM插桩处理的TheRouterServiceProvideInjecter类了。比如这个图中的流程,假设红色箭头是当前构建处理类的顺序,整体的逻辑就是处理A类的时候,记录是否需要插桩,再B,再C。但是到TheRouterServiceProvideInjecter的时候,就要开始读取记录,开始插桩了,实际上如果E、F也是需要插桩的话,此时就会被漏掉。
/** * Set the final version of the [type] artifact to the input fields of the [Task] [T]. * Those input fields should be annotated with [org.gradle.api.tasks.InputFiles] for Gradle to * property set the task dependency. * * @param type the [ScopedArtifact] to obtain the final value of. * @param inputJars lambda that returns a [ListProperty] or [RegularFile] that will be used to * set all incoming files for this artifact type. * @param inputDirectories lambda that returns a [ListProperty] or [Directory] that will be used * to set all incoming directories for this artifact type. */ funtoGet( type: ScopedArtifact, inputJars: (T) -> ListProperty, inputDirectories: (T) -> ListProperty)