一、背景
Android系統版本在不斷更新,從最初的Android 1.0到現在Google和各大手機廠商正在推的Android 10,平均下來每個年頭都有一個大的版本更新。但用戶正在用的手機上的Android系統版本往往更新上來有個過程,如當前時點不少App最低支持的Android系統版本還是4.4。新的Android系統版本更新,肯定會帶來一些新的系統變化,同時也為開發者帶來了新的功能接口或Api能力。既要支持老的系統版本,又要具備新的Api功能,怎么辦呢?
很自然的,Android官方提供了支持庫(android.support.*)。支持庫的對應版本中,包含了可以支持到的具體的最低版本的,同時具有新的功能提供的具體實現及接口。實現起來其實也還簡單,基本原理是將新的系統上的Api抽取出來,放到支持庫中,開發者引入支持庫后,最終是會打包到Apk包中的。於是,通過引入支持庫,既不影響最低支持的版本,又具有了新的Android系統功能。
從最初的Android Support v4到后來的v7,v13,以及在這其中對支持庫的內部拆分,使得開發者可以具體的去引入自己需要的子庫。支持庫在不斷的演變。同時,支持庫具體版本的最低支持版本的,也在發生着變化,如v4,從最初含義上的最低支持API 4到后來的9、14等,含義的變化,使得開發者引入時,不得不慎重核實。且支持庫為了利用Android系統自身的一些可能的新能力,每個版本的如v4的支持庫內部,又針對不同的編譯版本,划分出了多個支持庫細分版本。后來,開發者引入時,需要引入與當前項目編譯版本相對應的,且需要滿足當前項目最低支持版本的具體支持庫構建版本。如常見的寫法com.android.support:recyclerview-v7:${supportVersion}
,其中${supportVersion}
需要與項目編譯版本保持一致,至少需要在大的版本Api Level上需要保持一致。
最終導致的一個問題是,開發者往往會很迷惑,引入支持庫時,到底應該怎么寫。
自然,官方也意識到了這個問題。繁雜的支持庫變更,不僅開發者不方便使用,官方維護也不容易。於是,是時候對支持庫進行一次統一的梳理了。
AndroidX華麗麗的現身。
終於,支持庫的官方文檔頁面上出現了這個Notice。

Android支持庫官方文檔地址:
developer.android.com/topic/libra…
二、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…
三、支持庫遷移到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 復制代碼
大致的意思是使用JetifyTransform
對fingerprint-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-data
中android: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.*
形式,如官方文檔列出的確實有部分類以依然保留了原有的完整類名。


2,個別引入的依賴庫中確實用到了一些特別的寫法,如:

還好這是項目自己的的依賴庫,相應修正后,重新發布版本,更新主工程依賴即可。
重新構建,打包測試。
至此,Android支持庫遷移到AndroidX完成。
四、結語
AndroidX的出現,表示原有的Android支持庫開始退出歷史舞台。作為Jetpack套件的一部分,AndroidX具有更易使用,方便維護等特點。但本質上,我們應該意識到,AndroidX與原有Android支持庫功能和職責定位上目前還是一樣的。
在將Android支持庫遷移到AndroidX過程中,事實上涉及到的問題,是比官方文檔上寥寥數語是要復雜的。尤其涉及到第三方依賴庫和混淆,以及反射等相關的一些特別寫法也十分值得留意,往往一不小心,一個深坑可能就留下了。
jadx在對Apk的反編譯等方面確實十分強大,只是使用上,比較占內存,有時比較卡。其強大的文本搜索、使用跟蹤等功能,在核實遷移到AndroidX等需要反編譯Apk的相關用處上十分有用。
end ~
作者:HappyCorn
鏈接:https://juejin.im/post/5d821d336fb9a06af92bd965
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。