AndroidAndroid 性能优化

(十)Android 性能优化 Proguard / R8

2020-07-17  本文已影响0人  科技猿人

小酌鸡汤

古人学问无遗力,少壮工夫老始成。

本文来源《Android 性能优化 全家桶》

ProGuard 和 R8 的关系 ?

 当使用 Android Gradle 插件 3.4.0 或更高版本构建项目时,该插件不再使用 ProGuard 来执行编译时代码优化,而是与 R8 编译器协同工作来处理编译时任务 。

R8 编译器如何优化应用的 ?

现在,就一起实操体验 R8 ~

(1)启用缩减、混淆处理和优化功能:

 Android Studio默认是不开启R8。因此,在构建应用的最终版本(也就是在发布应用之前测试的版本)时,最好启用这些编译时任务。要启用缩减、混淆处理和优化功能,请在您的项目级 build.gradle 文件中添加以下代码:

android {
        buildTypes {
            release {
                //启用代码缩减、混淆处理和优化
                minifyEnabled true
                //启用资源文件缩减、优化
                shrinkResources true
                //默认混淆文件(无内容需要自己添加)
                proguardFiles getDefaultProguardFile(
                        'proguard-android-optimize.txt'),
                        'proguard-rules.pro'
            }
        }
        ...
    }
(2)R8 配置文件

 R8 使用 ProGuard 规则文件来修改其默认行为并更好地了解应用的结构,如充当应用代码入口点的类。虽然您可以修改其中一些规则文件,但某些规则可能由编译时工具(如 AAPT2)自动生成,或从应用的库依赖项继承而来。下表介绍了 R8 使用的 ProGuard 规则文件的来源:

来源 位置 说明
Android Studio <module-dir>/proguard-rules.pro  当您使用 Android Studio 创建新模块时,IDE 会在该模块的根目录中创建 proguard-rules.pro 文件。
 默认情况下,此文件不会应用任何规则。因此,请在此处添加您自己的 ProGuard 规则,如您的自定义保留规则。
Android Gradle 插件 由 Android Gradle 插件在编译时生成  Android Gradle 插件会生成 proguard-android-optimize.txt(其中包含了对大多数 Android 项目都有用的规则),并启用 @Keep* 注释。
 默认情况下,使用 Android Studio 创建新模块时,模块级 build.gradle 文件会将此规则文件纳入到您的发布版本中。
注意:虽然 Android Gradle 插件包含额外的预定义 ProGuard 规则文件,但建议您使用 proguard-android-optimize.txt。
库依赖项 AAR 库:<library-dir>/proguard.txt
JAR 库:<library-dir>/META-INF/proguard/
 如果某个 AAR 库是使用它自己的 ProGuard 规则文件发布的,并且您将该 AAR 库作为编译时依赖项纳入到项目中,则 R8 在编译项目时会自动应用其规则。
 如果 AAR 库需要某些保留规则才能正常运行,那么使用该库随附的规则文件将非常有用。 也就是说,库开发者已经为您执行了问题排查步骤。
 不过,请注意,由于 ProGuard 规则是累加的,因此 AAR 库依赖项包含的某些规则无法移除,并且可能会影响对应用其他部分的编译。例如,如果某个库包含停用代码优化的规则,该规则会针对整个项目停用优化。
Android 资产打包工具 2 (AAPT2) 使用 minifyEnabled true 构建项目后:module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt  AAPT2 会根据对应用清单中的类、布局及其他应用资源的引用来生成保留规则。例如,AAPT2 会为您在应用清单中注册为入口点的每个 Activity 添加一个保留规则。
自定义配置文件 默认情况下,当您使用 Android Studio 创建新模块时,IDE 会创建 <module-dir>/proguard-rules.pro,以便您添加自己的规则。  您可以添加其他配置,R8 会在编译时应用这些配置。

 如果将 minifyEnabled 属性设为 true,R8 会将来自上述所有可用来源的规则合并在一起。在排查 R8 问题 时需要谨记这一点,因为其他编译时依赖项(如库依赖项)可能会引入您不了解的 R8 行为变化。
 要输出 R8 在构建项目时应用的所有规则的完整报告,请将以下代码添加到模块的 proguard-rules.pro 文件中:

// You can specify any path and filename.
    -printconfiguration ~/tmp/full-r8-config.txt
(3)添加其他配置

 当您使用 Android Studio 创建新项目或模块时,IDE 会创建一个 <module-dir>/proguard-rules.pro 文件,以便您添加自己的规则。此外,您还可以通过将相应文件添加到模块的 build.gradle 文件中的 proguardFiles 属性,从其他文件添加额外的规则。

android {
        buildTypes {
            release {
                minifyEnabled true
                shrinkResources true
                proguardFiles getDefaultProguardFile(
                        'proguard-android-optimize.txt'),
                        'proguard-rules.pro', 'media-proguard-rules.pro', 'video-proguard-rules.pro'
            }
        }
        ...
    } 
(4)缩减代码
R8 会根据项目的组合保留规则构建一张图表来确定执行不到的代码
(5)自定义要保留的代码

 在大多数情况下,让要 R8 仅移除不使用的代码,使用默认的 ProGuard 规则文件 (proguard-android- optimize.txt) 就足够了。 不过,在某些情况下,R8 很难做出正确判断,因而可能会移除应用实际上需要的代码。下面列举了几个例子,说明了它在什么情况下可能会错误地移除代码:

 通过测试应用应该可以发现因错误移除代码而导致的错误,但您也可以通过生成已移除代码的报告来检查移除了哪些代码。
 要修复错误并强制 R8 保留某些代码,请在 ProGuard 规则文件中添加 -keep。例如:

-keep public class MyClass

 或者,您也可以为您要保留的代码添加 @Keep释。在类上添加 @Keep 可按原样保留整个类。在方法或字段上添加该注释,将使该方法/字段(及其名称)以及类名称保持不变。请注意,只有在使用 AndroidX 注释库且您添加 Android Gradle 插件随附的 ProGuard 规则文件时,此注释才可用。

(6)缩减资源

 资源缩减只有在与代码缩减配合使用时才能发挥作用。在代码缩减器移除所有不使用的代码后,资源缩减器便可确定应用仍要使用的资源,当您添加包含资源的代码库时尤其如此。您必须移除不使用的库代码,使库资源变为未引用资源,因而可由资源缩减器移除。

 shrinkResources true
(7)自定义要保留的资源

 如果有想要保留或舍弃的特定资源,请在项目中创建一个包含 <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*"
        //针对 productFlavors(多渠道打包)
        tools:discard="@layout/unused2" />

 将该文件保存在项目资源中,例如,保存在 res/raw/keep.xml。构建系统不会将此文件打包到 APK 中。

(8)启用严格引用检查

 通常,资源缩减器可以准确地判断是否使用了某个资源。不过,如果您的代码会调用 Resources.getIdentifier()(或者您的任何库会进行此调用,例如 AppCompat 库便会执行此调用),则意味着您的代码将根据动态生成的字符串查询资源名称。当您启用严格引用检查时,资源缩减器在默认情况下会采取保护性行为,将所有具有匹配名称格式的资源标记为可能已使用,无法移除。
 例如,以下代码会将所有带 img_ 前缀的资源标记为已使用。

  val name = String.format("img_%1d", angle + 1)
  val res = resources.getIdentifier(name, "drawable", packageName)

 这是默认启用的安全缩减模式的一些示例。 不过,您可以停用这种“防患于未然”的处理方式,指定资源缩减器只保留确定要使用的资源。 为此,您可以将 keep.xml 文件中的 shrinkMode 设置为 strict,如下所示:

<?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:shrinkMode="strict" />

 如果您确实启用了严格缩减模式,并且您的代码也通过动态生成的字符串引用资源(如上所示),则您必须使用 tools:keep 属性来手动保留这些资源。

(9)移除未使用的备用资源

 Gradle 资源缩减器只会移除未由您的应用代码引用的资源,这意味着,它不会移除用于不同设备配置的备用资源。如有必要,您可以使用 Android Gradle 插件的 resConfigs 属性来移除应用不需要的备用资源文件。
 以下代码段展示了如何设置只保留英语和法语的语言资源:

android {
        defaultConfig {
            ...
            resConfigs "en", "fr"
        }
    }
(10)合并重复资源

 默认情况下,Gradle 还会合并同名的资源,如可能位于不同资源文件夹中的同名可绘制对象。这一行为不受 shrinkResources 属性控制,也无法停用,因为当多个资源与代码查询的名称匹配时,有必要利用这一行为来避免错误。
 只有在两个或更多个文件具有完全相同的资源名称、类型和限定符时,才会进行资源合并。Gradle 会在重复项中选择它认为最合适的文件(根据下述优先顺序),并且只将这一个资源传递给 AAPT,以便在 APK 文件中分发。
 Gradle 会在以下位置查找重复资源:

 Gradle 会按以下级联优先顺序合并重复资源:
依赖项 → 主资源 → 版本变种 → 版本类型

 如果完全相同的资源出现在同一源集中,Gradle 无法合并它们,并且会发出资源合并错误。如果您在 build.gradle 文件的 sourceSet 属性中定义了多个源集,就可能会发生这种情况。例如,如果 src/main/res/ 和 src/main/res2/ 包含完全相同的资源,就可能会发生这种情况。

(11)对代码进行混淆处理

 混淆处理的目的是通过缩短应用的类、方法和字段的名称来减小应用的大小。

R8 对代码进行混淆处理

 混淆处理不会从应用中移除代码,但如果应用的 DEX 文件将许多类、方法和字段编入索引,那么混淆处理将可以显著缩减应用的大小。
 此外,如果您的代码依赖于应用的方法和类的可预测命名(例如,使用反射时),您应该将相应签名视为入口点并为其指定保留规则。这些保留规则会告知 R8 不仅要在应用的最终 DEX 中保留该代码,而且还要保留其原始命名。

(12)解码经过混淆处理的堆栈轨迹

 R8 对您的代码进行混淆处理后,理解堆栈轨迹的难度将会极大增加,因为类和方法的名称可能已发生变化。 除了重命名之外,R8 还可以更改堆栈轨迹中的行号,以在写入 DEX 文件时进一步缩减大小。 幸运的是,R8 在每次运行时都会创建一个 mapping.txt 文件,其中列出了经过混淆处理的类、方法和字段名称与原始名称的映射关系。此映射文件还包含用于将行号映射回原始源文件行号的信息。R8 会将此文件保存在 <module- name>/build/outputs/mapping/<build-type>/ 目录中。

注意 :您每次构建项目时都会覆盖 R8 生成的 mapping.txt 文件,因此您每次发布新版本时都要注意保存一个该文件的副本。 通过为每个发布版本保留一个 mapping.txt 文件的副本,您可以在用户提交来自旧版应用的经过混淆处理的堆栈轨迹时,调试相关问题。

(13)代码优化

 为了进一步缩减应用,R8 会在更深的层次上检查代码,以移除更多不使用的代码,或者在可能的情况下重写代码,以使其更简洁。下面是此类优化的几个示例:

 R8 不允许您停用或启用离散优化,也不允许您修改优化的行为。事实上,R8 会忽略试图修改默认优化行为的所有 ProGuard 规则,例如 -optimizations 和 - optimizationpasses。 此限制很重要,因为随着 R8 的不断改进,维护标准的优化行为有助于 Android Studio 团队轻松排查并解决您可能遇到的任何问题。

(14)启用更积极的优化

 R8 包含一组额外的优化功能,这些功能在默认情况下处于停用状态。 您可以通过在项目的 gradle.properties 文件中添加以下代码来启用这些额外的优化功能:

android.enableR8.fullMode=true

 这些额外的优化功能会使 R8 的行为与 ProGuard 不同,因此可能会需要您添加额外的 ProGuard 规则来避免运行时问题。例如,假设您的代码通过 Java Reflection API 引用一个类。默认情况下,R8 会假设您打算在运行时检查和操纵该类的对象(即使您的代码实际上并不这样做),因此它会自动保留该类及其静态初始化程序。
 不过,在使用“完整模式”时,R8 不会做出这种假设,如果 R8 断定您的代码从不在运行时使用该类,它会将该类从应用的最终 DEX 中移除。也就是说,如果您要保留该类及其静态初始化程序,则需要在规则文件中添加相应的保留规则。

(15)生成移除的(或保留的)代码的报告

 为了便于排查特定的 R8 问题,建议您查看 R8 从您的应用中移除的所有代码的报告。请针对要为其生成此报告的每个模块,将 -printusage <output-dir>/usage.txt 添加到您的自定义规则文件中。当您在 启用 R8 的情况下构建应用时,R8 会以您指定的路径和文件名输出报告。也可以在下面路径中查看:

R8 生成代码报告
(16)排查资源缩减问题

 Gradle 会在 <module-name>/build/outputs/mapping/release/(ProGuard 输出文件所在的文件夹)中创建一个名为 resources.txt 的诊断文件。此文件包含一些详细信息,比如,哪些资源引用了其他资源,哪些资源在使用,哪些资源被移除。

一起来查看自己的 R8 吧~

小编的扩展链接

参考链接

回眸一笑百媚生,六宫粉黛无颜色

举手之劳,赞有余香! ❤ 比心 ❤

上一篇 下一篇

猜你喜欢

热点阅读