Android代碼的縮減、混淆和優化
爲什麼把這三個內容放到一起說?因爲在Android Gradle中配置方法基本是在一起的。
官方說明如下:
爲了儘可能減小應用的大小,您應在發佈 build 中啓用縮減功能來移除不使用的代碼和資源。啓用縮減功能後,您還會受益於兩項功能,一項是混淆處理功能,該功能會縮短應用的類和成員的名稱;另一項是優化功能,該功能會採用更積極的策略來進一步減小應用的大小。本頁介紹 R8 如何爲項目執行這些編譯時任務,以及您如何對這些任務進行自定義。
當您使用 Android Gradle 插件 3.4.0 或更高版本構建項目時,該插件不再使用 ProGuard 執行編譯時代碼優化,而是與 R8 編譯器協同工作,處理以下編譯時任務:
- 代碼縮減(即搖樹優化):從應用及其庫依賴項中檢測並安全地移除不使用的類、字段、方法和屬性(這使其成爲了一個對於規避 64k 引用限制非常有用的工具)。例如,如果您僅使用某個庫依賴項的少數幾個 API,那麼縮減功能可以識別應用不使用的庫代碼並僅從應用中移除這部分代碼。如需瞭解詳情,請轉到介紹如何縮減代碼的部分。
- 資源縮減:從封裝應用中移除不使用的資源,包括應用庫依賴項中不使用的資源。此功能可與代碼縮減功能結合使用,這樣一來,移除不使用的代碼後,也可以安全地移除不再引用的所有資源。如需瞭解詳情,請轉到介紹如何縮減資源的部分。
- 混淆:縮短類和成員的名稱,從而減小 DEX 文件的大小。如需瞭解詳情,請轉到介紹如何對代碼進行混淆處理的部分。
- 優化:檢查並重寫代碼,以進一步減小應用的 DEX 文件的大小。例如,如果 R8 檢測到從未採用過給定 if/else 語句的
else {}
分支,則會移除else {}
分支的代碼。如需瞭解詳情,請轉到介紹代碼優化的部分。默認情況下,在構建應用的發佈版本時,R8 會自動執行上述編譯時任務。不過,您也可以停用某些任務或通過 ProGuard 規則文件自定義 R8 的行爲。事實上,R8 支持所有現有 ProGuard 規則文件,因此您在更新 Android Gradle 插件以使用 R8 時,無需更改現有規則。
開啓混淆功能
上面沒有提到的一個代碼混淆的重要作用:我們知道apk文件是相對容易被反編譯的,未加混淆的apk,反編譯後基本裸奔。而混淆的apk即使被反編譯,類名與變量名都會處理成無意義的字符,很大程度上降低了源碼的可讀性。
以下是在Android Studio中開啓混淆的方法和常用的配置。Android Studio的Gradle混淆功能默認是關閉的,我們需要手動在module的build.gradle中打開。
- 在Module的build.gradle中添加如下配置:
android{
...
buildTypes {
release {
// 開啓混淆
minifyEnabled true
// 開啓資源壓縮,編譯時會自動刪除未使用到的res資源
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
//開啓混淆會導致編譯速度變慢,debug通常不開啓
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
之後我們需要在proguard-rules.pro文件中編輯混淆規則。
proguard手冊
輸入/輸出配置項
-
@filename
等同於:-include filename,filename 文件名。
-
-include [filename]
從給定文件中讀取配置項,filename 文件名。
-
-basedirectory [directoryname]
爲後續的相對文件指定基礎目錄,directoryname 目錄名。
-
injars [classPath]
指定要處理的輸入jar(等壓縮包),包中的class文件會被處理後輸出到outjars的指定路徑下,非class文件直接輸出到outjars指定路徑,classpath jar包輸入路徑。 -
outjars [classPath]
指定輸出路徑,接收injars的文件輸出,classPath 輸出路徑。 -
libraryjars [classPath]
指定不被混淆的jar,classPath 文件路徑。
壓縮配置項
-
-dontshrink
不進行代碼壓縮,默認會進行代碼壓縮,打開此配置項則不進行壓縮。
-
-printusage [fileName]
將被壓縮的代碼輸出到指定路徑。
優化配置項
-
-dontoptimize
指定不進行代碼優化,默認情況下ProGuard會優化所有代碼。
-
-optimizations [optimization_filter]
在更細粒度的級別指定要啓用和禁用的優化,篇幅有限不展開講了,具體可以參考:Configuration - Optimizations
常用的配置方式:
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-
-optimizationpasses n
指定代碼迭代優化的次數,默認執行一遍。
混淆配置
-
-dontobfuscate
不進行混淆,默認會進行代碼混淆。
-
-printmapping [filename]
將混淆前後映射表輸出到指定文件。
-
-dontusemixedcaseclassnames
混淆時不使用大小寫混合,混淆後類名爲小寫。
-
-keepattributes [attribute_filter]
指定要保留的屬性,如:
-keepattributes Exceptions,InnerClasses
,具體可參考:Optional attributes
通用配置項
-
-verbose
指定在處理期間輸出更多信息。如果程序因異常終止,此選項將打印出整個堆棧跟蹤,而不僅僅是異常消息。
-
-dontnote [class_filter]
指定不打印匹配類的相關異常信息,class_filter爲正則表達式,符合表達式的類不打印信息。
-
-dontwarn [class_filter]
指定相關類不發出警告,class_filter爲正則表達式。如:
-dontwarn androidx.**
-
-dump [filename]
將所有class的內部結構輸出到指定文件。如:
-dump proguard/class_files.txt
keep配置項
-
-keep []
指定要保留的類和類成員。
//View子類,類名不混淆 - keep public class * extends android.view.View{ //set開頭的方法不混淆 void set*(***); //構造函數不混淆 public <init>(android.content.Context); public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); }
指定的類名和類成員不被混淆,需要注意的是以下配置只能保證類名不被混淆。
-keep public class * extends android.app.Activity
想要類名和類成員全部不混淆應該使用:
-keep public class * extends android.app.Activity{*;}
-
-keepclassmembers
保留指定的類成員。
//指定不混淆的類成員,類名會混淆 -keepclassmembers class * { //事件監聽類(參數符合**On*Event)的方法不混淆 void *(**On*Event); }
-
-keepclasseswithmembers
在所有類成員都存在的情況下,指定要保留的類和類成員。
//需要匹配類和所有指定成員都存在的類,類和類成員不混淆 -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); }
功能與
-keep
很像,舉個例子:public class MyClass extends android.view.View{ public MyClass(Context context) { super(context); } public MyClass(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MyClass(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } /** * 自定義方法 * @param onClickListener */ public void addListener(OnClickListener onClickListener){ //TODO: todo } }
當使用
-keep
時:- keep public class * extends android.view.View{ //構造函數不混淆 public <init>(android.content.Context); public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); public void addListener(***) public void getListener(***) }
MyClass類名和指定的類成員不會被混淆(未指定的類成員也會被混淆),因爲MyClass符合
class * extends android.view.View
條件。而使用
-keepclasseswithmembers
時:- keep public class * extends android.view.View{ //構造函數不混淆 public <init>(android.content.Context); public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); public void addListener(***) public void getListener(***) }
MyClass會被混淆,因爲MyClass不符合
public void getListener(***)
條件。當keepclasseswithmembers
指定的類和類成員全部存在的情況下,纔會匹配該類(MyClass不存在getListener()
方法,所以不能被匹配)。 -
-keepnames/-keepclassmembernames和-keepclasseswithmembernames
選項 等同於 作用 -keepnames ** -keep,allowshrinking ** 指定要保留的類和類成員,允許代碼壓縮優化,成員可能在壓縮階段被刪除 -keepclassmembernames ** - keepclassmembers,allowshrinking ** 指定要保留的類成員,允許代碼壓縮優化,成員可能在壓縮階段被刪除 -keepclasseswithmembernames ** -keepclasseswithmember,allowshrinking ** 在所有類成員都存在的情況下,指定要保留的類和類成員,允許代碼壓縮優化,成員可能在壓縮階段被刪除 簡單來說使用
-keep/-keepclassmember和-keepclasseswithmember
可能會阻止成員被壓縮優化,而使用-keepnames/-keepclassmembernames和-keepclasseswithmembernames
而不會。
Keep配置項修飾符
可用用來修飾-keep/-keepclassmember和-keepclasseswithmember
,使用方式:
-keep[,modifiter,...] class_specification
-keep,allowshrinking public class * extends android.view.View{*;}
-
allowshrinking
上面提到過了,允許對象進行代碼壓縮,對象可能在壓縮階段被刪除。
-
allowoptimization
指定的對象可能會被改變(優化步驟),但可能不會被混淆或者刪除。
-
allowobfuscation
指定對象可能會被重命名,但是不會被刪除和優化。
-
includedescriptorclasses
-
includecode
這兩個自行了解吧,太不常用了。(其實我也沒搞懂。。。)
通配符
符號 | 意義 | 人話 |
---|---|---|
? |
matches any single character in a class name, but not the package separator. For example, "com.example.Test? " matches "com.example.Test1 " and "com.example.Test2 ", but not "com.example.Test12 ". |
匹配名稱中的任意單個字符。 |
* |
matches any part of a class name not containing the package separator. For example, "com.example.*Test* " matches "com.example.Test " and "com.example.YourTestApplication ", but not "com.example.mysubpackage.MyTest ". Or, more generally, "com.example.* " matches all classes in "com.example ", but not in its subpackages. |
匹配名稱中任意多個字符,不包含包分隔符和目錄分隔符,用在類體中可以匹配任意字段和方法。 |
** |
matches any part of a class name, possibly containing any number of package separators. For example, "**.Test " matches all Test classes in all packages except the root package. Or, "com.example.** " matches all classes in "com.example " and in its subpackages. |
匹配名稱中任何部分,可以包含分隔符。 |
<n> |
matches the n'th matched wildcard in the same option. For example, "com.example.*Foo<1> " matches "com.example.BarFooBar ". |
在相同選項中匹配第n個匹配的通配符。 |
篇幅有限只列舉了部分常用的配置項,詳細的說明可以查詢:ProGuard 手冊。
proguard常用配置規則
常用文件配置如下:
1.常用配置
-optimizationpasses 5
# 混淆時不使用大小寫混合,混淆後的類名爲小寫
-dontusemixedcaseclassnames
# 指定不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses
# 指定不去忽略非公共庫的成員
-dontskipnonpubliclibraryclassmembers
# 混淆時不做預校驗
-dontpreverify
# 混淆時不記錄日誌
-verbose
# 代碼優化
-dontshrink
# 不優化輸入的類文件
-dontoptimize
# 保留註解不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 保留代碼行號,方便異常信息的追蹤
-keepattributes SourceFile,LineNumberTable
# 混淆採用的算法
-optimizations !code/simplification/cast,!field/*,!class/merging/*
# dump.txt文件列出apk包內所有class的內部結構
-dump proguard/class_files.txt
# seeds.txt文件列出未混淆的類和成員
-printseeds proguard/seeds.txt
# usage.txt文件列出從apk中刪除的代碼
-printusage proguard/unused.txt
# mapping.txt文件列出混淆前後的映射
-printmapping proguard/mapping.txt
2. Android 系統類
android類
# Android類
-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.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
support包
# support
-keep class android.support.** {*;}
-keep public class * extends android.support.**
-dontwarn android.support.**
-keep interface android.support.** { *; }
androidx包
# androidx
-keep class androidx.** {*;}
-keep interface androidx.** {*;}
-keep public class * extends androidx.**
-dontwarn androidx.**
自定義控件get/set和構造
# 自定義控件
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
R文件
# R文件
-keep class **.R$* {
*;
}
webview
# webview
-keepclassmembers class android.webkit.WebView {
public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, *);
}
android事件方法
# 按鍵等事件
-keepclassmembers class * {
void *(**On*Event);
}
# onClick
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
枚舉類型
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
其他類
# native方法
-keepclasseswithmembernames class * {
native <methods>;
}
# View構造方法
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# Parcelable
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 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();
}
3. 第三庫
ButterKnife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
OkHttp
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**
glide 3
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
glide 4
-keep public class * implements com.bumptech.glide.module.AppGlideModule
-keep public class * implements com.bumptech.glide.module.LibraryGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
gson
-keep class com.google.gson.** {*;}
-keep class com.google.**{*;}
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
greendao
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use Rx:
-dontwarn rx.**
Retrofit2
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
RxJava、RxAndroid
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
Picasso
-keep class com.parse.*{ *; }
-dontwarn com.parse.**
-dontwarn com.squareup.picasso.**
-keepclasseswithmembernames class * {
native <methods>;
}
fastJson
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.**{*; }
EventBus
# EventBus2
-keepclassmembers class ** {
public void onEvent*(***);
}
# Only required if you use AsyncExecutor
-keepclassmembers class * extends de.greenrobot.event.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
#EventBus3
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
阿里雲推送
-keepclasseswithmembernames class ** {
native <methods>;
}
-keepattributes Signature
-keep class sun.misc.Unsafe { *; }
-keep class com.taobao.** {*;}
-keep class com.alibaba.** {*;}
-keep class com.alipay.** {*;}
-keep class com.ut.** {*;}
-keep class com.ta.** {*;}
-keep class anet.**{*;}
-keep class anetwork.**{*;}
-keep class org.android.spdy.**{*;}
-keep class org.android.agoo.**{*;}
-keep class android.os.**{*;}
-keep class org.json.**{*;}
-dontwarn com.taobao.**
-dontwarn com.alibaba.**
-dontwarn com.alipay.**
-dontwarn anet.**
-dontwarn org.android.spdy.**
-dontwarn org.android.agoo.**
-dontwarn anetwork.**
-dontwarn com.ut.*
-dontwarn com.ta.**
以上只是列舉了一些Android系統和第三方庫常用的混淆配置方式,項目中需要按照實際需求進行配置。