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華麗麗的現身。markdown
終於,支持庫的官方文檔頁面上出現了這個Notice。app
Android支持庫官方文檔地址:
developer.android.com/topic/libra…mvvm
AndroidX,這個命名感受跟javax有點像。對應的包名是androidx.*
,以代替原有的支持庫android.support.*
,對應的,構件名稱也是幾乎都是以Androidx開頭(目前發現除了一個com.google.android.material:material
除外,有點奇怪,不知道官方怎麼想的)。ide
按照官方文檔的說法:工具
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對開發者使用更加友好,同時,支持庫文檔上官方已經明確支持庫後續再也不維護。另外,在Android Studio上新建模塊時,也發現若是沒有遷移到AndroidX,模塊建立不了,代表開始有強制性的措施使得開發者必須遷移到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及以上。
若是當前項目沒有知足上述條件,能夠先升級對應的配套。
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
複製代碼
按照文檔對應修正過來。
再次從新構建,能夠構建成功。
此時構建成功,是否是就真的都處理好了呢,是否是都沒問題了呢。顯然不是的。
首先,分別查看主工程對應的編譯時和運行時依賴,看看是否都從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 ~