# Android 混淆機制
###### tags: `Tag(Android 技術相關)`
Proguard : Android Studio 3.4以下默認默認開啟
R8 : Android Studio 3.4或Android Gradle插件3.4.0及更高版本時,R8是默認開啟
### 兩種混淆機制的相同特性:
* 4個功能特性:
* 壓縮:
- Java原始程式碼通常被編譯為位元組碼,雖然為元組碼比原始程式碼更簡潔,但他本身會包含很多無用的程式碼。
- 從應用及其庫依賴項中檢測並安全地移除未使用的類、字段、方法和屬性(這使其成為了一個對於規避[64k引用限制](https://developer.android.com/studio/build/multidex)非常有用的工具) 。例如,如果您僅使用某個庫依賴項的少數幾個API,縮減功能可以識別應用“未”使用的庫代碼並僅從應用中移除這部分代碼。如需了解詳情,請轉到介紹如何[縮減代碼](https://developer.android.com/studio/build/shrink-code#shrink-code)的部分。
* 最佳化
- 從封裝應用中移除不使用的資源,包括應用庫依賴項中的不使用的資源。此功能可與代碼縮減功能結合使用,這樣一來,移除不使用的代碼後,也可以安全地移除不再引用的所有資源。如需了解詳情,請轉到介紹如何[縮減資源](https://developer.android.com/studio/build/shrink-code#shrink-resources)的部分。
* 混淆
- 使用無意義的簡短字母組合對類別名稱、欄位名稱和方法名稱進行重新命名
- 縮短類和成員的名稱,從而減小DEX文件的大小。如需了解詳情,請轉到介紹如何[對代碼進行混淆處理](https://developer.android.com/studio/build/shrink-code#obfuscate)的部分。
* 優化
- 檢查並重寫代碼,以進一步減小應用的DEX文件的大小。例如,如果R8檢測到從未採用過給定if/else語句的else {}分支,則會移除else {}分支的代碼。如需了解詳情,請轉到介紹[代碼優化](https://developer.android.com/studio/build/shrink-code#optimization)的部分。
* build.gradle 相同設定
```
android {
buildTypes {
debug {
//關閉,僅啟用代碼收縮,混淆和優化
minifyEnabled false
//關閉,最佳化啟用資源縮減,該縮減由 Android Gradle插件
shrinkResources false
//默認ProGuard規則文件,配置檔 Android Gradle插件
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
release {
minifyEnabled true //開啟,混淆
shrinkResources true //開啟,去除用到的資源
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
```
### 輸出文件
啓用R8或ProGuard構建項目後會在模塊下的build\outputs\mapping\release文件夾下輸出下列文件:
dump.txt:說明 APK 中所有類文件的內部結構。
mapping.txt:提供原始與混淆過的類、方法和字段名稱之間的轉換。
seeds.txt:列出未進行混淆的類和成員。
usage.txt:列出從 APK 移除的代碼。
---
## Proguard
### 啟動和設定
一般build.gradle檔案預設已經對Proguard 進行設定,只不過是還沒有啟動。
- proguard-android-optimize.txt 是Proguard 預設的混淆設定檔
- 根據APP的特殊性增加或減少相關設定
- 其中 proguard-android.txt 為預設的混淆規則,該文件在SDK目錄 /tools/proguard 中。 proguard-rules.pro 為自定義的混淆規則。
**==注意:==**
**==使用 Proguard 時,不是只要 minifyEnabled 設為 true,就大公告成,因為 ProGuard 預設配置文件 (proguard-android.txt) ,會移除所有未使用的代碼。大部分情況下也會移除到真正需要的程式碼。這時我們就需要自定義哪些程式碼,需要特地保留,不要移除、不要混淆。==**
## 基本常用混淆語法
* 關鍵字 keep 就是要去保留什麼,不要去混淆什麼
* #為註解
* 保留類別和類別成員
| 保留 | 防止被刪除或重命名 | 防止被重命名 |
| -------- | -------- | -------- |
| 類別和類別成員 | -keep | -keepnames |
| 僅類別成員 | -keepclassmembers |-keepclassmembernames|
| 如果擁有某成員,保留類防止被重命名| -keepclasseswithmembers |-keepclasseswithmembernames
* 常用符號
| 符號 | 作用 |
| -------- | -------- | -------- |
| * | 匹配任意長度字符,但不含包名分隔符(.) |
| ** | 匹配任意長度字符,並且包含包名分隔符(.) |
| *** | 匹配任意參數類型 |
| … | 匹配任意長度的任意類型參數 |
| % | 匹配任何原始類型 |
| ? | 匹配類名中的任何單個字符 |
* 常用 ProGuard 模板
```
#壓縮比,預設5不修改
-optimizationpasses 5
#不使用大小寫混合,混淆後類名稱為小寫
-dontusemixedcaseclassnames
#指定不去忽略公開的 publicli classes
-dontskipnonpubliclibraryclasses
#混淆後產生印射文件
-verbose
#註解此行,可以自動上傳 mapping 檔到 Firebase
#-printmapping mapping.txt
#保留泛型
-keepattributes Signature
# 不做預校驗,加速建置速度
-dontpreverify
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 抛出異常時保留檔名與行數
-keepattributes SourceFile,LineNumberTable
# 保留 android-support
-dontwarn android.support.design.**
-keep class android.support.design.** { *; }
-keep interface android.support.design.** { *; }
-keep public class android.support.design.R$* { *; }
```
參考連結: http://tw-hkt.blogspot.com/2018/12/proguard.html
---
## R8
- R8會自動執行上述編譯時任務。不過,您也可以停用某些任務或通過ProGuard規則文件自定義R8的行為。
- ==R8支持所有現有ProGuard規則文件==,因此您在更新Android Gradle插件以使用R8時,無需更改現有規則。
### 啟動和設定
- 使用Android Studio 3.4或Android Gradle插件3.4.0及更高版本時,**R8是默認編譯器**,用於將項目的Java字節碼轉換為在Android平台上運行的DEX格式。
### R8 配置文件
#### R8 使用的ProGuard 規則文件可以使用的來源。
- **Android Studio <module-dir>/proguard-rules.pro**
當您使用Android Studio創建新模塊時,IDE會在該模塊的根目錄中創建文件。 proguard-rules.pro
默認情況下,此文件不會應用任何規則。因此,請在此處添加您自己的ProGuard規則,如您的[自定義保留規則](https://developer.android.com/studio/build/shrink-code#keep-code)
- **Android Gradle 插件 由Android Gradle 插件在編譯時生成**
Android Gradle插件會生成(其中包含了對大多數Android項目都有用的規則),並啟用註釋。 proguard-android-optimize.txt@Keep*
默認情況下,使用Android Studio創建新模塊時,模塊級build.gradle文件會將此規則文件納入到您的發布版本中。
**==注意:
雖然Android Gradle插件包含額外的預定義ProGuard規則文件,但建議您使用proguard-android-optimize.txt。==**
- **庫依賴項 AAR 庫:<library-dir>/proguard.txt
JAR 庫:<library-dir>/META-INF/proguard/**
- **ndroid 資產打包工具2 (AAPT2)**
使用minifyEnabled true構建項目後: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt
- **自定義配置文件**
默認情況下,當您使用Android Studio創建新模塊時,IDE會創建,以便您添加自己的規則。 <module-dir>/proguard-rules.pro
---
## R8 和 Proguard 相比
R8 可以更快地縮減代碼,同時改善輸出大小,R8 默認處於啓用狀態,你可將以下代碼添加到項目的 gradle.properties 文件以停用 R8:
`android.enableR8=false`
R8 普通模式是兼容 Proguard的,R8 完全模式與會啓用一些額外的優化,這個時候可能需要一些其它ProGuard規則以避免運行時問題,可以在 項目的gradle.properties 文件中設置以下內容啓用完全模式。
```
android.enableR8.fullMode=true
```
---
### 縮減代碼
如果將**minifyEnabled屬性設為true**,則默認會啟用R8代碼縮減功能
### 自定義要保留的代碼
强制 R8 保留某些代码,请在 ProGuard 规则文件中添加 -keep 代码行。例如:
`-keep public class MyClass`
或者,您也可以為您要保留的代碼添加[@Keep](https://developer.android.com/reference/androidx/annotation/Keep?hl=zh-cn)註釋。在類上==添加@Keep可按原樣保留整個類==。在方法或字段上添加該註釋,將使該方法/字段(及其名稱)以及類名稱保持不變。==請注意,只有在使用AndroidX註釋庫且您添加Android Gradle插件隨附的ProGuard規則文件時,此註釋才可用==。
---
### 縮減資源
**資源縮減**只有在與**代碼縮減**配合使用時才能發揮作用
**shrinkResources ==true==**
**minifyEnabled ==true==**
### 自定義要保留的資源
想要保留或捨棄的特定資源,請在項目中創建一個包含<resources>標記的XML文件,並==在**tools:keep**屬性中指定每個要保留的資源==,==在**tools:discard**屬性中指定每個要捨棄的資源==。這兩個屬性都接受以逗號分隔的資源名稱列表。您可以將==星號字符用作通配符==。
```
<? 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**庫便會執行此調用),則==意味著您的代碼將根據動態生成的字符串查詢資源名稱==。
當您啟用嚴格引用檢查時,資源縮減器在默認情況下會採取保護性行為,將所有具有匹配名稱格式的資源標記為可能已使用,無法移除
例如,以下代碼會將所有帶img_前綴的資源標記為已使用。
```
String name = String . format ( "img_%1d" , angle + 1 );
res = getResources (). getIdentifier ( name , "drawable" , getPackageName ());
```
資源縮減器還會查看代碼中的所有字符串常量以及各種res/raw/資源,以查找格式類似於==file:///android_res/drawable//ic_plus_anim_016.png的資源網址==。找到與此類似的字符串,或找到其他看似可用來構建與此類似的網址的字符串,==則不會將它們移除==。
==默認啟用==的安全縮減模式的一些示例。不過,您可以==停用==這種“防患於未然”的處理方式,指定資源縮減器只保留確定要使用的資源。為此,您可以==將keep.xml文件中的**shrinkMode**設置為**strict**==,如下所示:
<? xml version = "1.0" encoding = "utf-8" ?>
<resources xmlns:tools = "http://schemas.android.com/tools"
tools:shrinkMode = "strict" />
確實啟用了嚴格縮減模式,並且您的代碼也通過動態生成的字符串引用資源(如上所示),則您必須使用**tools:keep**屬性來手動保留這些資源。
### 移除不使用的備用資源
### 合併重複資源
默認情況下,Gradle還會合併同名的資源,如可能位於不同資源文件夾中的同名可繪製對象。這一行為不受shrinkResources屬性控制,也無法停用,因為當多個資源與代碼查詢的名稱匹配時,有必要利用這一行為來避免錯誤。
只有在兩個或更多個文件具有完全相同的資源名稱、類型和限定符時,才會進行資源合併。Gradle 會在重複項中選擇它認為最合適的文件(根據下述優先順序),並且只將這一個資源傳遞給AAPT,以便在APK 文件中分發。
Gradle 會在以下位置查找重複資源:
* 與主源集關聯的主資源,一般位於src/main/res/中。
* 來自版本類型和版本變種的變體疊加層。
* 庫項目依賴項
Gradle 會按以下級聯優先順序合併重複資源:
依賴項→ 主資源→ 版本變種→ 版本類型
---
參考說明:https://www.twblogs.net/a/5d443e55bd9eee5327fb496b
官網說明:https://developer.android.com/studio/build/shrink-code?hl=zh-cn#enable
#### R8常見問題 https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md
#### Proguard 常見問題
https://www.guardsquare.com/en/products/proguard/manual/troubleshooting