Android代碼的縮減、混淆和優(yōu)化
為什么把這三個內(nèi)容放到一起說?因為在Android Gradle中配置方法基本是在一起的。
官方說明如下:
為了盡可能減小應(yīng)用的大小,您應(yīng)在發(fā)布 build 中啟用縮減功能來移除不使用的代碼和資源喊崖。啟用縮減功能后,您還會受益于兩項功能雇逞,一項是混淆處理功能荤懂,該功能會縮短應(yīng)用的類和成員的名稱;另一項是優(yōu)化功能塘砸,該功能會采用更積極的策略來進(jìn)一步減小應(yīng)用的大小节仿。本頁介紹 R8 如何為項目執(zhí)行這些編譯時任務(wù),以及您如何對這些任務(wù)進(jìn)行自定義掉蔬。
當(dāng)您使用 Android Gradle 插件 3.4.0 或更高版本構(gòu)建項目時粟耻,該插件不再使用 ProGuard 執(zhí)行編譯時代碼優(yōu)化查近,而是與 R8 編譯器協(xié)同工作,處理以下編譯時任務(wù):
- 代碼縮減(即搖樹優(yōu)化):從應(yīng)用及其庫依賴項中檢測并安全地移除不使用的類挤忙、字段霜威、方法和屬性(這使其成為了一個對于規(guī)避 64k 引用限制非常有用的工具)。例如册烈,如果您僅使用某個庫依賴項的少數(shù)幾個 API戈泼,那么縮減功能可以識別應(yīng)用不使用的庫代碼并僅從應(yīng)用中移除這部分代碼。如需了解詳情赏僧,請轉(zhuǎn)到介紹如何縮減代碼的部分大猛。
- 資源縮減:從封裝應(yīng)用中移除不使用的資源,包括應(yīng)用庫依賴項中不使用的資源淀零。此功能可與代碼縮減功能結(jié)合使用挽绩,這樣一來,移除不使用的代碼后驾中,也可以安全地移除不再引用的所有資源唉堪。如需了解詳情,請轉(zhuǎn)到介紹如何縮減資源的部分肩民。
- 混淆:縮短類和成員的名稱唠亚,從而減小 DEX 文件的大小。如需了解詳情持痰,請轉(zhuǎn)到介紹如何對代碼進(jìn)行混淆處理的部分灶搜。
- 優(yōu)化:檢查并重寫代碼,以進(jìn)一步減小應(yīng)用的 DEX 文件的大小工窍。例如割卖,如果 R8 檢測到從未采用過給定 if/else 語句的
else {}
分支,則會移除else {}
分支的代碼患雏。如需了解詳情鹏溯,請轉(zhuǎn)到介紹代碼優(yōu)化的部分。默認(rèn)情況下纵苛,在構(gòu)建應(yīng)用的發(fā)布版本時剿涮,R8 會自動執(zhí)行上述編譯時任務(wù)言津。不過攻人,您也可以停用某些任務(wù)或通過 ProGuard 規(guī)則文件自定義 R8 的行為。事實上悬槽,R8 支持所有現(xiàn)有 ProGuard 規(guī)則文件怀吻,因此您在更新 Android Gradle 插件以使用 R8 時,無需更改現(xiàn)有規(guī)則初婆。
開啟混淆功能
上面沒有提到的一個代碼混淆的重要作用:我們知道apk文件是相對容易被反編譯的蓬坡,未加混淆的apk猿棉,反編譯后基本裸奔。而混淆的apk即使被反編譯屑咳,類名與變量名都會處理成無意義的字符萨赁,很大程度上降低了源碼的可讀性。
以下是在Android Studio中開啟混淆的方法和常用的配置兆龙。Android Studio的Gradle混淆功能默認(rèn)是關(guān)閉的杖爽,我們需要手動在module的build.gradle中打開。
- 在Module的build.gradle中添加如下配置:
android{
...
buildTypes {
release {
// 開啟混淆
minifyEnabled true
// 開啟資源壓縮紫皇,編譯時會自動刪除未使用到的res資源
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
//開啟混淆會導(dǎo)致編譯速度變慢慰安,debug通常不開啟
minifyEnabled false
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
之后我們需要在proguard-rules.pro文件中編輯混淆規(guī)則。
proguard手冊
輸入/輸出配置項
-
@filename
等同于:-include filename聪铺,filename 文件名化焕。
-
-include [filename]
從給定文件中讀取配置項,filename 文件名铃剔。
-
-basedirectory [directoryname]
為后續(xù)的相對文件指定基礎(chǔ)目錄撒桨,directoryname 目錄名。
injars [classPath]
指定要處理的輸入jar(等壓縮包)番宁,包中的class文件會被處理后輸出到outjars的指定路徑下元莫,非class文件直接輸出到outjars指定路徑,classpath jar包輸入路徑蝶押。outjars [classPath]
指定輸出路徑踱蠢,接收injars的文件輸出,classPath 輸出路徑棋电。libraryjars [classPath]
指定不被混淆的jar茎截,classPath 文件路徑。
壓縮配置項
-
-dontshrink
不進(jìn)行代碼壓縮赶盔,默認(rèn)會進(jìn)行代碼壓縮企锌,打開此配置項則不進(jìn)行壓縮。
-
-printusage [fileName]
將被壓縮的代碼輸出到指定路徑于未。
優(yōu)化配置項
-
-dontoptimize
指定不進(jìn)行代碼優(yōu)化撕攒,默認(rèn)情況下ProGuard會優(yōu)化所有代碼。
-
-optimizations [optimization_filter]
在更細(xì)粒度的級別指定要啟用和禁用的優(yōu)化烘浦,篇幅有限不展開講了抖坪,具體可以參考:Configuration - Optimizations
常用的配置方式:
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-
-optimizationpasses n
指定代碼迭代優(yōu)化的次數(shù),默認(rèn)執(zhí)行一遍闷叉。
混淆配置
-
-dontobfuscate
不進(jìn)行混淆擦俐,默認(rèn)會進(jìn)行代碼混淆。
-
-printmapping [filename]
將混淆前后映射表輸出到指定文件握侧。
-
-dontusemixedcaseclassnames
混淆時不使用大小寫混合蚯瞧,混淆后類名為小寫嘿期。
-
-keepattributes [attribute_filter]
指定要保留的屬性,如:
-keepattributes Exceptions,InnerClasses
,具體可參考:Optional attributes
通用配置項
-
-verbose
指定在處理期間輸出更多信息埋合。如果程序因異常終止备徐,此選項將打印出整個堆棧跟蹤,而不僅僅是異常消息甚颂。
-
-dontnote [class_filter]
指定不打印匹配類的相關(guān)異常信息坦喘,class_filter為正則表達(dá)式,符合表達(dá)式的類不打印信息西设。
-
-dontwarn [class_filter]
指定相關(guān)類不發(fā)出警告瓣铣,class_filter為正則表達(dá)式。如:
-dontwarn androidx.**
-
-dump [filename]
將所有class的內(nèi)部結(jié)構(gòu)輸出到指定文件贷揽。如:
-dump proguard/class_files.txt
keep配置項
-
-keep []
指定要保留的類和類成員棠笑。
//View子類,類名不混淆 - keep public class * extends android.view.View{ //set開頭的方法不混淆 void set*(***); //構(gòu)造函數(shù)不混淆 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
想要類名和類成員全部不混淆應(yīng)該使用:
-keep public class * extends android.app.Activity{*;}
-
-keepclassmembers
保留指定的類成員。
//指定不混淆的類成員印屁,類名會混淆 -keepclassmembers class * { //事件監(jiān)聽類(參數(shù)符合**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 } }
當(dāng)使用
-keep
時:- keep public class * extends android.view.View{ //構(gòu)造函數(shù)不混淆 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{ //構(gòu)造函數(shù)不混淆 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(***)
條件。當(dāng)keepclasseswithmembers
指定的類和類成員全部存在的情況下旗吁,才會匹配該類(MyClass不存在getListener()
方法踩萎,所以不能被匹配)。 -
-keepnames/-keepclassmembernames和-keepclasseswithmembernames
選項 等同于 作用 -keepnames ** -keep,allowshrinking ** 指定要保留的類和類成員,允許代碼壓縮優(yōu)化很钓,成員可能在壓縮階段被刪除 -keepclassmembernames ** - keepclassmembers,allowshrinking ** 指定要保留的類成員香府,允許代碼壓縮優(yōu)化,成員可能在壓縮階段被刪除 -keepclasseswithmembernames ** -keepclasseswithmember,allowshrinking ** 在所有類成員都存在的情況下码倦,指定要保留的類和類成員,允許代碼壓縮優(yōu)化企孩,成員可能在壓縮階段被刪除 簡單來說使用
-keep/-keepclassmember和-keepclasseswithmember
可能會阻止成員被壓縮優(yōu)化,而使用-keepnames/-keepclassmembernames和-keepclasseswithmembernames
而不會叹洲。
Keep配置項修飾符
可用用來修飾-keep/-keepclassmember和-keepclasseswithmember
柠硕,使用方式:
-keep[,modifiter,...] class_specification
-keep,allowshrinking public class * extends android.view.View{*;}
-
allowshrinking
上面提到過了工禾,允許對象進(jìn)行代碼壓縮运提,對象可能在壓縮階段被刪除蝗柔。
-
allowoptimization
指定的對象可能會被改變(優(yōu)化步驟),但可能不會被混淆或者刪除民泵。
-
allowobfuscation
指定對象可能會被重命名癣丧,但是不會被刪除和優(yōu)化。
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常用配置規(guī)則
常用文件配置如下:
1.常用配置
-optimizationpasses 5
# 混淆時不使用大小寫混合粪滤,混淆后的類名為小寫
-dontusemixedcaseclassnames
# 指定不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses
# 指定不去忽略非公共庫的成員
-dontskipnonpubliclibraryclassmembers
# 混淆時不做預(yù)校驗
-dontpreverify
# 混淆時不記錄日志
-verbose
# 代碼優(yōu)化
-dontshrink
# 不優(yōu)化輸入的類文件
-dontoptimize
# 保留注解不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
# 保留代碼行號,方便異常信息的追蹤
-keepattributes SourceFile,LineNumberTable
# 混淆采用的算法
-optimizations !code/simplification/cast,!field/*,!class/merging/*
# dump.txt文件列出apk包內(nèi)所有class的內(nèi)部結(jié)構(gòu)
-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 系統(tǒng)類
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和構(gòu)造
# 自定義控件
-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構(gòu)造方法
-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系統(tǒng)和第三方庫常用的混淆配置方式杖小,項目中需要按照實際需求進(jìn)行配置。
最后
在技術(shù)領(lǐng)域內(nèi)愚墓,沒有任何一門課程可以讓你學(xué)完后一勞永逸窍侧,再好的課程也只能是“師傅領(lǐng)進(jìn)門,修行靠個人”转绷∥凹“學(xué)無止境”這句話,在任何技術(shù)領(lǐng)域议经,都不只是良好的習(xí)慣斧账,更是程序員和工程師們不被時代淘汰、獲得更好機會和發(fā)展的必要前提煞肾。