---
# System prepended metadata

title: Obfuscate tool - R8
tags: [obfuscate, ProGuard, R8, Android, kotlin]

---

# Obfuscate tool - R8

* Gradle plugin 3.4以上預設已經不使用ProGuard執行編譯時的程式碼優化
* 現在預設是使用R8，配合ProGuard rule排除特定類別或函數

### R8編譯時執行那些任務
* Code shrinking
    * 它會幫你移除dependencies裡面沒有使用到的類別、方法、屬性，比方說引用了retrofit，但只使用到@GET，那其他沒使用到的方法，在這個階段就會被移除。
    * 如果dependency太多，就容易出現64K限制[官方解說](https://developer.android.com/studio/build/multidex)，透過code shrinking可以盡可能避免64K限制
* Resource shrinking
    * 它幫你移除app內的library dependencies沒使用到的recources，通常搭配code shrinking
* Obfuscation
    * 這邊會把class跟members重新命名，進而減少apk的大小
* Optimization
    * 這邊做程式碼優化，官網提到是if/ else區塊，如果只使用到if，它會自動把else移除，
    * 我在想是不是在kotlin我如果只寫if，在編譯時若沒用R8優化，在編譯完是不是也會包含一個else的空區塊...

### 如何使用呢
* 在build.gradle(Module:)。minifyEnable就是code shrinking
* 底下在加一行是recources shrinking
* 這樣就打開R8的基本功能了
![](https://i.imgur.com/dPr8Ofh.png)

### 搭配ProGuadr rule
* 每個新專案都會有一個空的rule: proguard-rules.pro
* 這裡面要填一些要排除，不要進行混淆的類別跟成員
* 那它也可以針對不同階段的產品配置不同的規則，太深入..可以[參考官方](https://developer.android.com/studio/build/shrink-code)
![](https://i.imgur.com/B8HU12C.png)

#### 如何定義要保留的code不被混淆呢
* 方法有兩個
    * 直接在class使用聲明註解 @Keep
    ![](https://i.imgur.com/depRj92.png)
    * 在proguard-rules.pro定義
        * com.androiddevs.fcm是我的專案名稱，PushNotification是資料類別
    ```kotlin
    -keep class com.androiddevs.fcm.PushNotification
    ```
#### 語法

[保持命令] [類別名稱] { [成员] }
* 保持命令
1. -keep                           # 防止類別和成員被移除或被混淆
2. -keepnames                      # 防止类和成員名稱被混淆
3. -keepclassmembers               # 防止類別成員被移除或被混淆
4. -keepclassmembernames           # 防止類別成員被混淆
5. -keepclasseswithmembers         # 防止參照該成員的類別和成員被移除或被混淆
6. -keepclasseswithmembernames     # 防止參照該成員的類別和成員被混淆

* 類別
1. 明確指定的類別
2. 修飾符 : public、private、protected
3. 通配符(*) : 匹配任意长度字符，但不包含包名分隔符(.)
4. 通配符(**) : 匹配任意长度字符，且包含包名分隔符(.)
5. extends : 繼承parent class的 child class
6. implements : 實例化特定介面類別
7. $ : 內部類別

* 成員
1. 建構子 : <init>
2. 變數 : <field>
3. 方法 : <methods>
4. 修飾符 : public、private、protected
5. 除了 * 和 ** 通配符外，還支持 *** 通配符，匹配任意参数類型
6. ... : 匹配任意長度的任意類型参数，如void test(...)可以匹配不同参数個數的test方法

```kotlin=
// keep fcm這個package底下的類別名稱，防止被刪除會混淆，但是fcm底下的子package不在此限
-keep class com.androiddevs.fcm.*

// keep fcm這個package和子package底下的類別名稱，防止被刪除會混淆
-keep class com.androiddevs.fcm.**

## 上述兩種它的方法和變數還是會被混淆，底下加入成員

//保持fcm package類別名稱還有方法與變數不被混淆
-keep class com.androiddevs.fcm.*{*;}

-keep class com.androiddevs.fcm.**{*;}

```
* 參考網路上一些相關的些法
```kotlin=
# 不混淆某个类的类名，及类中的内容
-keep class cn.coderpig.myapp.example.Test { *; }

# 不混淆指定包名下的类名，不包括子包下的类名
-keep class cn.coderpig.myapp*

# 不混淆指定包名下的类名，及类里的内容
-keep class cn.coderpig.myapp* {*;}

# 不混淆指定包名下的类名，包括子包下的类名
-keep class cn.coderpig.myapp**

# 不混淆某个类的子类
-keep public class * extends cn.coderpig.myapp.base.BaseFragment

# 不混淆实现了某个接口的类
-keep class * implements cn.coderpig.myapp.dao.DaoImp

# 不混淆类名中包含了"entity"的类，及类中内容
-keep class **.*entity*.** {*;}

# 不混淆内部类中的所有public内容
-keep class cn.coderpig.myapp.widget.CustomView$OnClickInterface {
    public *;
}

# 不混淆指定类的所有方法
-keep cn.coderpig.myapp.example.Test {
    public <methods>;
}

# 不混淆指定类的所有字段
-keep cn.coderpig.myapp.example.Test {
    public <fields>;
}

# 不混淆指定类的所有构造方法
-keep cn.coderpig.myapp.example.Test {
    public <init>;
}

# 不混淆指定参数作为形参的方法
-keep cn.coderpig.myapp.example.Test {
    public <methods>(java.lang.String);
}

# 不混淆类的特定方法
-keep cn.coderpig.myapp.example.Test {
    public test(java.lang.String);
}

# 不混淆native方法
-keepclasseswithmembernames class * {
    native <methods>;
}

# 不混淆枚举类
-keepclassmembers enum * {
  public static **[] values();
  public static ** valueOf(java.lang.String);
}

#不混淆资源类
-keepclassmembers class **.R$* {
    public static <fields>;
}

# 不混淆自定义控件
-keep public class * entends android.view.View {
    *** get*();
    void set*(***);
    public <init>;
}

# 不混淆实现了Serializable接口的类成员，此处只是演示，也可以直接 *;
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 不混淆实现了parcelable接口的类成员
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}
```

#### 注意事項
* Android底層元件和類別不可混淆
    ```kotlin
    -keep public class * extends android.app.Activity
    -keep public class * extends android.app.Application
    -keep public class * extends android.app.Service
    -keep public class * extends android.content.ContentProvider
    -keep public class * extends android.content.BroadcastReceiver
    -keep public class * extends android.view.View
    -keep public class * extends android.preference.Preference
    ```
    
* jni方法不可混淆
    ```kotlin
    -keepclasswithmembernames class *{
    native <methods>;
    }
    ```
    
* 反射用到的類名和方法不可混淆
    ```kotlin
    -keep public class com.example.ljz.** {
    public void set*(***);
    public *** get*();
    public *** is*();
    }
    
    ```
* 自定義View不可混淆(有查到其他有說系統默認不會混淆，不須額外設定)
* 第三方框架不可混淆，像一些Retrofit、Glide..等等的，除非有提供混淆規則
* WebView和Js互調介面不可混淆
    ```kotlin
    -keepclassmembers class ** {
    @android.webkit.JavascriptInterface public *;
    }
    ```
* 序列化的類別不可混淆
    ```kotlin
    -keepclassmembers class * implements android.os.Parcelable {
    static ** CREATOR;
    <fields>;
    <methods>;
    }

    -keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
    }
    ```
* 列舉類別
    ```kotlin
    -keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
    public static ** valueOf(int);
    }
    ```
* 總之...還有很多很多場景，有需要用到再去查詢吧~~~

#### Recources shrinking
* 這部分好像比較少用到，參考官網
* 在res路徑下建立 raw/keep.xml
    * tools:keep指定要保留的資源
    * tools:discard指定要捨棄的資源
```kotlin=
<?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" />
```
### mapping table
* 當你使用R8打包apk之後，會產生mapping file
* 在Project/app/build/outputs/mapping/release/mapping.txt
![](https://i.imgur.com/YS0g0pG.png)
* 這個文件可以在使用者發生系統崩潰產生的log，你必須上傳這個檔案到google play console，它可以協助去混淆，讓你方便閱讀log訊息
* [參考官方說明](https://support.google.com/googleplay/android-developer/answer/9848633?hl=zh-Hans&visit_id=637624395516347080-1010531705&rd=1)

參考資料
[Android混淆之Proguard的語法總結](https://www.itread01.com/content/1545201385.html)
[知乎](https://zhuanlan.zhihu.com/p/383300926)

###### tags: `R8` `ProGuard` `obfuscate` `kotlin` `Android`