---
# System prepended metadata

title: Android 混淆機制
tags: [Tag(Android 技術相關)]

---

# 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 預設的混淆設定檔
- 根據ＡPP的特殊性增加或減少相關設定

- 其中 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