# 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`