Android学习笔记04-Jetifier分析和应用

Posted by panthole on 2021-05-05

一、为什么会有Jetifier

早年Android使用的support lib 已不再维护,官方推荐的的是Jectpack,也就是AndroidX libraries,但是两种基础库不兼容,主要是google团队对包名和命名空间进行了重构,也是下定了决心长痛不如短痛,但是对开发者来说,这种升级方式要命了,基础库包名变了,不光是自己的代码,所有用到的二方库和三方库都得升级,也太难了吧。
这么一搞,google很难推进,所以google搞了个巧妙的办法帮你解决依赖库的升级,用工具帮你自动完成,这个就是Jetifier。

Jetifier把依赖库中的 android.support 替换成了等价的androidx 版本.
举个栗子
转换前:

1
import android.support.v7.widget.AppCompatImageView

转换后:

1
import androidx.appcompat.widget.AppCompatImageView

再理解下这个单词含义:
个人是这么理解的,Jetpack是目标,从support 到 Jetpack过程叫做Jetify过程,那么完成这个的工具就叫Jetifier

该转换发生在编译时,会增加一些编译时间

Jetifier, which converts all support package of dependency at build time.

对于开发者只需要开启开关(pull the trigger)

在gradle.properties文件中

1
android.enableJetifier=true

二、原理分析

  1. 既然要转换就要有一个对应关系表,google已经帮我们提供了映射关系文档,还有csv文件可以下载
    https://developer.android.com/jetpack/androidx/migrate/class-mappings

  2. 那么是怎么从只需要配置一个开关一步一步到转换完成的呢?
    关于 android.enableJetifier=true 这行代码背后的魔法需要查看Android Gradle插件源码

  3. Transformer api。 gradle 源码中调用了transform api, 在编译过程中, 但是在生成dex文件之前,可以自定义插件代码来操作class文件

Transform API

Starting with 1.5.0-beta1, the Gradle plugin includes a Transform API allowing 3rd party plugins to manipulate compiled class files before they are converted to dex files.

(The API existed in 1.4.0-beta2 but it’s been completely revamped in 1.5.0-beta1)

The goal of this API is to simplify injecting custom class manipulations without having to deal with tasks, and to offer more flexibility on what is manipulated. The internal code processing (jacoco, progard, multi-dex) have all moved to this new mechanism already in 1.5.0-beta1.

Note: this applies only to the javac/dx code path. Jack does not use this API at the moment.

  1. 真正转换时对不同文件类型,分别定义了不同的Transformer 类,比如字节码的ByteCodeTransformer,资源文件的XmlResourceTransformer,一般的文件通过字符串正则匹配来完成转化,字节码操纵比较特殊,使用高性能的ASM框架进行转换

img

  1. ASM

    对于需要手动操纵字节码的需求,可以使用ASM,它可以直接生产 .class字节码文件,也可以在类被加载入JVM之前动态修改类行为(如下图17所示)。ASM的应用场景有AOP(Cglib就是基于ASM)、热部署、修改其他jar包中的类等。

    ASM的API设计称作“访问者模式”:在实际操作的过程中,我们会使用ClassReader来读入原有的字节码,ASM库会对字节码进行扫描,并针对其中的每一个字节码指令调用其API中对应的函数。我们可以通过覆盖这些函数,将这些函数调用替换为我们想要的指令所对应的函数,而我们覆盖之后的函数访问流程会通过ClassWriter转换为对应的字节码,并写入到目标文件里。

    简单来说,访问者模式主要用于修改或操作一些数据结构比较稳定的数据,我们知道字节码文件的结构是由JVM固定的,所以很适合利用访问者模式对字节码文件进行修改。

图17 ASM修改字节码

  1. ByteCodeTransformer.kt
    AOSP源码中的ByteCodeTransformer 负责对字节码进行操纵和转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* The [Transformer] responsible for java byte code refactoring.
*/
class ByteCodeTransformer internal constructor(
private val context: TransformationContext
) : Transformer {

// Does not yet support single bytecode file transformation, file has to be within archive.
override fun canTransform(file: ArchiveFile) = file.isClassFile() && !file.isSingleFile

override fun runTransform(file: ArchiveFile) {
val reader = ClassReader(file.data)
val writer = ClassWriter(0 /* flags */)

val remapper = CoreRemapperImpl(context, writer)
reader.accept(remapper.classRemapper, 0 /* flags */)

if (!remapper.changesDone) {
file.setNewDataSilently(writer.toByteArray())
} else {
file.setNewData(writer.toByteArray())
}

file.updateRelativePath(remapper.rewritePath(file.relativePath))
}
}

这里使用了ClassRemapper,ClassRemapper是一个使用Remapper重新映射类型的ClassVisitor,这里对Remapper进行了自定义

org.objectweb.asm.commons

Class ClassRemapper

  • java.lang.Object
    • org.objectweb.asm.ClassVisitor
      • org.objectweb.asm.commons.ClassRemapper

1
2
public class ClassRemapper
extends ClassVisitor

A ClassVisitor for type remapping.
https://www.javadoc.io/doc/org.ow2.asm/asm/5.2/org/objectweb/asm/commons/ClassRemapper.html

CustomRemapper重写了Remapper的3个方法,分别是map方法用于映射类的内部名称和新名称,mapPackageName方法用于映射包名和新名称,mapValue方法用于映射值。方法内部最终调用了CoreRemapper接口中的rewriteType和rewriteString方法,CoreRemapper的实现类是CoreRemapperImpl

CoreRemapperImpl最终根据可配置的config map对应的映射关系完成转换,这个config map就是第一步中google官方映射关系文档的实例。

三、思考和应用

知道来龙去脉和原理,最后看看怎么实际应用

  1. 升级到jetpack

最简单就项目升级到jetpack,开启Jetifier选项,可以获得jetpack的各种新特性支持,官方更新也很快,还有Jetpack Compose(develop android native ui in reactive way)等

为什么要升级:

  • support library 已经不再维护

  • jetpack采用更好更灵活的包管理机制,版本更新可以很快,有助于提供新特性和修复问题

  • jetpack经过几年的沉淀已经稳定,且是android官方强烈推荐

  • 很多基础库都开始迁移到jectpack,如果一直不迁移,以后可能无法使用新特性的三方基础库

  • jectpack有越来越多好用的feature, 对架构优化和开发效率提升有很多优化

room,livedata, lifecyle, compose, page等等

img

  1. Jetifier 可以支持工程级别开启,或者不开启而使用standalone工具去转换依赖用到的aar,或者jar包,带来了更多的灵活性,可以根据自己的项目做调整

A standalone tool that migrates a library’s dependencies on the deprecated support library to equivalent AndroidX dependencies.

https://developer.android.com/jetpack/androidx/releases/jetifier

  1. 如果老项目暂时没有计划升级的,但是又想使用已迁移到jetpack基础库的新特性怎么办?
    由于知道内部原理,是不是只要把映射关系表翻过来执行下再执行jetifier过程,不就可以了吗,映射表文件是支持定制的,那么原理上是肯定没问题的。
    实际上google官方已经在最新的standalone 工具支持reverse模块,方便暂时不升级的项目也能使用基础库的新特性!

Reverse mode

If you pass the -r flag, the utility runs in reverse mode. In this mode, the utility converts AndroidX APIs to the support library equivalents, instead of the other way around. Reverse mode is useful, for example, if you are developing libraries that use AndroidX APIs, but also need to distribute versions that use the support library.

Example

The following example runs the utility in reverse mode on the library myAndroidXLib.aar (in the current directory) and writes the output to supportLibVersion.aar in the same directory:

1
./jetifier-standalone -r -i myAndroidXLib.aar -o supportLibVersion.aar
  1. 当然,不用官方提供的映射表,我们也可以自行提供map表,可以用于指定class 类型的转换,类型场景就是像jetpack这种包名重构升级场景。