做者 / Google 软件工程师 SørenGjesse 和 Christoffer Adamsenjava
人们更倾向于安装并保留较小和安装占用空间更小的应用,在新兴市场中尤其明显。有了 R8 编译器,您能够经过压缩、混淆和优化,更全面的缩小应用体积。android
本文咱们将对 R8 的特性进行一个简要的介绍,并介绍可预期的代码缩减程度以及如何在 R8 中启用这些功能。ios
R8 经过下面 4 项特性来减小 Android 应用大小:git
开发应用时,全部代码都应有目的并在应用中实现相应功能。不过,大多数应用都会使用 Jetpack、OkHttp、Guava、Gson 和 Google Play 服务 等第三方库,而且用 Kotlin 编写的应用始终包含 Kotlin 标准库。当您使用这其中的某个第三方库时,您的应用中一般只使用其中很小一部分。若不压缩,全部库代码都会保留在您的应用中。github
您的代码大小也可能比实际须要的大,由于冗长的代码有时能够提升可读性和可维护性: 例如,您可能会尽可能使用有意义的变量名和建造者模式 (builder pattern) 来帮助其余人更容易检查和理解您的代码。可是这些模式会加大代码量。一般,您本身编写的代码有很大的压缩空间。算法
要在 release build 上启用 R8 压缩,须要在应用的主 build.gradle 文件中将 minifyEnable 属性设置为 true,以下所示:编程
android { buildTypes { release { minifyEnabled true } } }
别被 minifyEnable 这个名字所迷惑,它会启用 R8 的代码缩减功能。api
R8 能够大大减少应用的大小。例如,去年的 Google I/O 应用大小为 18.55 MB,压缩前包含 150,220 个方法和 3 个 DEX 文件。压缩后,应用大小缩小到 6.45 MB,包含 45,831 个方法和 1 个 DEX 文件。R8 缩减了 65% 的 DEX 文件大小 (测量数据来自 Android Studio 3.5.1 和 IOSched 示例应用)。app
为简单起见,咱们写了一个基于 Java 编程语言的程序做为参考:jvm
class com.example.JavaHelloWorld { private void unused() { System.out.println("Unused"); } private static void greeting() { System.out.println("Hello, world!"); } public static void main(String[] args) { greeting(); } }
程序的入口是 static void main 方法,咱们使用如下 keep 规则 指定该方法:
-keep class com.example.JavaHelloWorld { public static void main(java.lang.String[]); }
R8 缩减算法的运做方式以下:
class com.example.JavaHelloWorld { private static void a() { System.out.println("Hello, world!"); } public static void main(String[] args) { a(); } }
class com.example.JavaHelloWorld { public static void main(String[] args) { System.out.println("Hello, world!"); } }
如您所见,处理后的代码比原始代码短得多。
正如独立的 Java 程序同样,Android 应用有许多常见的入口点: Activity (活动)
,Service (服务)
,Content Provider (内容提供者)
和 Broadcast Receiver (广播接收者)
。aapt2
工具经过基于 Android Manifest
文件生成 keep 规则来为您处理这些入口点。
除了这些熟知的入口点,Android 应用还须要其余标准的 keep 规则。这些规则由 Android Gradle 插件提供,您能够在配置构建时指定该默认配置文件:
android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt') } } }
反射 (Reflection) 会致使 R8 在跟踪代码时没法识别到代码的入口点。第三方库也可能用到反射,而且因为第三方库其实是您的应用的一部分,您 (做为应用开发者) 将负责这些库以及您本身的代码中使用的反射。第三方库可能附带了它们本身的规则,可是切记,有些库不必定是为 Android 编写的,抑或是未考虑缩减问题,所以它们可能须要其余配置。
以一个 Kotlin 类为例,该类具备一个名为 name 的字段和一个 main 方法,该方法建立一个实例并将该实例序列化为 JSON:
class Person(val name: String) fun printJson() { val gson = Gson() val person = Person("Søren Gjesse") println(gson.toJson(person)) }
缩减代码后,运行程序将输出一个空的 JSON 对象 {}。这是由于 R8 仅将字段名视为写入 (在 Person 构造函数中),但从未读取,所以 R8 会将其移除。最后 Person 丢失了字段值,形成空的 JSON 对象。可是,该字段由 Gson 序列化读取,而 Gson 使用反射的方式来执行此操做,所以 R8 没法看到此字段已被读取。
要保留名称字段,请在您的 proguard-rules.pro 文件中添加一个保留规则 -keep:
-keep class com.example.myapplication.Person { public java.lang.String name; }
此规则告诉 R8 不要处理 Person 类中的 name 的字段。将其放置在适当位置后,运行代码便可获得预期的 JSON 对象 {"name": "SørenGjesse"}
。
最后,在配置项目时,请确保将 proguard-rules.pro
文件添加到 build.gradle
配置中:
android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }
有兴趣更深刻了解 R8 压缩器如何运做吗?请参考 R8 开发者文档 了解更多!