點贊關注,再也不迷路,你的支持對我意義重大!android
🔥 Hi,我是醜醜。本文 GitHub · Android-NoteBook 已收錄,這裏有 Android 進階成長路線筆記 & 博客,歡迎跟着彭醜醜一塊兒成長。(聯繫方式在 GitHub)git
代碼混淆對於每一個入門的 Android 工程師來講都不會太陌生,由於在編譯正式版本時,這是一個必不可少的過程。並且使用代碼混淆也至關簡單,簡單到只須要配置一句minifyEnabled true
。可是你是否理解混淆的原理,若是問你代碼混淆到底作了什麼,你會怎麼說?github
若是以混淆編譯器來劃分的話,Android 代碼混淆能夠分爲如下兩個時期:web
下圖梳理了它們隨着 Android Gradle Plugin 版本迭代相應作出的變動:markdown
其中,混淆編譯器的變動:架構
其中:DEX編譯器的變動:app
若是須要修正 Android Gradle Plugin 的默認行爲,能夠在gradle.properties
中添加配置:工具
# 顯式啓用 R8
android.enableR8 = true
複製代碼
# 1. 只對 Android Library module 停用 R8 編譯器
android.enableR8.libraries = false
# 2. 對全部 module 停用 R8 編譯器
android.enableR8 = false
複製代碼
# 顯式啓用 D8
android.enableD8 = true
複製代碼
# 顯式禁用 D8
android.enableD8 = false
複製代碼
另外,若是在應用模塊的 build.gradle
文件中設置useProguard = false
,也會使用 R8 編譯器代替 ProGuard。oop
ProGuard 與 R8 都提供了壓縮(shrinker)、優化(optimizer)、混淆(obfuscator)、預校驗(preverifier)四大功能:組件化
壓縮(也稱爲搖樹優化,tree shaking):從 應用及依賴項 中移除 未使用 的類、方法和字段,有助於規避 64 方法數的瓶頸
優化:經過代碼 分析 移除更多未使用的代碼,甚至重寫代碼
混淆:使用無心義的簡短名稱 重命名 類/方法/字段,增長逆向難度
預校驗:對於面向 Java 6 或者 Java 7 JVM 的 class 文件,編譯時能夠把 預校驗信息 添加到類文件中(StackMap 和 StackMapTable屬性),從而加快類加載效率。預校驗對於 Java 7 JVM 來講是必須的,可是對於 Android 平臺 無效
使用 ProGuard 時,部分編譯流程以下圖所示:
使用 R8 時,部分編譯流程以下圖所示:
對比如下 ProGuard 與 R8 :
一、開源
二、R8 支持全部現有 ProGuard 規則文件
三、都提供了四大功能:壓縮、優化、混淆、預校驗
一、ProGuard 可用於 Java 項目,而 R8 專爲 Android 項目設計
二、R8 將脫糖(Desugar)、壓縮、優化、混淆和 dex(D8 編譯器)整合到一個步驟中,顯着提升了編譯性能
關於 D8 編譯器
將 .class 字節碼轉化爲 .dex 字節碼的過程被稱爲 DEX 編譯,最初是由DX 編譯器完成。與 DX 編譯器相比,新的 D8 編譯器的編譯速度 更快,輸出的 .dex 文件 更小 ,卻能保持相同乃至 更出色 的應用運行時性能
不管使用 R8 仍是 ProGuard,默認不會啓用壓縮、優化和混淆功能。 這個設計主要是出於兩方面考慮:一方面是由於這些編譯時任務會增長編譯時間,另外一方面是由於若是沒有充分定義混淆保留規則,還可能會引入運行時錯誤。所以,最好 只在應用的測試版本和發佈版本中啓用這些編譯時任務,參考使用示例:
// build.gradle
...
android {
buildTypes {
// 測試版本
preview {
// 啓用代碼壓縮、優化和混淆(由R8或者ProGuard執行)
minifyEnabled true
// 啓用資源壓縮(由Android Gradle plugin執行)
shrinkResources true
// 指定混淆保留規則文件
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// 發佈版本
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// 開發版本
debug{
minifyEnabled false
}
}
...
}
複製代碼
minifyEnabled
:(默認狀況下)啓用代碼壓縮、優化、混淆與預校驗shrinkResources
:啓用資源壓縮proguardFiles
、proguardFile
:指定 ProGuard 規則文件,前者能夠指定多個參數。下面兩段配置的做用是同樣的。 // 方式一:
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
// 方式二:
proguardFile getDefaultProguardFile('proguard-android-optimize.txt')
proguardFile 'proguard-rules.pro'
複製代碼
前面提到了:不管使用R8仍是ProGuard,壓縮、優化和混淆功能都是 默認關閉的。經過如下配置能夠靈活控制:
minifyEnabled false
// 這行就沒有效果了
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
複製代碼
-dontshrink
複製代碼
-dontoptimize
複製代碼
注意:R8 不能關閉優化,也不容許修改優化的行爲,事實上,R8 會忽略修改默認優化行爲的規則。例如設置 -optimizations
和 -optimizationpasses
後會獲得編譯時警告:
AGPBI: {"kind":"warning","text":"Ignoring option: -optimizations","sources":[{"file":"省略..."}],"tool":"D8"}
AGPBI: {"kind":"warning","text":"Ignoring option: -optimizationpasses","sources":"省略..."}],"tool":"D8"}
複製代碼
-dontobfuscate
複製代碼
-dontpreverify
複製代碼
R8 延續了 ProGuard 使用規則文件修改默認行爲的作法。在不少時候,規則文件也被稱爲混淆保留規則文件,這是由於該文件內定義的絕大多數規則都是和代碼混淆相關的。事實上,文件內還能夠定義代碼壓縮、優化和預校驗規則,所以稱爲 ProGuard 規則文件比較嚴謹。
在上一節裏,咱們提到了使用proguardFiles
和proguardFile
指定 ProGuard 規則文件。對於任何一個項目,它的 ProGuard 規則文件有如下三種來源:
在編譯時,Android Gradle 插件會生成 proguard-android-optimize.txt
、 proguard-android.txt
,位置在<module-dir>/build/intermediates/proguard-files/
。這兩個文件中除了註釋以外,惟一的區別是前者啓用了以下代碼壓縮,然後者關閉了代碼壓縮,以下所示:
# proguard-android-optimize.txt
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
相同部分省略...
複製代碼
# proguard-android.txt
-dontoptimize
相同部分省略...
複製代碼
其中相同的那部分混淆規則中,下面這一部分是比較特殊的:
-keep class android.support.annotation.Keep
-keep class androidx.annotation.Keep
// 保留@Keep註解的類,保留...TODO
-keep @android.support.annotation.Keep class * {*;}
-keep @androidx.annotation.Keep class * {*;}
// 保留@Keep修飾的方法
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <methods>;
}
// 保留@Keep修飾的字段
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <fields>;
}
// 保留@Keep修飾的構造方法
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <init>(...);
}
複製代碼
它指定了與@Keep
註解相關的全部保留規則,這裏就解釋了爲何使用@Keep
修飾的成員不會被混淆了吧?
在編譯時,AAPT2 會根據對 Manifest 中的類、佈局及其餘應用資源的引用來生成aapt_rules.txt
,位置在<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt
。 例如,AAPT2 會爲 Manifest 中註冊的每一個組件添加保留規則:
Referenced at [項目路徑]/app/build/intermediates/merged_manifests/release/AndroidManifest.xml:19
-keep class com.have.a.good.time.MainActivity { <init>(); }
省略...
複製代碼
在這裏,AAPT2 生成了MainActivity
的保留規則,同時它還指出了引用出處:AndroidManifest.xml:19
。這是由於 啓動 Activity 的過程當中,須要使用反射的方式實例化具體的每個 Activity ,有興趣能夠看下 ActivityThread#performLaunchActivity()
-> Instrumentation#newActivity()
建立新 Module 時,IDE 會在該模塊的根目錄中建立一個 proguard-rules.pro
文件。固然,除了這個自動生成的文件,還能夠按需建立額外的規則文件。例如,下面的配置對 release 添加了額外的規則文件:
...
android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
productFlavors {
dev{
...
}
release{
proguardFile 'release-rules.pro'
}
}
}
...
複製代碼
小結一下:
規則文件來源 | 描述 |
---|---|
Android Gradle 插件 | 在編譯時,由 Android Gradle 插件生成 |
AAPT2 | 在編譯時,AAPT2 根據對應用清單中的類、佈局及其餘應用資源的引用來生成保留規則 |
Module | 建立新 Module 時,由 IDE 建立,或者另外按需建立 |
若是將 minifyEnabled
屬性設爲 true
,ProGuard 或 R8 會未來自上面列出的全部可用來源的規則組合在一塊兒。爲了看到完整的規則文件,能夠在proguard-rules.pro
中添加如下配置,輸出編譯項目時應用的全部規則的完整報告:
-printconfiguration build/intermediates/proguard-files/full-config.txt
複製代碼
在組件化的項目中,須要注意應用 Module 和 Library Module 的行爲差別和組件化的資源匯合規則,總結爲如下幾個重點:
使用較高版本的 Android Gradle Plugin,不會將彙總的資源放置在 exploded-aar
文件夾。即使如此,Lib Module 的資源彙總到 App Module 的規則是同樣的。
咱們經過一個簡單示例測試不一樣配置下的混淆結果:
配置一 | 配置二 | 配置三 | 配置四 | |
---|---|---|---|---|
App Module 開啓混淆 | X | X | √ | √ |
Base Module 開啓混淆 | X | √ | X | √ |
將構建的 apk 包拖到 Android Studio 面板上便可分析 Base 類混淆結果,例如配置一的結果:
所有測試結果以下:
配置一 | 配置二 | 配置三 | 配置四 | |
---|---|---|---|---|
App Module 開啓混淆 | X | X | √ | √ |
Base Module 開啓混淆 | X | √ | X | √ |
(結果)Base 類是否被混淆 | X | X | √ | √ |
能夠看到,混淆開啓由 App Module 決定, 與Lib Module 無關。
如今咱們分別在 Lib Module 和 App Module 的 proguard-rules.pro
中添加 Base 類的混淆保留規則,並在 build.gradle
中添加配置文件,測試 Base 類是否能保留:
-keep class com.rui.base.Base
複製代碼
測試結果以下:
配置位置 | Lib Module | App Module |
---|---|---|
(結果)Base 類是否保留 | X | √ |
能夠看到:(默認狀況)混淆規則以 App Module 中的混淆規則文件爲準。
這裏就引入兩種主流的組件化混淆方案:
這種方案將混淆規則都放置到 App Module 的proguard-rules.pro
中,最簡單也最直觀,缺點是移除 Lib Module 時,須要從 App Module 中移除相應的混淆規則。儘管多餘的混淆規則並不會形成編譯錯誤或者運行錯誤,但仍是會影響編譯效率。
不少的第三方 SDK,就是採用了這種組件化混淆方案。在 App Module 中添加依賴的同時,也須要在proguard-rules.pro
中添加專屬的混淆規則,這樣才能保證release
版本正常運行。
這種方案將專屬的混淆規則設置到 Lib Module 的proguard-rules.pro
,可是根據前面的測試,在 Lib Module 中設置的混淆規則是不生效的。爲了讓規則生效,還須要在 Lib Module 的build.gradle
中添加如下配置:
...
android{
defaultConfig{
consumerProguardFiles 'consumer-rules.pro'
}
}
複製代碼
其中consumer-rules.pro
文件:
-keep class com.rui.base.Base
複製代碼
測試結果代表,Base 類已經被保留了。這種使用consumerProguardFiles
的方式有如下幾個特色:
consumerProguardFiles
只對 Lib Module 生效,對 App Module 無效consumerProguardFiles
會將混淆規則輸出爲proguard.txt
文件,並打包進 aar 文件proguard.txt
彙總爲最終的混淆規則,這一點能夠經過前面提到的-printconfiguration
證實ProGuard 是 Java 字節碼優化工具,而 R8 是專爲 Android 設計的,編譯性能和編譯產物更優秀;
ProGuard 與 R8 都提供了四大功能:壓縮、優化、混淆和預校驗。ProGuard 主要是對 .class 文件執行代碼壓縮、優化與混淆,再由 D8 編譯器執行脫糖並轉換爲 .dex 文件。R8 將壓縮、優化、混淆、脫糖和 dex 整合爲一個步驟;
ProGuard 規則文件有三種來源:Android Gradle 插件、AAPT二、Module;
默認狀況下,混淆規則以 App Module 中的混淆規則文件爲準,使用 consumer-rules.pro 文件能夠設置 Lib Module 專屬混淆規則。
創做不易,你的「三連」是醜醜最大的動力,咱們下次見!