刚学会Transform,你告诉我就要被移除了

背景

我们工程中使用的AGP是4.0.2版本,在去年升级的,目前最新版本是AGP7.2.1,已经落后官方5个大版本了
gradle升级是必然的,只是时候未到而已。同时在去年也进行了AGP7.0升级调研,由于种种原因,按下了暂停键。但是这不妨碍我们与时俱进,去学习新的知识。

今天给大家介绍我们未来会使用到的一个新特性,先看下面这张图

从 AGP1.5 一直存在的Transform API在AGP7.0被标记为废弃了,AGP8.0会直接移除掉,连AGP中最稳定的Transform API都被废弃了,所以技术的更新迭代,没有什么是不可能的,只有变化才能适用发展
根据Google的计划AGP8.0在今年年中就会正式发布

什么是Transform

已经2022年了,作为一名Android工程师,我相信大家都知道Transform是什么,如果有人不知道,也没关系,因为Transform已经快过时了,按照惯例,这里我还是要介绍一下Transform

AGP包含了一个Transform API, 它允许第三方插件在编译后的class文件转换为dex文件之前做处理操作. 而使用Transform API, 我们完全可以不用去关注相关task的生成与执行流程, 我们可以只聚焦在如何对输入的class文件进行处理

  • 工作时机 :Transform工作在Android构建中由Class → Dex 的节点
  • 处理对象: 处理对象包括Javac编译后的Class文件、Java标准 resource 资源、本地依赖和远程依赖的 JAR/AAR
  • Transform Task: 每个Transform都对应一个Task,Transform 的输入和输出可以理解为对应 Transform Task 的输入输出
  • Transform 链: TaskManager 会将每个TransformTask 串联起来,前一个Transform的输出会作为下一个 Transform 的输入

Transform使用场景

  • 对编译class文件做自定义的处理
  • 读取编译产生的class文件,做一些其他事情,但是不需要修改它。

Transform替代API

官方解释
Transform API被移除的原因:为了提高构建性能,使用Transform API很难与其它Gradle特性结合使用

Transform API 没有单一的替代 API,每个case都会有新的针对性 API。所有替代 API 都位于 androidComponents {} 代码块中,在 AGP 7.2 中均有提供。

Transform替代API示例

先贴一段代码,这段代码就是后面要讲到的内容

友情提示:后文代码较多,代码密集恐惧症患者可以直接看注释和文字

class ExamplePlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)

        androidComponents.onVariants { variant ->
            //基于不同的变种增加对应的适配工作
            println("variant.name--${variant.name}")
            //最终会在transformClassesWithAsm这个task里面执行
            variant.instrumentation.transformClassesWith(
                ExampleClassVisitorFactory::class.java,
                InstrumentationScope.ALL//扫描范围
            ) {
                it.writeToStdout.set(true)
            }
            variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES)
        }
    }

    interface ExampleParams : InstrumentationParameters {
        @get:Input
        val writeToStdout: Property<Boolean>
    }
    //AsmClassVisitorFactory根据官方说法,编译速度会有提升,大概18%左右
    abstract class ExampleClassVisitorFactory : AsmClassVisitorFactory<ExampleParams> {
        
        override fun createClassVisitor(
            classContext: ClassContext,
            nextClassVisitor: ClassVisitor
        ): ClassVisitor { //ClassNode和ClassVisitor
            return if (parameters.get().writeToStdout.get()) {
                TraceClassVisitor(nextClassVisitor, PrintWriter(System.out))
            } else {
                TraceClassVisitor(nextClassVisitor, PrintWriter(File("trace_out")))
            }
        }

        //判断当前类是否要进行扫描,ClassData则包含了类的一些信息
        override fun isInstrumentable(classData: ClassData): Boolean {
            return classData.className.startsWith("com.wuba.instrument")
        }
    }
}

网上有人说Transform Action是替代方案,但是从官方的demo看来,官方推荐的应该是AsmClassVisitorFactory

AsmClassVisitorFactory

A factory to create class visitor objects to instrument classes.

The implementation of this interface must be an abstract class where the parameters and instrumentationContext are left unimplemented. The class must have an empty constructor which will be used to construct the factory object.

AsmClassVisitorFactory根据官方说法,编译速度会提升大概18%左右,同时可以减少约5倍代码

interface AsmClassVisitorFactory<ParametersT : InstrumentationParameters> : Serializable {

    
    @get:Nested
    val parameters: Property<ParametersT>

  
    @get:Nested
    val instrumentationContext: InstrumentationContext

    //我们要做的就是在这个方法中返回一个ClassVisitor
    fun createClassVisitor(
        classContext: ClassContext,
        nextClassVisitor: ClassVisitor
    ): ClassVisitor

    //判断当前类是否要进行扫描,可以通过这个方法过滤掉很多我们不需要扫描的类
    fun isInstrumentable(classData: ClassData): Boolean
interface ClassData {
    /**
     * Fully qualified name of the class.
     */
    val className: String

    /**
     * List of the annotations the class has.
     */
    val classAnnotations: List<String>

    /**
     * List of all the interfaces that this class or a superclass of this class implements.
     */
    val interfaces: List<String>

    /**
     * List of all the super classes that this class or a super class of this class extends.
     */
    val superClasses: List<String>

ClassData并不是ASM的API,所以其中包含的内容相对来说比较少,但是应该也勉强够用,成员变量都比较容易理解,这里就不过多解释了

新的Extension

AGP版本升级7.0之后,应该是为了区分新旧版的Extension,所以在AppExtension的基础上,新增了一个AndroidComponentsExtension出来。
我们的transformClassesWith方法就需要注册在这个上面。这个需要考虑到变种(variant),和之前的Transform还是有比较大的区别的,这样我们就可以基于不同的变种增加对应的适配工作了。查看源码发现AsmClassVisitorFactory
最终是运行在TransformClassesWithAsmTask里面

TransformClassesWithAsmTask

他有一个即视感非常强烈的前缀,并且其中的成员和Transform中的成员也比较相似

//维护了一个ClassVistorFactory的列表
abstract val visitorsList: ListProperty<AsmClassVisitorFactory<*>>

//下面两个field标记输入(有.class文件和.jar文件),Transform中对应的就是transformInvocation.inputs
abstract val inputClassesDir: ConfigurableFileCollection

abstract val inputJarsDir: DirectoryProperty

// 这个field用于确定输出目录
abstract val inputJarsWithIdentity: JarsClasspathInputsWithIdentity

// 输出,Transform中对应的是transformInvocation.outputProvider
abstract val classesOutputDir: DirectoryProperty

abstract val jarsOutputDir: DirectoryProperty

inputClassesDir和inputJarsDir的赋值也和TransformManager添加Stream时指定的FileCollection相同

task.inputClassesDir.from(
    creationConfig.artifacts.getAll(MultipleArtifact.ALL_CLASSES_DIRS)
)

task.inputJarsWithIdentity.inputJars.from(
    creationConfig.artifacts.getAll(MultipleArtifact.ALL_CLASSES_JARS)
)

由此可见旧版的Transform和新增的TransformClassesWithAsmTask处理的源文件基本是一样的
不同的点就在于transform这个流程。

Transform使用Stream的方式是每次被消耗后,都会通过在build/intermediates/transforms文件夹下生成对应的输出目录,然后下一个Transform再使用之前的输出目录作为输入,多次IO叠加在一起,编译耗时呈线性增长

TransformClassesWithAsmTask,相当于强制使用了ASM进行字节码的处理,使用visitorsList这个field维护了一个ClassVistorFactory的列表,进行transform流程时,只需要依次应用对应的ClassVisitor即可,不需要再为每一次transform准备一个IO的输出。

AsmClassVisitorFactory优点

  • 不用写复杂的增量构建TransformClassesWithAsmTask继承自NewIncrementalTask,已经处理了增量逻辑,不需要我们再手动处理
  • 构建性能大幅提升

在AsmInstrumentationManager中做统一处理,只进行一次IO操作,可以有效地减少IO操作,这是新版本API构建性能提升的主要原因

ASM相比于AspectJ和Javassist有明显的性能优势,强制使用ASM,这是构建性能提升的次要原因(占比较小)

AsmClassVisitorFactory缺点

  • 没有之前Transform API的灵活性,它和ASM字节码工具是绑定的,不支持其他字节码修改工具。
  • 对于一些复杂的修改,可能需要使用Gradle Task来进行实现
  • ASM学习成本更高

新API是否稳定

AsmClassVisitorFactory拥有更好的性能,更容易适配Gradle的新特性,目前AGP 7.2.1版本已经达到稳定阶段,可以作为生产环境使用,AGP8.0会正式移除Transform ,有图为证

Google官方给的demo里面

AGP内部Transform变化

网上很多人提到 “自定义Transform的执行时机早于系统内置Transform”,但从AGP 7.2.1源码看,并不存在系统 Transform。猜测是新版本AGP将系统内置Transform修改为Task直接实现,毕竟从AGP7.0开始Transform标记为过时了

为了验证这个猜测,我对比了AGP3.5.3、4.0.2和7.2.1版本的代码,下面我们一起来看下AGP系统内置Transform的发展变化

ApplicationTaskManager类 Transform=>Task代码变迁

  • AGP 3.5.3 先添加自定义Transform,再添加系统Transform
public void createPostCompilationTasks(
        @NonNull final VariantScope variantScope) {
    TransformManager transformManager = variantScope.getTransformManager();
    // ----- External Transforms -----  自定义Transform添加
    // apply all the external transforms.
    List<Transform> customTransforms = extension.getTransforms();
    List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();

    for (int i = 0, count = customTransforms.size(); i < count; i++) {
        Transform transform = customTransforms.get(i);

        List<Object> deps = customTransformsDependencies.get(i);
        transformManager.addTransform(...)
    }

    // 系统Tranform
    if (variantData.getType().isFeatureSplit() || variantScope.consumesFeatureJars()) {
        createMergeClassesTransform(variantScope);
    }
    // ----- Android studio profiling transforms 系统Tranform
    if (appliesCustomClassTransforms(variantScope, projectOptions)) {
        for (String jar : getAdvancedProfilingTransforms(projectOptions)) {
            if (jar != null) {
                transformManager.addTransform(...);
            }
        }
    }
    // ----- Minify next -----  系统Tranform
    CodeShrinker shrinker = maybeCreateJavaCodeShrinkerTransform(variantScope);
    if (shrinker == CodeShrinker.R8) {
        maybeCreateResourcesShrinkerTransform(variantScope);
        maybeCreateDexSplitterTransform(variantScope);
        return;
    }
    //系统Tranform
    maybeCreateResourcesShrinkerTransform(variantScope);
    //系统Tranform
    maybeCreateDexSplitterTransform(variantScope);
}

AGP 4.0.2 部分系统Transform替换为Task实现

public void createPostCompilationTasks(
        @NonNull final VariantScope variantScope) {

    TransformManager transformManager = variantScope.getTransformManager();

    // ----- External Transforms ----- 自定义Tranform
    // apply all the external transforms.
    List<Transform> customTransforms = extension.getTransforms();
    List<List<Object>> customTransformsDependencies = extension.getTransformsDependencies();

    boolean registeredExternalTransform = false;
    for (int i = 0, count = customTransforms.size(); i < count; i++) {
        Transform transform = customTransforms.get(i);

        List<Object> deps = customTransformsDependencies.get(i);
        registeredExternalTransform |=
                transformManager.addTransform(...)        
    }

    // ----- Android studio profiling transforms 系统Tranform
    if (appliesCustomClassTransforms(variantScope, projectOptions)) {
        for (String jar : getAdvancedProfilingTransforms(projectOptions)) {
            if (jar != null) {
                transformManager.addTransform(...);
            }
        }
    }

    // ----- Minify next -----  系统实现方式从Tranform变为Task
    maybeCreateCheckDuplicateClassesTask(variantScope);
    CodeShrinker shrinker = maybeCreateJavaCodeShrinkerTask(variantScope);
    if (shrinker == CodeShrinker.R8) {
        maybeCreateResourcesShrinkerTasks(variantScope);
        maybeCreateDexDesugarLibTask(variantScope, false);
        return;
    }
    //系统实现方式从Tranform变为Task
    maybeCreateResourcesShrinkerTasks(variantScope);
    //系统实现方式从Tranform变为Task
    maybeCreateDexSplitterTask(variantScope);
}
  • AGP7.2.1 有意思的是,Google的开发者把Transforms的变量名都修改了,从customTransforms改为LegacyTransforms。除了遗留的Transform,其它的系统Transform全部替换为Task实现,可以大胆预测一下,在AGP8.0中添加遗留Transforms的这段代码会被删除掉
fun createPostCompilationTasks(creationConfig: ApkCreationConfig) {
    Preconditions.checkNotNull(creationConfig.taskContainer.javacTask)
    val transformManager = creationConfig.transformManager
    taskFactory.register(MergeGeneratedProguardFilesCreationAction(creationConfig))

    // ----- External Transforms ----- 遗留的transform,大胆预测一下,AGP8.0会把这段代码删掉
    val registeredLegacyTransform = addExternalLegacyTransforms(transformManager, creationConfig)

    // New gradle-transform jacoco instrumentation support.
    if (isTestCoverageEnabled && jacocoTransformEnabled) {
        if (registeredLegacyTransform) {
            createJacocoTaskWithLegacyTransformSupport(creationConfig)
        } else {
            createJacocoTask(creationConfig)
        }
    }
    //上面提到的TransformClassesWithAsmTask
    maybeCreateTransformClassesWithAsmTask(
        creationConfig as ComponentImpl,
        isTestCoverageEnabled
    )

    // Produce better error messages when we have duplicated classes.
    maybeCreateCheckDuplicateClassesTask(creationConfig)

    // Resource Shrinking
    maybeCreateResourcesShrinkerTasks(creationConfig)

    // Code Shrinking
    // Since the shrinker (R8) also dexes the class files, if we have minifedEnabled we stop
    // the flow and don't set-up dexing.
    maybeCreateJavaCodeShrinkerTask(creationConfig)
    if (creationConfig.minifiedEnabled) {
        maybeCreateDesugarLibTask(creationConfig, false)
        return
    }
    .....
}

新的Variant API

build 类型 (buildTypes) 和产品变种 (productFlavors) 都是项目的 build.gradle 文件中的概念。Android Gradle 插件会根据这些定义生成不同的变体对象,并对应各自的构建任务(task)。这些构建任务的输出会被注册为与任务对应的工件 (artifact),并且根据需要被分为公有工件和私有工件。

早期版本的 AGP API 允许访问这些构建任务,但是这些 API 并不稳健,因为每个任务的具体实现细节是会发生改变的。Android Gradle 插件在 7.0 版本中引入了新的 API,可以访问到这些变体对象和一些中间工件。
新的 Variant 接口、Extension 接口公开的 API 比之前少了,但更加规范

Artifacts API

主要负责将我们的Task,插入到编译流程内,让我们尽量少的关注到输入和输出,Artifact APIs 规范了一套标准操作,使得我们可以简易地和已有的数据、中间产物进行交互

// buildSrc/src/main/kotlin/ToyPlugin.kt,官方的一个示例
abstract class ToyPlugin: Plugin<Project> {
  override fun apply(project: Project) {
    val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
 
    androidComponents.onVariants { variant ->
      val taskProvider = 
        project.tasks.register(variant.name + "AddAsset", AddAssetTask::class.java) {
          it.content.set("foo")
        }
 
      // 核心部分
      variant.artifacts
        .use(taskProvider)
        .wireWith(AddAssetTask::outputDir)
        .toAppendTo(MultipleArtifact.ASSETS)
    }
  }
}

对于AGP7.x Variant部分,恕我太菜,还没有进行比较深入的研究,大家有兴趣可以自行研究一下

业界插件架构

滴滴Booster和字节ByteX实现机制都是基于一个BaseTransform,但是Booster和ByteX这两个流行的开源框架,其实都是对Transform有所隔离的,
这么抽象的好处就是当发生了这种AGP的API过期,适配调整的时候,就可以避免所有写了Transform的插件一起调整了。只需要底层进行对应的适配工作,上层不需要做过多修改。
现在我们项目中使用的内部插件,基本上还是各自为营,后续升级AGP肯定会有一次痛苦的经历
另外吐槽一下AGP适配,Booster适配AGP升级非常快,已经适配了7.2版本,反观Qigsaw和ByteX毫无动静,还停留在4.x阶段原地踏步

总结

  • Transform API在AGP7.0已标记为废弃,并且将在AGP8.0(2022年中会正式发布)中移除,我们是时候了解一下如何迁移Transform API了,尤其是经常开发公共插件的同学。
  • AsmClassVisitorFactory相比Transform API,使用起来更加简单,不需要手动处理增量逻辑,可以专注于字节码插桩操作。通过减少IO的方式,编译速度可以提升18%左右。
  • Transform的替换肯定会成为后续升级AGP的一个坎,可能类似当初开启R8编译一样,升上去了编译速度就会得到质的飞越,升不上去就只能止步不前,当然了这一切离我们还很遥远,毕竟我们现在用的还是AGP4.0.2

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

作者:掀乱书页的风
链接:https://juejin.cn/post/7114863832954044446

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值