Android 支持库迁移到AndroidX

1、背景

Android系统版本在不断更新,从最初的Android 1.0到如今Google和各大手机厂商正在推的Android 10,平均下来每一个年头都有一个大的版本更新。但用户正在用的手机上的Android系统版本每每更新上来有个过程,如当前时点很多App最低支持的Android系统版本仍是4.4。新的Android系统版本更新,确定会带来一些新的系统变化,同时也为开发者带来了新的功能接口或Api能力。既要支持老的系统版本,又要具有新的Api功能,怎么办呢?java

很天然的,Android官方提供了支持库(android.support.*)。支持库的对应版本中,包含了能够支持到的具体的最低版本的,同时具备新的功能提供的具体实现及接口。实现起来其实也还简单,基本原理是将新的系统上的Api抽取出来,放到支持库中,开发者引入支持库后,最终是会打包到Apk包中的。因而,经过引入支持库,既不影响最低支持的版本,又具备了新的Android系统功能。android

从最初的Android Support v4到后来的v7,v13,以及在这其中对支持库的内部拆分,使得开发者能够具体的去引入本身须要的子库。支持库在不断的演变。同时,支持库具体版本的最低支持版本的,也在发生着变化,如v4,从最初含义上的最低支持API 4到后来的九、14等,含义的变化,使得开发者引入时,不得不慎重核实。且支持库为了利用Android系统自身的一些可能的新能力,每一个版本的如v4的支持库内部,又针对不一样的编译版本,划分出了多个支持库细分版本。后来,开发者引入时,须要引入与当前项目编译版本相对应的,且须要知足当前项目最低支持版本的具体支持库构建版本。如常见的写法com.android.support:recyclerview-v7:${supportVersion},其中${supportVersion}须要与项目编译版本保持一致,至少须要在大的版本Api Level上须要保持一致。web

最终致使的一个问题是,开发者每每会很迷惑,引入支持库时,到底应该怎么写。api

天然,官方也意识到了这个问题。繁杂的支持库变动,不只开发者不方便使用,官方维护也不容易。因而,是时候对支持库进行一次统一的梳理了。bash

AndroidX华丽丽的现身app

终于,支持库的官方文档页面上出现了这个Notice。mvvm

Android支持库官方文档地址:
developer.android.com/topic/libra…ide


2、AndroidX

AndroidX,这个命名感受跟javax有点像。对应的包名是androidx.*,以代替原有的支持库android.support.*,对应的,构件名称也是几乎都是以Androidx开头(目前发现除了一个com.google.android.material:material除外,有点奇怪,不知道官方怎么想的)。工具

按照官方文档的说法:测试

AndroidX 是 Android 团队用于在 Jetpack中开发、
测试、打包和发布库以及对其进行版本控制的开源项目。

AndroidX 对原始 Android 支持库进行了重大改进。   
与支持库同样,AndroidX 与 Android 操做系统分开提供,
并与各个 Android 版本向后兼容。
AndroidX 彻底取代了支持库,不只提供同等的功能,
并且提供了新的库。此外,AndroidX 还包括如下功能:

AndroidX 中的全部软件包都使用一致的命名空间,以字符串 androidx 开头。 
支持库软件包已映射到对应的 androidx.* 软件包。  
有关全部旧类到新类以及旧编译工件到新编译工件的完整映射,请参阅软件包重构页面。

与支持库不一样,AndroidX 软件包会单独维护和更新。
androidx 软件包使用严格的语义版本控制,从版本 1.0.0 开始。
您能够单独更新项目中的 AndroidX 库。

全部新支持库的开发工做都将在 AndroidX 库中进行。
这包括维护原始支持库工件和引入新的 Jetpack 组件。
复制代码

对应官方文档:
developer.android.com/jetpack/and…

简单点说就是,对App开发者而言,AndroidX更加友好,由于咱们引入时,只须要关注AndroidX中具体的须要引入的构件版本便可。且大部分具体的构件,具备一致的版本号。开发者使用起来再也不须要关注项目自身的最低支持版本和编译版本了,只须要像引入其余的第三方库同样,v1.0、v2.0、v3.0这种方式引入便可。

如原有的引入写法
com.android.support:recyclerview-v7:28.0.0
变成了
androidx.recyclerview:recyclerview:1.0.0

官方文档也提供了Androidx版本具体的升级日志记录。
developer.android.com/jetpack/and… developer.android.com/jetpack/and…

3、支持库迁移到AndroidX

3.1 迁移AndroidX的必要性

AndroidX对开发者使用更加友好,同时,支持库文档上官方已经明确支持库后续再也不维护。另外,在Android Studio上新建模块时,也发现若是没有迁移到AndroidX,模块建立不了,代表开始有强制性的措施使得开发者必须迁移到AndroidX。

3.2 迁移AndroidX的前置条件

Android Sudio在3.2版本开始,对直接迁移到AndroidX进行了支持。在操做路径Refactor > Migrate to AndroidX下,但使用时会发现可能存在以下提示:

这也说明了,利用官方内置的迁移方式迁移AndroidX以前,工程环境上最好知足以下条件:
1,Android Studio 3.2及以上。当前时点最新版本已是3.5稳定版了。
2,AGP版本3.2.0及以上,对应的Gradle版本4.6及以上。
3,项目编译版本28及以上。

若是当前项目没有知足上述条件,能够先升级对应的配套。

3.3 迁移过程

Android官方提供了具体的迁移指引。具体参见文档:
developer.android.com/jetpack/and…

Just Start!

如下主要记录实际项目中的迁移过程,以及遇到的问题及解决。

Refactor > Migrate to AndroidX操做后,AS会有对应的迁移提醒,提示你去备份项目文件,若有必要能够先备份。但通常而言,AS项目都是基于Git进行管理,直接单独切一个分支进行迁移操做便可,此处备份成zip现实意义不大。

点击 Migrate后,会出现弹窗 Looking for Usages,开始在当前项目中搜索全部可能须要迁移的源文件,包括代码源文件、XML文件、build.gradle配置文件等,最终会列出当前主工程使用到支持库的全部文件列表。

点击Do Refactor确认迁移,AS自动执行迁移AndroidX的替换过程。如将对应的支持库类名、包名、构件名等都替换成相应的AndroidX形式。

一点时间后,主工程替换完成。此时打开gradle.properties,会发现自动添加了以下配置项。

android.useAndroidX=true
android.enableJetifier=true
复制代码

android.useAndroidX=true,表示主工程使用AndroidX形式。
android.enableJetifier=true,表示针对主工程中使用到的三方库,也会自动执行AndroidX的替换过程。

同时,在自动执行三方库的替换时,出下了以下报错信息:

ERROR: Unable to resolve dependency for ':MyCorn@prodDebug/compileClasspath': Failed to transform file 'fingerprint-1.1.1.aar' to match attributes {artifactType=processed-aar} using transform JetifyTransform
复制代码

大体的意思是使用JetifyTransformfingerprint-1.1.1.aar进行替换过程当中,出现了问题。但具体问题没有进一步的提示信息。因而,直接经过命令执行下构建看一下:

./gradlew assembleDevDebug 
....
....

1: Task failed with an exception.
-----------
* What went wrong:
Could not resolve all files for configuration ':MyCorn:devDebugCompileClasspath'.
> Failed to transform file 'fingerprint-1.1.1.aar' to match attributes {artifactType=processed-aar} using transform JetifyTransform
   > Failed to transform '/Users/corn/.gradle/caches/modules-2/files-2.1/com.corn.feature/fingerprint/1.1.1/ae2da4c824fb2923eac7a1340222d50d6308f7ea/fingerprint-1.1.1.aar' using Jetifier. Reason: 8. (Run with --stacktrace for more details.) To disable Jetifier, set android.enableJetifier=false in your gradle.properties file.
复制代码

进而,带上--stacktrace看看。

./gradlew assembleDevDebug --stacktrace
....
....
Caused by: java.lang.ArrayIndexOutOfBoundsException: 8
        at org.objectweb.asm.ClassReader.readFrameType(ClassReader.java:2313)
        at org.objectweb.asm.ClassReader.readFrame(ClassReader.java:2269)
        at org.objectweb.asm.ClassReader.readCode(ClassReader.java:1448)
        at org.objectweb.asm.ClassReader.readMethod(ClassReader.java:1126)
        at org.objectweb.asm.ClassReader.accept(ClassReader.java:698)
        at org.objectweb.asm.ClassReader.accept(ClassReader.java:500)
        at com.android.tools.build.jetifier.processor.transform.bytecode.ByteCodeTransformer.runTransform(ByteCodeTransformer.kt:39)
        at com.android.tools.build.jetifier.processor.Processor.visit(Processor.kt:328)
        at com.android.tools.build.jetifier.processor.archive.ArchiveFile.accept(ArchiveFile.kt:41)
        at com.android.tools.build.jetifier.processor.Processor.visit(Processor.kt:316)
        at com.android.tools.build.jetifier.processor.archive.Archive.accept(Archive.kt:66)
        at com.android.tools.build.jetifier.processor.Processor.visit(Processor.kt:316)
        at com.android.tools.build.jetifier.processor.archive.Archive.accept(Archive.kt:66)
        at com.android.tools.build.jetifier.processor.Processor.transformLibrary(Processor.kt:312)
        at com.android.tools.build.jetifier.processor.Processor.transform(Processor.kt:175)
        at com.android.build.gradle.internal.dependency.JetifyTransform.transform(JetifyTransform.kt:199)
        ... 39 more
复制代码

咱们发现,JetifyTransform内部使用了ASM,在对aar进行ClassReader的过程当中抛出了异常。而且从错误栈信息上看,应该有一类叫jetifier的工具,是在这个工具中调用的ASM操做。

官方文档搜索下,果真发现了jetifier的踪影。
developer.android.com/studio/comm…

一样的,Google Source上也找到了其对应的实现。
android.googlesource.com/platform/fr…

下载对应的jetifier-standalone,解压后,执行命令对fingerprint-1.1.1.aar执行AndroidX转化。

➜  bin ./jetifier-standalone -i  ./fingerprint-1.1.1.aar  -o  11.aar
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 8
    at org.objectweb.asm.ClassReader.readFrameType(ClassReader.java:2313)
    at org.objectweb.asm.ClassReader.readFrame(ClassReader.java:2269)
    at org.objectweb.asm.ClassReader.readCode(ClassReader.java:1448)
    at org.objectweb.asm.ClassReader.readMethod(ClassReader.java:1126)
    at org.objectweb.asm.ClassReader.accept(ClassReader.java:698)
    at org.objectweb.asm.ClassReader.accept(ClassReader.java:500)
    at com.android.tools.build.jetifier.processor.transform.bytecode.ByteCodeTransformer.runTransform(ByteCodeTransformer.kt:40)
    at com.android.tools.build.jetifier.processor.Processor.visit(Processor.kt:539)
    at com.android.tools.build.jetifier.processor.archive.ArchiveFile.accept(ArchiveFile.kt:53)
    at com.android.tools.build.jetifier.processor.Processor.visit(Processor.kt:521)
    at com.android.tools.build.jetifier.processor.archive.Archive.accept(Archive.kt:76)
    at com.android.tools.build.jetifier.processor.Processor.visit(Processor.kt:521)
    at com.android.tools.build.jetifier.processor.archive.Archive.accept(Archive.kt:76)
    at com.android.tools.build.jetifier.processor.Processor.transformLibrary(Processor.kt:517)
    at com.android.tools.build.jetifier.processor.Processor.transform2(Processor.kt:291)
    at com.android.tools.build.jetifier.processor.Processor.transform2$default(Processor.kt:251)
    at com.android.tools.build.jetifier.standalone.Main.run(Main.kt:156)
    at com.android.tools.build.jetifier.standalone.Main$Companion.main(Main.kt:109)
    at com.android.tools.build.jetifier.standalone.Main.main(Main.kt)
复制代码

发现出现了一样的错误信息。

显然,应该是fingerprint-1.1.1.aar中有字节码有问题。经查,fingerprint内部直接以jar方式引入了三星的指纹识别库,已经很比较老的版本了,经业务同窗确认,如今已经能够直接去除。

去除fingerprint内部的三星指纹库后,升级版本,下载对应的aar文件后,再次尝试转化:

./jetifier-standalone -i  ./fingerprint-1.1.3-20190916.092208-1.aar -o  mm.aar
复制代码

执行成功,且有转换后的对应文件生成。

主工程更新fingerprint对应依赖版本后,从新执行构建,出现错误提示:

e: /Users/corn/AndroidStudioProjects/MyCorn/base/src/main/java/com/mycorn/base/mvvm/EventLiveData.kt: (13, 5): 'observe' overrides nothing
e: /Users/corn/AndroidStudioProjects/MyCorn/base/src/main/java/com/mycorn/base/mvvm/EventLiveData.kt: (20, 5): 'removeObserver' overrides nothing
复制代码

缘由在于对应的LiveData接口observe、removeObserver中的形参有所改动,从原来的

@NonNull Observer<T> observer
复制代码

变成了

@NonNull Observer<? super T> observer
复制代码

修正EventLiveData类中的重写方法的对应形参,与接口保持一致便可。

再次从新构建,出现错误信息:

/Users/corn/AndroidStudioProjects/MyCorn/trans/src/main/java/com/mycorn/biz/supertrans/v12/slide/ItemSlideHelper.java:566: 错误: 程序包androidx.appcompat.recyclerview.R不存在
.getDimension(androidx.appcompat.recyclerview.R.dimen.item_touch_helper_swipe_escape_velocity);

/Users/corn/AndroidStudioProjects/MyCorn/trans/src/main/java/com/mycorn/biz/supertrans/v12/slide/ItemSlideHelper.java:568: 错误: 程序包androidx.appcompat.recyclerview.R不存在
.getDimension(androidx.appcompat.recyclerview.R.dimen.item_touch_helper_swipe_escape_max_velocity);

/Users/corn/AndroidStudioProjects/MyCorn/trans/src/main/java/com/mycorn/biz/supertrans/v12/slide/ItemSlideHelper.java:2115: 错误: 程序包androidx.appcompat.recyclerview.R不存在
androidx.appcompat.recyclerview.R.dimen.item_touch_helper_max_drag_scroll_per_frame);
复制代码

核查官方文档,对应的替换关系应该是:

ndroid.support.v7.recyclerview.R
复制代码

替换为

androidx.recyclerview.R
复制代码

此处替换成了

androidx.appcompat.recyclerview.R
复制代码

按照文档对应修正过来。

再次从新构建,能够构建成功。

3.4 核验与发现

此时构建成功,是否是就真的都处理好了呢,是否是都没问题了呢。显然不是的。

首先,分别查看主工程对应的编译时和运行时依赖,看看是否都从android.support.* 替换成了androidx.*

./gradlew -q MyCorn:dependencies --configuration devDebugAndroidTestCompileClasspath  >   ~/compile.txt
复制代码
./gradlew -q MyCorn:dependencies --configuration devDebugAndroidTestRuntimeClasspath  >   ~/runtime.txt
复制代码

仔细对比后发现,虽然依赖中都已经被替换成了androidx.* 。但编译时的依赖中含有大量的rc01。如:

androidx.appcompat:appcompat:1.0.0-rc01
androidx.recyclerview:recyclerview:1.0.0-rc01
...
复制代码

但运行时依赖中却没有rc01

androidx.appcompat:appcompat:1.0.0
androidx.recyclerview:recyclerview:1.0.0
...
复制代码

经核查后发现,主工程中以前依赖支持库时有两种写法,一种是直接写法,如:

implementation 'com.android.support:appcompat-v7:28.0.0 复制代码

另外一种是采起统必定义后,进行的变量形式引入:

api rootProject.ext.dependencies["appcompat-v7"]
复制代码

其中,具体变量,如appcompat-v7被统必定义在了专用的一个dependencies.gradle文件中。
大体形式以下:

// 统一配置管理
def supportVersion = "28.0.0"

project.ext {
    android = [
        "compileSdkVersion": 28,
        "minSdkVersion"    : 19,
        "targetSdkVersion" : 26,
        "javaMaxHeapSize"  : "5G"
    ]

    dependencies = [
        "support-compat"      : "com.android.support:support-compat:${supportVersion}",
        "support-core-utils"  : "com.android.support:support-core-utils:${supportVersion}",
        "support-core-ui"     : "com.android.support:support-core-ui:${supportVersion}",
        "support-media-compat": "com.android.support:support-media-compat:${supportVersion}",
        "support-fragment"    : "com.android.support:support-fragment:${supportVersion}",
        "support-annotations" : "com.android.support:support-annotations:${supportVersion}",
        "appcompat-v7"        : "com.android.support:appcompat-v7:${supportVersion}",
        ....
        ....
    ]
}
复制代码

来到对应的文件,发现对应的android.support.*并无被替换成androidx.*

而在build.gralde中直接引入的写法,是被相应替换了的。已经被正确替换成了:

implementation 'androidx.appcompat:appcompat:1.0.0'
复制代码

也就是说,构建时,遇到api rootProject.ext.dependencies["appcompat-v7"],实际上是没有被准确识别的,按照编译时的依赖关系,最终被识别成了带有rc01的AndroidX版本。

因而,才出现了编译时和运行时大量的版本不一致状况。

解决起来很简单,直接在dependencies.gradle文件中,将android.support.*对应人为替换成androidx.*形式,并从新规范命名和版本,相应修正对应的被使用到的地方,并修正成统一的AndroidX依赖写法。

// 统一配置管理
def androidXVersion = "1.0.0"

project.ext {
    android = [
        "compileSdkVersion": 28,
        "minSdkVersion"    : 19,
        "targetSdkVersion" : 26,
        "javaMaxHeapSize"  : "5G"
    ]

    dependencies = [
        "androidx-core"         : "androidx.core:core:${androidXVersion}",
        "androidx-core-utils"   : "androidx.legacy:legacy-support-core-utils:${androidXVersion}",
        "androidx-core-ui"      : "androidx.legacy:legacy-support-core-ui:${androidXVersion}",
        "androidx-media"        : "androidx.media:media:${androidXVersion}",
        "androidx-fragment"     : "androidx.fragment:fragment:${androidXVersion}",
        "support-annotations"   : "androidx.annotation:annotation:${androidXVersion}",
        "androidx-appcompat"    : "androidx.appcompat:appcompat:${androidXVersion}",
        ....
        ....
    ]
}
复制代码

从新输出编译时和运行时依赖,发现此时支持库版本已经一致。

从新构建项目,发现能够构建成功。

但到此时,咱们依然有四个问题须要进一步确认:
1,主工程中是否有支持库相关的一些特别的写法,结果会跟上面的dependencies.gradle同样,不能被自动识别并迁移?例如反射?字符串?甚至字符串拼接?等等。

2,原有依赖库android.support.*确定会有一些混淆配置,如今迁移成androidx.*后,混淆配置这块如何对应处理?

3,自动迁移时,依赖库与主工程实际上是不同的技术原理。显然,主工程采用的是对源文件的相应替换,而依赖库已是编译后的class等二进制文件,采用的是ASM字节码操做,那是否也还存在一些不能被ASM操做的特殊状况?例如反射?字符串?甚至字符串拼接?等等。

4,对整个工程而言,如何进一步确认总体迁移确实已经成功?

第一个问题其实比较好处理,主工程直接全局搜索如android.support关键字,对搜索结果一一甄别,对应处理便可。最终主要发现的可疑问题有两处:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.mycorn.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>
复制代码

经查,此处的meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"写法在AndroidX中确实是没有发生变化的。

另外一处是项目中有用到了反射的写法:

if (mClearMethod == null) {
    mClearMethod = Class.forName("android.support.v7.widget.RecyclerView$Recycler")
            .getDeclaredMethod("clear");
    mClearMethod.setAccessible(true);
}
复制代码

显然,此处没有被自动替换,须要人为修正:

if (mClearMethod == null) {
    mClearMethod = Class.forName("androidx.recyclerview.widget.RecyclerView$Recycler")
            .getDeclaredMethod("clear");
    mClearMethod.setAccessible(true);
}
复制代码

至此,主工程确认完成。

第二个问题,项目中对支持库在混淆配置中有进行keep,此时须要针对AndroidX增长上对应的配置:

# AndroidX 防止混淆
-keep class com.google.android.material.** {*;}
-keep class androidx.** {*;}
-keep public class * extends androidx.**
-keep interface androidx.** {*;}
-keep @androidx.annotation.Keep class *
-keepclassmembers class * {
    @androidx.annotation.Keep *;
}
复制代码

并生成release的Apk,查看构建时的混淆关系文件,或反编译后进一步确认对应的类是否有被混淆。

第三个问题,其实必定程度上与第四个问题相同。采起的方式能够是针对最终构建的包,采起反编译的方式,查到对应的如android.support关键字,并核实androidx.*等关键类。

此处可使用jadx,将生成的DevDebug变体的Apk中的dex,逐一检视。很快,也发现了两个问题:
1,生成的Apk中依然含有android.support.*开头的包和类文件,但以前确认的运行时依赖关系中,确实已经没有了对应的android.support.*依赖,这是怎么回事呢?

原来,并非全部的类的包名都被替换成了androidx.*形式,如官方文档列出的确实有部分类以依然保留了原有的完整类名。

也就是说,有少部分其实只是改了构件名,但包名是没有改变的,如Media相关的。

2,个别引入的依赖库中确实用到了一些特别的写法,如:

还好这是项目本身的的依赖库,相应修正后,从新发布版本,更新主工程依赖便可。

从新构建,打包测试。

至此,Android支持库迁移到AndroidX完成。


4、结语

AndroidX的出现,表示原有的Android支持库开始退出历史舞台。做为Jetpack套件的一部分,AndroidX具备更易使用,方便维护等特色。但本质上,咱们应该意识到,AndroidX与原有Android支持库功能和职责定位上目前仍是同样的。

在将Android支持库迁移到AndroidX过程当中,事实上涉及到的问题,是比官方文档上寥寥数语是要复杂的。尤为涉及到第三方依赖库和混淆,以及反射等相关的一些特别写法也十分值得留意,每每一不当心,一个深坑可能就留下了。

jadx在对Apk的反编译等方面确实十分强大,只是使用上,比较占内存,有时比较卡。其强大的文本搜索、使用跟踪等功能,在核实迁移到AndroidX等须要反编译Apk的相关用处上十分有用。

end ~

相关文章
相关标签/搜索