在Android開(kāi)發(fā)中,為了使App盡可能小,可以使用R8來(lái)壓縮,混淆,優(yōu)化App,當(dāng)使用Android Gradle插件3.4.0或更高版本時(shí),插件不再使用ProGuard執(zhí)行優(yōu)化而是R8。
R8的功能
- 代碼壓縮:安全地從App及其庫(kù)依賴項(xiàng)中刪除未使用的類,字段,方法和屬性。
- 資源壓縮:從打包的App中刪除未使用的資源,包括應(yīng)用程序庫(kù)依賴項(xiàng)中未使用的資源。它與代碼壓縮一起使用,這樣一旦刪除了未使用的代碼,也可以安全地刪除不再引用的資源。
- 代碼混淆:使用簡(jiǎn)短無(wú)意義的名稱重命名代碼里的類,字段和方法,從而減少DEX文件大小。
- 代碼優(yōu)化:刪除未使用的代碼或重寫(xiě)代碼使其更簡(jiǎn)潔。
R8 和 Proguard
R8和Proguard 相比,R8 可以更快地縮減代碼,同時(shí)改善輸出大小,R8 默認(rèn)處于啟用狀態(tài),你可將以下代碼添加到項(xiàng)目的 gradle.properties 文件以停用 R8:
android.enableR8=false
R8 普通模式是兼容 Proguard的,R8 完全模式與會(huì)啟用一些額外的優(yōu)化,這個(gè)時(shí)候可能需要一些其它ProGuard規(guī)則以避免運(yùn)行時(shí)問(wèn)題,可以在 項(xiàng)目的gradle.properties 文件中設(shè)置以下內(nèi)容啟用完全模式。
android.enableR8.fullMode=true
啟用壓縮,混淆,優(yōu)化
使用Android Studio創(chuàng)建新項(xiàng)目時(shí),默認(rèn)情況下不啟用壓縮,混淆,優(yōu)化,因?yàn)檫@會(huì)增加項(xiàng)目的構(gòu)建時(shí)間,而且有些代碼混淆后會(huì)出錯(cuò)。要想啟用這些功能,需要在項(xiàng)目的build.gradle包含下面的內(nèi)容。
getDefaultProguardFile('proguard-android.txt') 方法可從 Android SDK tools/proguard/ 文件夾獲取默認(rèn)的 ProGuard規(guī)則文件。
proguard-rules.pro文件用于添加自定義 ProGuard 規(guī)則。默認(rèn)情況下,該文件位于模塊根目錄。
android {
buildTypes {
release {
//啟用代碼壓縮,混淆,優(yōu)化
minifyEnabled true
//啟用資源壓縮
shrinkResources true
//ProGuard規(guī)則文件
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
...
}
自定義要保留的資源
如果您有想要保留或舍棄的特定資源,請(qǐng)?jiān)谀捻?xiàng)目中創(chuàng)建一個(gè)包含 <resources> 標(biāo)記的 XML 文件,并在 tools:keep 屬性中指定每個(gè)要保留的資源,在 tools:discard 屬性中指定每個(gè)要舍棄的資源。這兩個(gè)屬性都接受逗號(hào)分隔的資源名稱列表。您可以使用星號(hào)字符作為通配符。
<?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" />
嚴(yán)格壓縮模式
如果你的代碼或庫(kù)代碼(例如AppCompat)調(diào)用了Resources.getIdentifier(),這就表示你的代碼將根據(jù)動(dòng)態(tài)生成的字符串查詢資源名稱,默認(rèn)情況下資源壓縮器會(huì)采取防御性行為,將所有具有匹配名稱格式的資源標(biāo)記為可能已使用,無(wú)法移除。例如,以下代碼會(huì)使所有帶 img_ 前綴的資源標(biāo)記為已使用。
String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
在 keep.xml 文件中將 shrinkMode 設(shè)置為 strict可以停用該防御性行為,這個(gè)時(shí)候你必須用tools:keep 屬性手動(dòng)保留這些資源。
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
合并重復(fù)資源
默認(rèn)情況下,Gradle 還會(huì)合并同名資源,例如可能位于不同資源文件夾中的同名可繪制對(duì)象。這一行為不受 shrinkResources 屬性控制,也無(wú)法停用,因?yàn)樵谟卸鄠€(gè)資源匹配代碼查詢的名稱時(shí),有必要利用這一行為來(lái)避免錯(cuò)誤。
只有在兩個(gè)或更多個(gè)文件具有完全相同的資源名稱、類型和限定符時(shí),才會(huì)進(jìn)行資源合并。
自定義要保持的代碼
在ProGuard規(guī)則文件中可以使用-keep保持特定代碼不被移除或混淆,或者向你想保持的代碼添加 @Keep 注解,在類上添加 @Keep 可原樣保持整個(gè)類,在方法或字段上添加它可完整保持方法/字段(及其名稱)以及類名稱。
-keep public class MyClass
必須保持的代碼
- AndroidManifest.xml引用的類。
- JNI調(diào)用的方法。
- 反射用到的類。
- WebView中JavaScript使用的類。
- Layout文件引用的自定義View。
常用ProGuard規(guī)則
關(guān)閉壓縮
-dontshrink
關(guān)閉代碼優(yōu)化,默認(rèn)Proguard規(guī)則文件已包含
-dontoptimize
關(guān)閉混淆
-dontobfuscate
指定代碼優(yōu)化級(jí)別,值在0-7之間,默認(rèn)為5
-optimizationpasses 5
混淆時(shí)不使用大小寫(xiě)混合類名,默認(rèn)Proguard規(guī)則文件已包含
-dontusemixedcaseclassnames
不忽略庫(kù)中的非public的類,默認(rèn)Proguard規(guī)則文件已包含
-dontskipnonpubliclibraryclasses
不忽略庫(kù)中的非public的類成員
-dontskipnonpubliclibraryclassmembers
輸出詳細(xì)信息,默認(rèn)Proguard規(guī)則文件已包含
-verbose
不做預(yù)校驗(yàn),預(yù)校驗(yàn)是作用在Java平臺(tái)上的,Android平臺(tái)上不需要這項(xiàng)功能,去掉之后還可以加快混淆速度,默認(rèn)Proguard規(guī)則文件已包含
-dontpreverify
保持指定包下的類名,不包括子包下的類名
-keep class com.xy.myapp*
保持指定包下的類名,包括子包下的類名
-keep class com.xy.myapp**
保持指定包下的類名以及類里面的內(nèi)容
-keep class com.xy.myapp.* {*;}
保持所有繼承于指定類的類
-keep public class * extends android.app.Activity
其它keep方法:
| 保留 | 防止被移除或者被混淆 | 防止被混淆 |
|---|---|---|
| 類和類成員 | -keep | -keepnames |
| 僅類成員 | -keepclassmembers | -keepclassmembernames |
| 如果擁有某成員,保留類和類成員 | -keepclasseswithmembers | -keepclasseswithmembernames |
如果我們要保留一個(gè)類中的內(nèi)部類不被混淆則需要用$符號(hào),如下例子表示保持MyClass內(nèi)部類JavaScriptInterface中的所有public內(nèi)容。
-keepclassmembers class com.xy.myapp.MyClass$JavaScriptInterface {
public *;
}
保持指定類的所有方法
-keep class com.xy.myapp.MyClass {
public <methods>;
}
保持指定類的所有字段
-keep class com.xy.myapp.MyClass {
public <fields>;
}
保持指定類的所有構(gòu)造器
-keep class com.xy.myapp.MyClass {
public <init>;
}
保持用指定參數(shù)作為形參的方法
-keep class com.xy.myapp.MyClass {
public <methods>(java.lang.String);
}
類文件除了定義類,字段,方法外,還為它們附加了一些屬性,例如注解,異常,行號(hào)等,優(yōu)化操作會(huì)刪除不必要的屬性,使用-keepattributes可以保留指定的屬性
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
使指定的類不輸出警告信息
-dontwarn com.squareup.okhttp.**
特殊ProGuard規(guī)則
由于enum類的特殊性,下面兩個(gè)方法會(huì)被反射調(diào)用,默認(rèn)Proguard規(guī)則文件已經(jīng)處理。
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
Parcelable的子類和Creator靜態(tài)成員變量要保持,否則會(huì)產(chǎn)生Android.os.BadParcelableException異常,默認(rèn)Proguard規(guī)則文件已經(jīng)處理。
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
常用混淆模板
# 指定代碼的壓縮級(jí)別
-optimizationpasses 5
# 不忽略庫(kù)中的非public的類成員
-dontskipnonpubliclibraryclassmembers
# google推薦算法
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 避免混淆Annotation、內(nèi)部類、泛型、匿名類
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
# 拋出異常時(shí)保留代碼行號(hào)
-keepattributes SourceFile,LineNumberTable
# 保持四大組件
-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
-keep public class com.android.vending.licensing.ILicensingService
# 保持support下的所有類及其內(nèi)部類
-keep class android.support.** {*;}
# 保留繼承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
# 保持自定義控件
-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);
}
# 保持所有實(shí)現(xiàn) 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();
}
# webView處理
-keepclassmembers class fqcn.of.javascript.interface.for.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, jav.lang.String);
}
輸出文件
啟用R8或ProGuard構(gòu)建項(xiàng)目后會(huì)在模塊下的build\outputs\mapping\release文件夾下輸出下列文件:
- dump.txt:說(shuō)明 APK 中所有類文件的內(nèi)部結(jié)構(gòu)。
- mapping.txt:提供原始與混淆過(guò)的類、方法和字段名稱之間的轉(zhuǎn)換。
- seeds.txt:列出未進(jìn)行混淆的類和成員。
- usage.txt:列出從 APK 移除的代碼。