- 在之前的文章Android高手筆記-包體積優化中提到過通過編譯優化包體積,涉及到了ProGuard,D8,R8,其中關于ProGuard及包體積優化方案已經進行了詳細介紹,那么今天我們來說說D8和R8;
D8
- D8是一款用于取代 DX、更快的 Dex 編譯器,可以生成更小的 APK;
開啟D8的好處
- 編譯更快、時間更短
- 編譯時占用內存更小
- .dex文件更小
- .dex 文件擁有更好的運行時性能
- 支持在代碼中使用 Java 8 語言
開啟與關閉
- Android Studio 3.0 需要主動在gradle.properties文件中新增:android.enableD8=true
- D8作為DX的一個替代方案,Android Studio 3.1版本開始,將D8作為默認的Dex編譯器。
- 想關閉D8 ,可以在gradle.properties里添加如下配置:
android.enableD8=false //關閉D8恢復到DX-
android.enableD8.desugaring=false //恢復到以前的行為,讓脫糖發生在Java編譯之后,.class字節碼仍遵循Java 7格式
執行增量構建
- 為了在開發過程中提高構建速度(例如提高持續集成 build 的速度),可以指示 d8 僅編譯項目的部分 Java 字節碼;
- 例如,如果啟用了按類 dexing 處理,則只需重新編譯自上次構建以來修改過的類(d8 無法自動檢測哪些字節碼文件已被修改過,因此您需要手動指定類列表):
//執行幾個類的增量構建,并啟用按類 dexing 處理,并為增量構建指定輸出目錄
d8 MainActivity.class R.class --intermediate --file-per-class --output ~/build/intermediate/dex
- 可以使用 --main-dex-list 指定想讓 d8 編譯到主 DEX 文件中的類
d8 ~/build/intermediate/dex --release --main-dex-list ~/build/classes.txt --output ~/build/release/dex
支持Java8
- 通過一個叫做“脫糖”的編譯過程,將這些實用的語言功能轉換為可以在 Android 平臺上運行的字節碼,D8脫糖就不會在transforms目錄下生成desugar目錄。
- Android Studio 和 Android Gradle 插件包含了 d8 啟用脫糖所需的類路徑資源。
- 從命令行使用 d8 時,需要手動添加一些資源:
- --lib:標記目標 Android SDK 中的 android.jar路徑
- --classpath:標記項目的部分已編譯的 Java 字節碼,目前不打算將這部分字節碼編譯為 DEX 字節碼,但在將其他類編譯為 DEX 字節碼時需要用到這些字節碼。例如,如果代碼使用默認和靜態接口方法(一種 Java 8 語言功能),則需要使用此標記來指定您項目的所有 Java 字節碼的路徑,即使您不打算將所有 Java 字節碼都編譯為 DEX 字節碼也是如此。這是因為 d8 需要根據這些信息來理解您項目的代碼并解析對接口方法的調用
d8 MainActivity.class --intermediate --file-per-class --output ~/build/intermediate/dex
--lib android_sdk/platforms/api-level/android.jar
--classpath ~/build/javac/debug
Java8新特性:接口默認方法和靜態方法
- JDK1.8以前,接口(interface)沒有提供任何具體的實現;
- JDK1.8開始,接口允許定義默認方法和靜態方法
R8
- R8之前采用D8+ProGuard的形式構建,R8則將ProGuard和D8工具進行整合,目的是加速構建時間和減少輸出apk的大小;
開啟R8的好處
- 代碼縮減(搖樹優化):使用靜態代碼分析來查找和刪除無法訪問的代碼和未實例化的類型,對規避 64k 引用限制非常有用;
- 資源縮減:移除不使用的資源,包括應用庫依賴項中不使用的資源。
- 混淆代碼:縮短類和成員的名稱,從而減小 DEX 文件的大小
- 優化代碼:檢查并重寫代碼,選擇性內聯,移除未使用的參數和類合并來優化代碼大小
- 減少調試信息 : 規范化調試信息并壓縮行號信息。
- R8 會自動執行上述編譯時任務,也可以停用某些任務或通過 ProGuard 規則文件自定義 R8 的行為。
- 使用某個第三方庫時,通常只使用其中很小一部分。若不壓縮,所有庫代碼都會保留在應用中。冗長的代碼有時可以提高可讀性和可維護性: 例如,使用有意義的變量名和建造者模式 來幫助其他人更容易檢查和理解代碼;但是這些模式會加大代碼量,通常我們自己編寫的代碼有很大的壓縮空間。
開啟與關閉
- Android Studio 3.3 需在項目的 gradle.properties 里加上:android.enableR8=true
- Android Studio 3.4 或 Android Gradle 插件 3.4.0 及更高版本時,R8 是默認編譯器(不再使用 ProGuard 執行編譯時代碼優化),用于將項目的 Java 字節碼轉換為在 Android 平臺上運行的 DEX 格式。
- 不過創建新項目時,縮減、混淆處理和代碼優化功能默認處于停用狀態。因為這些編譯時優化功能會增加項目的構建時間,而且如果沒有充分自定義要保留的代碼,還可能會引入錯誤。
- 開啟代碼縮減,需要在應用的主 build.gradle 文件中將 minifyEnable 屬性設置為 true
- 開啟資源縮減:需要在應用的主 build.gradle 文件中將 shrinkResources 屬性設置為 true
- 資源縮減只有在與代碼縮減配合使用時才能發揮作用。在代碼縮減器移除所有不使用的代碼后,資源縮減器便可確定應用仍要使用的資源,當添加包含資源的代碼庫時尤其如此。必須移除不使用的庫代碼,使庫資源變為未引用資源,因而可由資源縮減器移除。
- 創建新項目或模塊時,IDE 會創建一個 <module-dir>/proguard-rules.pro 文件,以便您添加自己的規則。
android {
...
buildTypes {
release {
shrinkResources true //啟用 R8 的資源縮減功能
minifyEnabled true //啟用 R8 的代碼縮減功能
proguardFiles
//1. Android Gradle 插件會生成 proguard-android-optimize.txt(其中包含了對大多數 Android 項目都有用的規則),并啟用 @Keep* 注解。
getDefaultProguardFile('proguard-android-optimize.txt'),
//2. 使用 Android Studio 創建新模塊時,Android Studio 會在該模塊的根目錄中創建 proguard-rules.pro 文件
'proguard-rules.pro'
//3. AAR 庫:<library-dir>/proguard.txt, JAR 庫:<library-dir>/META-INF/proguard/
//由于 ProGuard 規則是累加的,因此 AAR 庫依賴項包含的某些規則無法移除,并且可能會影響對應用其他部分的編譯。
//例如,如果某個庫包含停用代碼優化功能的規則,該規則會針對整個項目停用優化功能。
//4. Android 資源打包工具 2 (AAPT2):
//使用 minifyEnabled true 構建項目后,AAPT2 會根據對應用清單中的類、布局及其他應用資源的引用,生成保留規則。
//文件路徑為:<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt
//5. 自定義配置文件:詳見下面的添加其他配置
}
}
}
添加其他配置
- 可以通過在相應的 productFlavor 代碼塊中再添加一個 proguardFiles 屬性來添加每個構建變體專用的規則
android {
...
buildTypes {
release {
...
}
}
flavorDimensions "version"
productFlavors {
flavor1 {
...
}
flavor2 {
proguardFile 'flavor2-rules.pro'
}
}
}
- flavor2 使用全部三個 ProGuard 規則,因為還應用了來自 release 代碼塊的規則。
關閉R8
- 可以在gradle.properties里添加如下配置:
android.enableR8=false
開啟R8完全模式
- R8 普通模式是兼容 ProGuard的,若原項目里已使用了ProGuard,直接啟用 R8 即可。同時,R8 也有完全模式,與ProGuard不直接兼容。
可以在 gradle.properties 文件中另外設置以下內容:
android.enableR8.fullMode=true
- 額外的優化功能會使 R8 的行為與 ProGuard 不同,因此可能會需要您添加額外的 ProGuard 規則,以避免運行時問題。
自定義要保留的代碼
- 在某些情況下,R8 很難做出正確判斷,因而可能會移除應用實際上需要的代碼:
1. 當應用通過 Java 原生接口 (JNI) 調用方法時
2. 當您的應用在運行時查詢代碼時(如使用反射)
- 反射 (Reflection) 會導致 R8 在跟蹤代碼時無法識別到代碼的入口點
- 如需修復錯誤并強制 R8 保留某些代碼,在 ProGuard 規則文件中添加 -keep 代碼行,如
-keep public class MyClass
1. 在類上添加 @Keep 可按原樣保留整個類
2. 在方法或字段上添加該注釋,將使該方法/字段(及其名稱)以及類名稱保持不變。
3. 只有在使用 AndroidX 注解庫且您添加 Android Gradle 插件隨附的 ProGuard 規則文件時,此注解才可用。
- 如需輸出 R8 在構建項目時應用的所有規則的完整報告,請將以下代碼添加到模塊的 proguard-rules.pro 文件中:
// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt
自定義要保留的資源
- 如果您有想要保留或舍棄的特定資源,請在項目中創建一個包含 <resources> 標記的 XML 文件,并在 tools:keep 屬性中指定每個要保留的資源,在 tools:discard 屬性中指定每個要舍棄的資源。這兩個屬性都接受以逗號分隔的資源名稱列表。您可以將星號字符用作通配符。
- 將該文件保存在項目資源中,例如,保存在 res/raw/keep.xml 中。構建系統不會將此文件打包到應用中。
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
tools:discard="@layout/unused2" />
嚴格引用檢查
- 通常資源縮減器可以準確地判斷是否使用了某個資源。不過如果代碼中調用了 Resources.getIdentifier()(或者引用的任何庫會執行此調用,例如 AppCompat 庫便會執行此調用),這意味著代碼將根據動態生成的字符串查詢資源名稱。資源縮減器在默認情況下(安全縮減模式)會采取保護行為,將所有具有匹配名稱格式的資源標記為可能已使用,無法移除。資源縮減器還會查看代碼中的所有字符串常量以及各種 res/raw/ 資源,以查找格式類似于 file:///android_res/drawable//ic_plus_anim_016.png 的資源網址。如果它找到與此類似的字符串,或找到其他看似可用來構建與此類似的網址的字符串,則不會將它們移除。
- 例如,以下代碼會將所有帶 img_ 前綴的資源標記為已使用:
val name = String.format("img_%1d", angle + 1)
val res = resources.getIdentifier(name, "drawable", packageName)
- 啟用嚴格引用檢查: 將 keep.xml 文件中的 shrinkMode 設為 strict,此時如果通過動態生成的字符串引用資源,必須使用 tools:keep 屬性手動保留這些資源。
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
移除未使用的備用資源
- Gradle 資源縮減器只會移除未由應用代碼引用的資源,這意味著,它不會移除用于不同設備配置的備用資源;
- 例如使用的是包含語言資源的庫(如 AppCompat 或 Google Play 服務),那么應用中將包含這些庫中消息的所有已翻譯語言的字符串,可以使用 resConfigs 屬性移除應用不需要的備用資源文件,如設置只保留英語和法語的語言資源
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
合并重復資源
- 默認情況下,Gradle 還會合并同名的資源(多個文件具有完全相同的資源名稱、類型和限定符時)。這一行為不受 shrinkResources 屬性控制,也無法停用,因為當多個資源與代碼查詢的名稱匹配時,有必要利用這一行為避免錯誤。
- Gradle 會在重復項中選擇它認為最合適的文件(根據下述優先順序),并且只將這一個資源傳遞給 AAPT,以便在最終工件中分發
- Gradle 會按以下級聯優先順序合并重復資源:庫項目依賴項 → 主資源 → 構建變種 → 構建類型, 如某個重復資源同時出現在主資源和構建變種中,Gradle 會選擇構建變種中的資源。
- 如果完全相同的資源出現在同一源代碼集中,Gradle 無法合并它們,并且會發出資源合并錯誤,或者在 build.gradle 文件的 sourceSet 屬性中定義了多個源代碼集,src/main/res/ 和 src/main/res2/ 包含完全相同的資源也會報錯。
參考
我是今陽,如果想要進階和了解更多的干貨,歡迎關注微信公眾號 “今陽說” 接收我的最新文章