5

使用 R8 压缩您的应用

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzAwODY4OTk2Mg%3D%3D&%3Bmid=2652075647&%3Bidx=1&%3Bsn=a6b4c50e0e9c30b3e290dbba3e6b8eca
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ZNfaUjj.png!mobile

作者 / Google 软件工程师 SørenGjesse 和 Christoffer Adamsen

人们更倾向于安装并保留较小和安装占用空间更小的应用,在新兴市场中尤为明显。有了 R8 编译器,您可以通过压缩、混淆和优化,更全面的缩小应用体积。

本文我们将对 R8 的特性进行一个简要的介绍,并介绍可预期的代码缩减程度以及如何在 R8 中启用这些功能。

R8 的压缩特性

R8 通过下面 4 项特性来减少 Android 应用大小:

  • 摇树优化 (Tree shaking): 使用静态代码分析来查找和删除无法访问的代码和未实例化的类型;

  • 优化 : 通过删除无效代码,选择性内联,移除未使用的参数和类合并来优化代码大小;

  • 重命名标识,即混淆处理 : 使用短名称以及缩短包命名空间;

  • 减少调试信息 : 规范化调试信息并压缩行号信息。

为什么需要 R8 压缩

开发应用时,所有代码都应有目的并在应用中实现相应功能。不过,大多数应用都会使用 Jetpack OkHttp Guava Gson Google Play 服务 等第三方库,并且用 Kotlin 编写的应用始终包含 Kotlin 标准库 。当您使用这其中的某个第三方库时,您的应用中通常只使用其中很小一部分。若不压缩,所有库代码都会保留在您的应用中。

  • Jetpack

    https://developer.android.google.cn/jetpack

  • OkHttp

    https://square.github.io/okhttp/

  • Guava

    https://github.com/google/guava

  • Gson

    https://github.com/google/gson

  • Google Play 服务

    https://developers.google.cn/android/guides/overview

  • Kotlin 标准库

    https://kotlinlang.org/api/latest/jvm/stdlib/

您的代码大小也可能比实际需要的大,因为冗长的代码有时可以提高可读性和可维护性: 例如,您可能会尽量使用有意义的变量名和建造者模式 (builder pattern) 来帮助其他人更容易检查和理解您的代码。但是这些模式会加大代码量。通常,您自己编写的代码有很大的压缩空间。

启用 R8 来压缩您的应用

要在 release build 上启用 R8 压缩,需要在应用的主 build.gradle 文件中将 minifyEnable 属性设置为 true,如下所示:

android {
buildTypes {
release {
minifyEnabled true
}
}
}

别被 minifyEnable 这个名字所迷惑,它会启用 R8 的代码缩减功能。

R8 能缩减多少应用大小?

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 示例应用 )。

  • IOSched 示例应用

    https://github.com/google/iosched

基本压缩算法

为简单起见,我们写了一个基于 Java 编程语言的程序作为参考:

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 规则

    https://developer.android.google.cn/studio/build/shrink-code#configuration-files

-keep class com.example.JavaHelloWorld {
public static void main(java.lang.String[]);
}

R8 缩减算法的运作方式如下:

  • 首先,它从程序常见的入口点跟踪所有可访问的代码。这些入口点由 R8 keep 规则定义。例如,在此 Java 代码示例中,R8 会在 main 方法处开始运行。

  • 在该示例中,R8 从 main 方法跟踪到 greeting 方法。greeting 方法是在运行时被调用的,因此跟踪在此处停止。

  • 跟踪完成后,R8 使用摇树优化来删除未使用的代码。在此示例中,摇树删除了未使用的方法,因为 R8 的跟踪过程检测到从任何已知的入口都无法到达该方法。

  • 接下来,R8 将标识重命名为较短的名称,这些名称在 DEX 文件中占用较少的空间。在示例中,R8 可能会将 greeting 方法重命名为短名称 a :

class com.example.JavaHelloWorld {


private static void a() {
System.out.println("Hello, world!");
}


public static void main(String[] args) {
a();
}
}
  • 最后,应用代码优化。缩减代码大小的内联是其一。在此示例中,将方法 a 的主体直接迁移到 main 中,代码会显得更简洁:

class com.example.JavaHelloWorld {


public static void main(String[] args) {
System.out.println("Hello, world!");
}
}

如您所见,处理后的代码比原始代码短得多。

使用 R8 压缩应用前的准备工作

正如独立的 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:

  • proguard-rules.pro

    https://developer.android.google.cn/studio/build/shrink-code#add-configuration

-keep class com.example.myapplication.Person {
public java.lang.String name;
}

此规则告诉 R8 不要处理 Person 类中的 name 的字段。将其放置在适当位置后,运行代码即可得到预期的 JSON 对象 {"name": "SørenGjesse"}

最后,在配置项目时,请确保将 proguard-rules.p ro 文件添加到 build.gradle 配置中:

android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
'proguard-rules.pro'
}
}
}

了解更多

有兴趣更深入了解 R8 压缩器如何运作吗?请参考 R8 开发者文档 了解更多!

  • R8 开发者文档

    https://developer.android.google.cn/studio/build/shrink-code

umuauan.png!mobile

推荐阅读

6biYzun.png!mobile

EnUZNbA.png!mobile

AbemY3f.png!mobile

点击屏末    |   查看 Android 开发者文档《缩减、混淆处理和优化您的应用》

3IF7nu7.gif!mobile

v2QBZvN.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK