如何打开疯狂的android讲义源码_58同城迁移到AndroidX实践及Jetifier源码分析

  • Post author:
  • Post category:其他


本文介绍了58同城迁移到AndroidX实践过程及对Jetifier源码分析。

《春宵》

春宵一刻值千金,花有清香月有阴。

歌管楼台声细细,秋千院落夜沉沉。

-宋代,苏轼

前言

AndroidX是谷歌在2018年IO大会上推出的,是对support库的整理后的产物,用于取代support库,解决使用support库必须保持统一的版本及命名混乱等问题。在2018年9月发布了support库的最后一个版本28.0.0,之后support库将不再维护。AndroidX 1.0.0版本对应于support库28.0.0版本。为了确保迁移过程顺畅,迁移前请先将support库升级28.0.0版本。

开始迁移

使用Android Studio提供的功能Refactor > Migrate to AndroidX进行迁移。首先会弹出一个对话框,提示你备份工程代码,并告知可能需要手动处理一些错误。
d2944f5893f63469b18e2ecade32d2b0.png

点击Migrate后Android Studio会对工程中所有文件进行搜索需要处理的文件列表,点击Do Refactor开始迁移。
3f47b94d7304f370c34ea723c2e59ada.png

这会在gradle.properties文件中添加以下属性:

android.useAndroidX=true //表示启用androidx
android.enableJetifier=true //会对依赖库进行迁移

迁移后效果类似下面的修改:
bf1010bc722e6766dbb8e932edea9f46.png

尝试编译发现有很多错误,有导包错误,搜索发现还有好多文件未能成功迁移:
64b69023834284cd9b2a9645b792cc09.png

自动化迁移

显然Android Studio提供的工具还有很大的缺陷,由于58同城源码工程代码文件庞大且包含多个业务线,如果手动修改这些导包错误花费的时间成本比较大,并且官方提供了support库和androdx库类映射关系,所以这里我们使用python脚本获取映射关系,通过映射关系对工程中所有文件进行扫描迁移。备注:该脚本不支持多行替换和依赖替换。

传送门:https://github.com/yuweiguocn/MigrateToAndroidX

在gradle.properties文件中添加以下属性:

android.useAndroidX=true //表示启用androidx
android.enableJetifier=true //会对依赖库进行迁移

打开终端在工程根目录执行以下命令:

git clone git@github.com:yuweiguocn/MigrateToAndroidX.git
python MigrateToAndroidX/migrate.py

运行结果:
c5a9496f05a26aa3297d67dcbda1e375.png

通过此脚本工具迁移后执行打包只出现了一两个小问题,其中一个是使用的butterknife需要升级。从实践结果来看使用脚本工具对源码迁移成本还是相对很低的,并且目前androidx的多个库已经发布了新的版本,所以还没有迁移到androidx的小伙伴是时候进行迁移了。

验证迁移结果

执行混淆打包后查看混淆后的mapping文件,全局搜索android.support发现有800多处,经过确认是androidx库版本带有的support包名的class文件,分别是:
7e920d7afeea41cd3393fa9e51a491ef.png

833637102a5f523e602a9172b0a7c4a2.png

4eef28ec2caa65a1290d1b903179049f.png

485a4633fdc1d2969fcd1e2a287dcf01.png

5ba787f7184c4a7615307e5d1d3e90ea.png

这样可以确认迁移androidx已经完成,最后来看下support库和androidx库的版本对比,确保迁移后不会对现有功能产生影响。

cde9b6db07c93722a3cd3a3167d379fa.png

除了黑色加粗的依赖有版本升级其余库均没有版本变动,从官方网站查看版本变更记录:

  • arch:

    没有变更说明

  • constraintlayout:

    没有变更说明,小版本升级影响不大

  • lifecycle:

    修复ProGuard规则

  • multidex:

    修复了与 Robolectric 测试的兼容性问题。

    提升了版本检查代码的性能。

  • room:

    问题修复及api变更

  • sqlite:

    没有变更说明

重点测试以上依赖库对应的相应功能即可。

Jetifier源码分析

当我们在gradle.properties文件中添加

android.enableJetifier=true

属性开启Jetifier后执行打包时会自动将依赖库改为新的androidx库,这个是如何做到的?接下来我们对Jetifier的相关源码进行一下分析。备注:基于android插件gradle_3.4.0版本。

Android Gradle插件源码

相关源码可以从这里获取:https://github.com/yuweiguocn/build-system

一个工程中的Module可能包括application和library,这里我们只分析application插件。

apply plugin: 'com.android.application'

从应用的插件名称找到插件配置文件com.android.application.properties,可以看到实现类是AppPlugin。

implementation-class=com.android.build.gradle.AppPlugin

AppPlugin的父类继承自BasePlugin,BasePlugin最终会在插件apply时调用到VariantManager的createAndroidTasks方法,该方法最终会调用到configureDependencies方法,接下来该主角登场了:


VariantManager.java

public void configureDependencies() {
final DependencyHandler dependencies = project.getDependencies();
// 如果开启了Jetifier没有开启androidX会抛出异常
if (!globalScope.getProjectOptions().get(BooleanOption.USE_ANDROID_X)
&& globalScope.getProjectOptions().get(BooleanOption.ENABLE_JETIFIER)) {
throw new IllegalStateException(
"AndroidX must be enabled when Jetifier is enabled. To resolve, set "
+ BooleanOption.USE_ANDROID_X.getPropertyName()
+ "=true in your gradle.properties file.");
}

// 如果开启了Jetifier会使用AndroidX替换support
if (globalScope.getProjectOptions().get(BooleanOption.ENABLE_JETIFIER)) {
AndroidXDepedencySubstitution.replaceOldSupportLibraries(project);
}

final String jetifierBlackList =
Strings.nullToEmpty(
globalScope.getProjectOptions().get(StringOption.JETIFIER_BLACKLIST));
dependencies.registerTransform(
transform -> {
transform.getFrom().attribute(ARTIFACT_FORMAT, AAR.getType());
transform.getTo().attribute(ARTIFACT_FORMAT, TYPE_PROCESSED_AAR);
if (globalScope.getProjectOptions().get(BooleanOption.ENABLE_JETIFIER)) {
transform.artifactTransform(
JetifyTransform.class, config -> config.params(jetifierBlackList));
} else {
transform.artifactTransform(IdentityTransform.class);
}
});
dependencies.registerTransform(
transform -> {
transform.getFrom().attribute(ARTIFACT_FORMAT, JAR.getType());
transform.getTo().attribute(ARTIFACT_FORMAT, PROCESSED_JAR.getType());
if (globalScope.getProjectOptions().get(BooleanOption.ENABLE_JETIFIER)) {
transform.artifactTransform(
JetifyTransform.class, config -> config.params(jetifierBlackList));
} else {
transform.artifactTransform(IdentityTransform.class);
}
});
...
}

该方法中判断如果开启了Jetifier会对工程中依赖进行处理,然后注册了JetifyTransform用于处理aar和jar文件。接下来看下对工程中的依赖的处理:


AndroidXDepedencySubstitution.kt

object AndroidXDepedencySubstitution {

/**
* 老的依赖到AndroidX依赖的映射
* map中的key是"old-group:old-module" (不包含版本) value是
* "new-group:new-module:new-version" (包含版本).
*/
@JvmStatic
val androidXMappings: Map = Processor.createProcessor3(
config = ConfigParser.loadDefaultConfig()!!,
dataBindingVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION
).getDependenciesMap(filterOutBaseLibrary = false)
@JvmStatic
fun replaceOldSupportLibraries(project: Project) {
project.dependencies.components.all { component ->
component.allVariants { variant ->
variant.withDependencies { metadata ->
val oldDeps = mutableSetOf()
val newDeps = mutableListOf()
metadata.forEach { it ->
val newDep = if (bypassDependencySubstitution(it)) {
null
} else {
androidXMappings["${it.group}:${it.name}"]
}
if (newDep != null) {
oldDeps.add(it)
newDeps.add(newDep)
}
}
// 某些情况下 metadata.removeAll(oldDeps) 不起作用,所以使用循环处理
for (oldDep in oldDeps.map { it -> "${it.group}:${it.name}" }) {
metadata.removeIf { it -> "${it.group}:${it.name}" == oldDep }
}
for (newDep in newDeps) {
metadata.add(newDep)
}
}
}
}
project.configurations.all { config ->
// 只处理可解决的配置
if (config.isCanBeResolved) {
config.resolutionStrategy.dependencySubstitution.all { it ->
maybeSubstituteDependency(it, config, androidXMappings)
}
}
}
}
/**
* 如果依赖是老的support库则使用新的androidx替换
*/
private fun maybeSubstituteDependency(
dependencySubstitution: DependencySubstitution,
configuration: Configuration,
androidXMappings: Map
) {
// 只处理 Gradle module 依赖 (group:module:version这种形式的)
if (dependencySubstitution.requested !is ModuleComponentSelector) {
return
}
val requestedDependency = dependencySubstitution.requested as ModuleComponentSelector
if (bypassDependencySubstitution(requestedDependency, configuration)) {
return
}
androidXMappings[requestedDependency.group + ":" + requestedDependency.module]?.let {
dependencySubstitution.useTarget(
it,
BooleanOption.ENABLE_JETIFIER.name + " is enabled"
)
}
}
...
}

AndroidXDepedencySubstitution文件中有一个androidXMappings变量存储的是support依赖和AndroidX依赖之间的映射,key是”old-group:old-module” (不包含版本) ,value是”new-group:new-module:new-version” (包含版本),通过该映射关系对现有工程依赖进行替换。这里用到了Processor类,该类在jetifier工程中,稍后再分析该依赖配置是怎么获取的。

接下来看下刚才注册的JetifyTransform类:


JetifyTransform.kt

class JetifyTransform @Inject constructor(blackListOption: String) : ArtifactTransform() {

companion object {

private val jetifierProcessor: Processor by lazy {
Processor.createProcessor3(
config = ConfigParser.loadDefaultConfig()!!,
dataBindingVersion = Version.ANDROID_GRADLE_PLUGIN_VERSION,
allowAmbiguousPackages = false,
stripSignatures = true
)
}
}

private val jetifierBlackList: List = getJetifierBlackList(blackListOption)
private fun getJetifierBlackList(blackListOption: String): List {
val blackList = mutableListOf()
if (!blackListOption.isEmpty()) {
blackList.addAll(Splitter.on(",").trimResults().splitToList(blackListOption))
}
// Jetifier should not jetify itself (http://issuetracker.google.com/119135578)
blackList.add("jetifier-.*\\.jar")
return blackList.map { Regex(it) }
}
override fun transform(aarOrJarFile: File): List {
Preconditions.checkArgument(
aarOrJarFile.name.toLowerCase().endsWith(".aar")
|| aarOrJarFile.name.toLowerCase().endsWith(".jar")
)
/*
* aars 或 jars 可以分为四类
* - AndroidX 库
* - 老的 support 库
* - 黑名单中的其他库
* - 非黑名单中的其他库
* 下面会相应处理这些情况
*/
// 情况 1: 是AndroidX library不需要处理
if (jetifierProcessor.isNewDependencyFile(aarOrJarFile)) {
return listOf(aarOrJarFile)
}
// 情况 2:如果是老的support库表示在之前的依赖替换阶段没有被替换,可能它还没有androidx版本
// 也不需要对它处理
if (jetifierProcessor.isOldDependencyFile(aarOrJarFile)) {
return listOf(aarOrJarFile)
}
// 情况 3: 如果在黑名单也不需要处理
if (jetifierBlackList.any { it.containsMatchIn(aarOrJarFile.absolutePath) }) {
return listOf(aarOrJarFile)
}
// 情况 4: 对剩下的库进行处理
val outputFile = File(outputDirectory, "jetified-" + aarOrJarFile.name)
val maybeTransformedFile = try {
jetifierProcessor.transform(
setOf(FileMapping(aarOrJarFile, outputFile)), false
)
.single()
} catch (exception: Exception) {
throw RuntimeException(
"Failed to transform '$aarOrJarFile' using Jetifier." +
" Reason: ${exception.message}. (Run with --stacktrace for more details.)",
exception
)
}
...
return listOf(maybeTransformedFile)
}
}

这里主要看下transform方法,aar或jar可以分为四类:1.AndroidX库 2.老的support库,表示在之前的依赖替换阶段没有被替换 3.黑名单中的其他库 4.非黑名单中的其他库。只对情况4进行处理,最终还是调用了jetifier库中Processor类。备注:我们可以通过在gradle.properties文件中添加

android.jetifier.blacklist

属性指定不需要处理的黑名单。

Jetifier源码

jetifier库源码地址:https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-jetifier-release/jetifier/

创建Processor对象是config的参数值是ConfigParser.loadDefaultConfig()表示加载默认的配置,最终是通过读取default.generated.config文件中的json数据转换为Config对象。


ConfigParser.kt

object ConfigParser {

private const val TAG: String = "Config"

private val gson = GsonBuilder().setPrettyPrinting().create()
...
fun parseFromString(inputText: String): Config? {
return gson.fromJson(inputText, Config.JsonData::class.java).toConfig()
}
...
fun loadDefaultConfig(): Config? {
Log.v(TAG, "Using the default config '%s'", Config.DEFAULT_CONFIG_RES_PATH)

// Use getResource().openStream() instead of getResourceAsStream() as the latter can result
// in concurrency issues (see http://issuetracker.google.com/137929327 for details).
val inputStream = javaClass.getResource(Config.DEFAULT_CONFIG_RES_PATH).openStream()
inputStream.reader().use {
return parseFromString(it.readText())
}
}
...
}

default.generated.config文件中存放了support库和androidx库之间的映射关系,通过注释可以看出该文件是由default.config配置文件和preprocessor/scripts/processDefaultConfig.sh脚本生成的,这里不再对该shell脚本进行分析,有兴趣的同学可以自己看下。
cccc16cfa81c4627a6b0c7250c9a18f5.png

27268879b6eb53027135f4433915d0a8.png

a21b24312244db1f9ef545e32eeffe07.png

回过头来继续分析Processor的处理,上面注册的JetifyTransform最终调用了Processor的transfrom方法,transform方法已经被废弃,内部调用了transform2方法,这里主要看下transform2方法:


Processor.kt

fun transform2(
input: Set,
copyUnmodifiedLibsAlso: Boolean = true,
skipLibsWithAndroidXReferences: Boolean = false
): TransformationResult {
val nonSingleFiles = HashSet(input)
for (fileMapping in nonSingleFiles) {
// 将所有文件视为单个文件并检查是否可转换
val file = ArchiveFile(fileMapping.from.toPath(), fileMapping.from.readBytes())
file.setIsSingleFile(true)
val transformer = transformers.firstOrNull { it.canTransform(file) }
if (transformer != null) {
// 单个文件java和xml是可转换的,设置相对路径为输出路径
file.updateRelativePath(fileMapping.to.toPath())
transformer.runTransform(file)
nonSingleFiles.remove(fileMapping)
}
}
if (nonSingleFiles.isEmpty()) {
// 所有文件都是单个文件,处理完成
return TransformationResult(librariesMap = emptyMap(), numberOfLibsModified = 0)
}
val inputLibraries = nonSingleFiles.map { it.from }.toSet()
if (inputLibraries.size != input.size) {
throw IllegalArgumentException("Input files are duplicated!")
}
// 1) 解压并加载所有库文件
val allLibraries = loadLibraries(input)
// 2) 过滤出包含AndroidX 引用的库
val librariesToProcess =
if (skipLibsWithAndroidXReferences) {
filterOutLibrariesWithAndroidX(allLibraries)
} else {
allLibraries
}
// 3) 搜索 POM 文件
val pomFiles = scanPomFiles(librariesToProcess)
// 4) 转换所有 libraries
librariesToProcess.forEach { transformLibrary(it) }
if (context.errorsTotal() > 0) {
if (context.isInReversedMode && context.rewritingSupportLib) {
throw IllegalArgumentException("There were ${context.errorsTotal()} errors found " +
"during the de-jetification. You have probably added new androidx types " +
"into support library and dejetifier doesn't know where to move them. " +
"Please update default.config and regenerate default.generated.config via " +
"jetifier/jetifier/preprocessor/scripts/processDefaultConfig.sh")
}
throw IllegalArgumentException("There were ${context.errorsTotal()}" +
" errors found during the remapping. Check the logs for more details.")
}
// 5) 转换 POM 文件
transformPomFiles(pomFiles)
// 6) 找到签名文件如果需要则抛出异常
runSignatureDetectionFor(librariesToProcess)
val numberOfLibsModified = librariesToProcess.count { it.wasChanged }
// 7) 重新打包到存档文件
var result = allLibraries
.map {
if (it.wasChanged || copyUnmodifiedLibsAlso) {
it.relativePath.toFile() to it.writeSelf()
} else {
it.relativePath.toFile() to null
}
}.toMap()
return TransformationResult(
librariesMap = result,
numberOfLibsModified = numberOfLibsModified)
}

先来分析下主要流程:

0)首先对传入的单个文件包含java和xml做了转换处理,如果列表为空则表示处理完成

1)解压并加载所有库文件

2)根据方法参数值过滤掉包含androidx引用的库,默认不过滤

3)扫描加载所有pom文件

4)转换所有库文件

4.1)判断转换如果发生错误则抛出异常

5)转换所有pom文件

6)找出签名文件如果需要则抛出异常

7)重新打包到存档文件

8)最后返回处理结果

然后来看一下具体的转换细节,其中transformers是一个list包含四个类,分别用于处理字节码class非单个文件、xml非pom文件、proguard非单个文件、单个java源码文件,上面流程首先将传入的所有文件视为单个文件使用JavaTransformer和XmlResourcesTransformer对java源码文件和xml文件进行了处理。第4步使用transformers对所有库文件进行了处理,第5步对所有pom文件进行了处理。


Processor.kt

private fun createTransformers(context: TransformationContext) = listOf(
ByteCodeTransformer(context),// class && !single
XmlResourcesTransformer(context),// xml && !pom
ProGuardTransformer(context), // proguard && !single
JavaTransformer(context) // java && single
)

不同类型的转换处理相关代码原理一样,都是通过映射关系使用正则匹配等方法进行替换。这里只看下字节码的处理,字节码的处理类ByteCodeTransformer使用了ASM工具对class文件进行了处理并将结果保存在ArchiveFile对象中,在上述流程第7步重新打包到了存档文件中,完成了对第三方库文件的处理。


ByteCodeTransformer.kt

class ByteCodeTransformer internal constructor(
private val context: TransformationContext
) : Transformer {
...
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进行了自定义:


CustomRemapper.kt

class CustomRemapper(private val remapper: CoreRemapper) : Remapper() {

override fun map(typeName: String): String {
return remapper.rewriteType(JavaType(typeName)).fullName
}

override fun mapPackageName(name: String): String {
return remapper.rewriteType(JavaType(name)).fullName
}

override fun mapValue(value: Any?): Any? {
val stringVal = value as? String
if (stringVal == null) {
return super.mapValue(value)
}

fun mapPoolReferenceType(typeDeclaration: String): String {
if (!typeDeclaration.contains(".")) {
return remapper.rewriteType(JavaType(typeDeclaration)).fullName
}

if (typeDeclaration.contains("/")) {
// Mixed "." and "/" - not something we know how to handle
return typeDeclaration
}

val toRewrite = typeDeclaration.replace(".", "/")
return remapper.rewriteType(JavaType(toRewrite)).toDotNotation()
}

if (stringVal.startsWith("L") && stringVal.endsWith(";")) {
// L denotes a type declaration. For some reason there are references in the constant
// pool that ASM skips.
val typeDeclaration = stringVal.substring(1, stringVal.length - 1)
if (typeDeclaration.isEmpty()) {
return value
}

if (typeDeclaration.contains(";L")) {
// We have array of constants
return "L" +
typeDeclaration
.split(";L")
.joinToString(";L") { mapPoolReferenceType(it) } +
";"
}

return "L" + mapPoolReferenceType(typeDeclaration) + ";"
}
return remapper.rewriteString(stringVal)
}
}

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


CoreRemapperImpl.kt

class CoreRemapperImpl(
private val context: TransformationContext,
visitor: ClassVisitor
) : CoreRemapper {
...

private val typesMap = context.config.typesMap

var changesDone = false
private set

val classRemapper = ClassRemapper(visitor, CustomRemapper(this))

override fun rewriteType(type: JavaType): JavaType {
val result = context.typeRewriter.rewriteType(type)
if (result != null) {
changesDone = changesDone || result != type
return result
}

context.reportNoMappingFoundFailure(TAG, type)
return type
}

override fun rewriteString(value: String): String {

...

// Try rewrite rules
if (context.useFallbackIfTypeIsMissing) {
val rewrittenType = context.config.rulesMap.rewriteType(type)
if (rewrittenType != null) {
Log.i(TAG, "Map string: '%s' -> '%s' via fallback", value, rewrittenType)
return if (hasDotSeparators) {
rewrittenType.toDotNotation()
} else {
rewrittenType.fullName
}
}
}

// We do not treat string content mismatches as errors
Log.i(TAG, "Found string '%s' but failed to rewrite", value)
return value
}

fun rewritePath(path: Path): Path {
val owner = path.toFile().path.replace('\\', '/').removeSuffix(".class")
val type = JavaType(owner)

val result = context.typeRewriter.rewriteType(type)
if (result == null) {
context.reportNoMappingFoundFailure("PathRewrite", type)
return path
}

if (result != type) {
changesDone = true
return path.fileSystem.getPath(result.fullName + ".class")
}

return path
}
}

方法内部根据条件判断最终调用了映射关系数据Config类获取对应的映射关系完成了转换。

总结

使用本文提供的脚本工具对工程源码迁移到anroidx成本相对较低,迁移到androidx后并非需要对所有依赖库进行升级到对应androidx版本,原因是当我们在gradle.properties文件中添加

android.enableJetifier=true

属性开启Jetifier后执行打包时会自动将依赖的support库修改为新的androidx库,对于第三方库会对aar中class文件、xml文件以及proguard文件和pom依赖进行处理。

参考

  • https://developer.android.com/jetpack/androidx/migrate#migrate

  • https://developer.android.com/jetpack/androidx/migrate/class-mappings

  • https://github.com/yuweiguocn/MigrateToAndroidX

  • https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-jetifier-release/jetifier/

  • https://github.com/yuweiguocn/build-system

16bf791b3a68c1ab4b99532082d6a89c.png

听说点在看年终奖翻倍
c12e60d608bd244e0160e7c3f9d23802.png
c12e60d608bd244e0160e7c3f9d23802.png
c12e60d608bd244e0160e7c3f9d23802.png
?



版权声明:本文为weixin_39619481原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。