在Android开发中,为了使App尽可能小,可以使用R8来压缩,混淆,优化App,当使用Android Gradle插件3.4.0或更高版本时,插件不再使用ProGuard执行优化而是R8。
R8的功能
- 代码压缩:安全地从App及其库依赖项中删除未使用的类,字段,方法和属性。
- 资源压缩:从打包的App中删除未使用的资源,包括应用程序库依赖项中未使用的资源。它与代码压缩一起使用,这样一旦删除了未使用的代码,也可以安全地删除不再引用的资源。
- 代码混淆:使用简短无意义的名称重命名代码里的类,字段和方法,从而减少DEX文件大小。
- 代码优化:删除未使用的代码或重写代码使其更简洁。
R8 和 Proguard
R8和Proguard 相比,R8 可以更快地缩减代码,同时改善输出大小,R8 默认处于启用状态,你可将以下代码添加到项目的 gradle.properties 文件以停用 R8:
android.enableR8=false
R8 普通模式是兼容 Proguard的,R8 完全模式与会启用一些额外的优化,这个时候可能需要一些其它ProGuard规则以避免运行时问题,可以在 项目的gradle.properties 文件中设置以下内容启用完全模式。
android.enableR8.fullMode=true
启用压缩,混淆,优化
使用Android Studio创建新项目时,默认情况下不启用压缩,混淆,优化,因为这会增加项目的构建时间,而且有些代码混淆后会出错。要想启用这些功能,需要在项目的build.gradle包含下面的内容。
getDefaultProguardFile('proguard-android.txt') 方法可从 Android SDK tools/proguard/ 文件夹获取默认的 ProGuard规则文件。
proguard-rules.pro文件用于添加自定义 ProGuard 规则。默认情况下,该文件位于模块根目录。
android {
buildTypes {
release {
//启用代码压缩,混淆,优化
minifyEnabled true
//启用资源压缩
shrinkResources true
//ProGuard规则文件
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
...
}
自定义要保留的资源
如果您有想要保留或舍弃的特定资源,请在您的项目中创建一个包含 <resources> 标记的 XML 文件,并在 tools:keep 属性中指定每个要保留的资源,在 tools:discard 属性中指定每个要舍弃的资源。这两个属性都接受逗号分隔的资源名称列表。您可以使用星号字符作为通配符。
<?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" />
严格压缩模式
如果你的代码或库代码(例如AppCompat)调用了Resources.getIdentifier(),这就表示你的代码将根据动态生成的字符串查询资源名称,默认情况下资源压缩器会采取防御性行为,将所有具有匹配名称格式的资源标记为可能已使用,无法移除。例如,以下代码会使所有带 img_ 前缀的资源标记为已使用。
String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
在 keep.xml 文件中将 shrinkMode 设置为 strict可以停用该防御性行为,这个时候你必须用tools:keep 属性手动保留这些资源。
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
合并重复资源
默认情况下,Gradle 还会合并同名资源,例如可能位于不同资源文件夹中的同名可绘制对象。这一行为不受 shrinkResources 属性控制,也无法停用,因为在有多个资源匹配代码查询的名称时,有必要利用这一行为来避免错误。
只有在两个或更多个文件具有完全相同的资源名称、类型和限定符时,才会进行资源合并。
自定义要保持的代码
在ProGuard规则文件中可以使用-keep保持特定代码不被移除或混淆,或者向你想保持的代码添加 @Keep 注解,在类上添加 @Keep 可原样保持整个类,在方法或字段上添加它可完整保持方法/字段(及其名称)以及类名称。
-keep public class MyClass
必须保持的代码
- AndroidManifest.xml引用的类。
- JNI调用的方法。
- 反射用到的类。
- WebView中JavaScript使用的类。
- Layout文件引用的自定义View。
常用ProGuard规则
关闭压缩
-dontshrink
关闭代码优化,默认Proguard规则文件已包含
-dontoptimize
关闭混淆
-dontobfuscate
指定代码优化级别,值在0-7之间,默认为5
-optimizationpasses 5
混淆时不使用大小写混合类名,默认Proguard规则文件已包含
-dontusemixedcaseclassnames
不忽略库中的非public的类,默认Proguard规则文件已包含
-dontskipnonpubliclibraryclasses
不忽略库中的非public的类成员
-dontskipnonpubliclibraryclassmembers
输出详细信息,默认Proguard规则文件已包含
-verbose
不做预校验,预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度,默认Proguard规则文件已包含
-dontpreverify
保持指定包下的类名,不包括子包下的类名
-keep class com.xy.myapp*
保持指定包下的类名,包括子包下的类名
-keep class com.xy.myapp**
保持指定包下的类名以及类里面的内容
-keep class com.xy.myapp.* {*;}
保持所有继承于指定类的类
-keep public class * extends android.app.Activity
其它keep方法:
保留 | 防止被移除或者被混淆 | 防止被混淆 |
---|---|---|
类和类成员 | -keep | -keepnames |
仅类成员 | -keepclassmembers | -keepclassmembernames |
如果拥有某成员,保留类和类成员 | -keepclasseswithmembers | -keepclasseswithmembernames |
如果我们要保留一个类中的内部类不被混淆则需要用$符号,如下例子表示保持MyClass内部类JavaScriptInterface中的所有public内容。
-keepclassmembers class com.xy.myapp.MyClass$JavaScriptInterface {
public *;
}
保持指定类的所有方法
-keep class com.xy.myapp.MyClass {
public <methods>;
}
保持指定类的所有字段
-keep class com.xy.myapp.MyClass {
public <fields>;
}
保持指定类的所有构造器
-keep class com.xy.myapp.MyClass {
public <init>;
}
保持用指定参数作为形参的方法
-keep class com.xy.myapp.MyClass {
public <methods>(java.lang.String);
}
类文件除了定义类,字段,方法外,还为它们附加了一些属性,例如注解,异常,行号等,优化操作会删除不必要的属性,使用-keepattributes可以保留指定的属性
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
使指定的类不输出警告信息
-dontwarn com.squareup.okhttp.**
特殊ProGuard规则
由于enum类的特殊性,下面两个方法会被反射调用,默认Proguard规则文件已经处理。
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
Parcelable的子类和Creator静态成员变量要保持,否则会产生Android.os.BadParcelableException异常,默认Proguard规则文件已经处理。
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
常用混淆模板
# 指定代码的压缩级别
-optimizationpasses 5
# 不忽略库中的非public的类成员
-dontskipnonpubliclibraryclassmembers
# google推荐算法
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
# 避免混淆Annotation、内部类、泛型、匿名类
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
# 抛出异常时保留代码行号
-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下的所有类及其内部类
-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);
}
# 保持所有实现 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构建项目后会在模块下的build\outputs\mapping\release文件夹下输出下列文件:
- dump.txt:说明 APK 中所有类文件的内部结构。
- mapping.txt:提供原始与混淆过的类、方法和字段名称之间的转换。
- seeds.txt:列出未进行混淆的类和成员。
- usage.txt:列出从 APK 移除的代码。