Android高手笔记-D8, R8编译优化
作者:互联网
D8
- D8是一款用于取代 DX、更快的 Dex 编译器,可以生成更小的 APK;
开启D8的好处
- 编译更快、时间更短
- 编译时占用内存更小
- .dex文件更小
- .dex 文件拥有更好的运行时性能
- 支持在代码中使用 Java 8 语言
开启与关闭
- Android Studio 3.0 需要主动在gradle.properties文件中新增:android.enableD8=true
- D8作为DX的一个替代方案,Android Studio 3.1版本开始,将D8作为默认的Dex编译器。
- 想关闭D8 ,可以在gradle.properties里添加如下配置:
android.enableD8=false //关闭D8恢复到DX- android.enableD8.desugaring=false //恢复到以前的行为,让脱糖发生在Java编译之后,.class字节码仍遵循Java 7格式
执行增量构建
- 为了在开发过程中提高构建速度(例如提高持续集成 build 的速度),可以指示 d8 仅编译项目的部分 Java 字节码;
- 例如,如果启用了按类 dexing 处理,则只需重新编译自上次构建以来修改过的类(d8 无法自动检测哪些字节码文件已被修改过,因此您需要手动指定类列表):
//执行几个类的增量构建,并启用按类 dexing 处理,并为增量构建指定输出目录 d8 MainActivity.class R.class --intermediate --file-per-class --output ~/build/intermediate/dex
- 可以使用 --main-dex-list 指定想让 d8 编译到主 DEX 文件中的类
d8 ~/build/intermediate/dex --release --main-dex-list ~/build/classes.txt --output ~/build/release/dex
支持Java8
- 通过一个叫做“脱糖”的编译过程,将这些实用的语言功能转换为可以在 Android 平台上运行的字节码,D8脱糖就不会在transforms目录下生成desugar目录。
- Android Studio 和 Android Gradle 插件包含了 d8 启用脱糖所需的类路径资源。
- 从命令行使用 d8 时,需要手动添加一些资源:
- --lib:标记目标 Android SDK 中的 android.jar路径
- --classpath:标记项目的部分已编译的 Java 字节码,目前不打算将这部分字节码编译为 DEX 字节码,但在将其他类编译为 DEX 字节码时需要用到这些字节码。例如,如果代码使用默认和静态接口方法(一种 Java 8 语言功能),则需要使用此标记来指定您项目的所有 Java 字节码的路径,即使您不打算将所有 Java 字节码都编译为 DEX 字节码也是如此。这是因为 d8 需要根据这些信息来理解您项目的代码并解析对接口方法的调用
- 示例对一个访问默认接口方法的类执行增量构建:
d8 MainActivity.class --intermediate --file-per-class --output ~/build/intermediate/dex --lib android_sdk/platforms/api-level/android.jar --classpath ~/build/javac/debug
Java8新特性:接口默认方法和静态方法
- JDK1.8以前,接口(interface)没有提供任何具体的实现;
- JDK1.8开始,接口允许定义默认方法和静态方法
R8
- R8之前采用D8+ProGuard的形式构建,R8则将ProGuard和D8工具进行整合,目的是加速构建时间和减少输出apk的大小;
开启R8的好处
- 代码缩减(摇树优化):使用静态代码分析来查找和删除无法访问的代码和未实例化的类型,对规避 64k 引用限制非常有用;
- 资源缩减:移除不使用的资源,包括应用库依赖项中不使用的资源。
- 混淆代码:缩短类和成员的名称,从而减小 DEX 文件的大小
- 优化代码:检查并重写代码,选择性内联,移除未使用的参数和类合并来优化代码大小
- 减少调试信息 : 规范化调试信息并压缩行号信息。
- R8 会自动执行上述编译时任务,也可以停用某些任务或通过 ProGuard 规则文件自定义 R8 的行为。
- 使用某个第三方库时,通常只使用其中很小一部分。若不压缩,所有库代码都会保留在应用中。冗长的代码有时可以提高可读性和可维护性: 例如,使用有意义的变量名和建造者模式 来帮助其他人更容易检查和理解代码;但是这些模式会加大代码量,通常我们自己编写的代码有很大的压缩空间。
开启与关闭
- Android Studio 3.3 需在项目的 gradle.properties 里加上:android.enableR8=true
- Android Studio 3.4 或 Android Gradle 插件 3.4.0 及更高版本时,R8 是默认编译器(不再使用 ProGuard 执行编译时代码优化),用于将项目的 Java 字节码转换为在 Android 平台上运行的 DEX 格式。
- 不过创建新项目时,缩减、混淆处理和代码优化功能默认处于停用状态。因为这些编译时优化功能会增加项目的构建时间,而且如果没有充分自定义要保留的代码,还可能会引入错误。
- 开启代码缩减,需要在应用的主 build.gradle 文件中将 minifyEnable 属性设置为 true
- 开启资源缩减:需要在应用的主 build.gradle 文件中将 shrinkResources 属性设置为 true
- 资源缩减只有在与代码缩减配合使用时才能发挥作用。在代码缩减器移除所有不使用的代码后,资源缩减器便可确定应用仍要使用的资源,当添加包含资源的代码库时尤其如此。必须移除不使用的库代码,使库资源变为未引用资源,因而可由资源缩减器移除。
- 创建新项目或模块时,IDE 会创建一个 /proguard-rules.pro 文件,以便您添加自己的规则。
android { ... buildTypes { release { shrinkResources true //启用 R8 的资源缩减功能 minifyEnabled true //启用 R8 的代码缩减功能 proguardFiles //1. Android Gradle 插件会生成 proguard-android-optimize.txt(其中包含了对大多数 Android 项目都有用的规则),并启用 @Keep* 注解。 getDefaultProguardFile('proguard-android-optimize.txt'), //2. 使用 Android Studio 创建新模块时,Android Studio 会在该模块的根目录中创建 proguard-rules.pro 文件 'proguard-rules.pro' //3. AAR 库:<library-dir>/proguard.txt, JAR 库:<library-dir>/META-INF/proguard/ //由于 ProGuard 规则是累加的,因此 AAR 库依赖项包含的某些规则无法移除,并且可能会影响对应用其他部分的编译。 //例如,如果某个库包含停用代码优化功能的规则,该规则会针对整个项目停用优化功能。 //4. Android 资源打包工具 2 (AAPT2): //使用 minifyEnabled true 构建项目后,AAPT2 会根据对应用清单中的类、布局及其他应用资源的引用,生成保留规则。 //文件路径为:<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt //5. 自定义配置文件:详见下面的添加其他配置 } } }
添加其他配置
- 可以通过在相应的 productFlavor 代码块中再添加一个 proguardFiles 属性来添加每个构建变体专用的规则
android { ... buildTypes { release { ... } } flavorDimensions "version" productFlavors { flavor1 { ... } flavor2 { proguardFile 'flavor2-rules.pro' } } }
- flavor2 使用全部三个 ProGuard 规则,因为还应用了来自 release 代码块的规则。
关闭R8
- 可以在gradle.properties里添加如下配置:
android.enableR8=false
开启R8完全模式
- R8 普通模式是兼容 ProGuard的,若原项目里已使用了ProGuard,直接启用 R8 即可。同时,R8 也有完全模式,与ProGuard不直接兼容。
可以在 gradle.properties 文件中另外设置以下内容:
android.enableR8.fullMode=true
- 额外的优化功能会使 R8 的行为与 ProGuard 不同,因此可能会需要您添加额外的 ProGuard 规则,以避免运行时问题。
自定义要保留的代码
- 在某些情况下,R8 很难做出正确判断,因而可能会移除应用实际上需要的代码:
1. 当应用通过 Java 原生接口 (JNI) 调用方法时 2. 当您的应用在运行时查询代码时(如使用反射) - 反射 (Reflection) 会导致 R8 在跟踪代码时无法识别到代码的入口点
- 如需修复错误并强制 R8 保留某些代码,在 ProGuard 规则文件中添加 -keep 代码行,如
-keep public class MyClass
- 或者为要保留的代码添加 @Keep 注解
1. 在类上添加 @Keep 可按原样保留整个类 2. 在方法或字段上添加该注释,将使该方法/字段(及其名称)以及类名称保持不变。 3. 只有在使用 AndroidX 注解库且您添加 Android Gradle 插件随附的 ProGuard 规则文件时,此注解才可用。
- 如需输出 R8 在构建项目时应用的所有规则的完整报告,请将以下代码添加到模块的 proguard-rules.pro 文件中:
// You can specify any path and filename. -printconfiguration ~/tmp/full-r8-config.txt
自定义要保留的资源
- 如果您有想要保留或舍弃的特定资源,请在项目中创建一个包含 标记的 XML 文件,并在 tools:keep 属性中指定每个要保留的资源,在 tools:discard 属性中指定每个要舍弃的资源。这两个属性都接受以逗号分隔的资源名称列表。您可以将星号字符用作通配符。
- 将该文件保存在项目资源中,例如,保存在 res/raw/keep.xml 中。构建系统不会将此文件打包到应用中。
<?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" />
严格引用检查
- 通常资源缩减器可以准确地判断是否使用了某个资源。不过如果代码中调用了 Resources.getIdentifier()(或者引用的任何库会执行此调用,例如 AppCompat 库便会执行此调用),这意味着代码将根据动态生成的字符串查询资源名称。资源缩减器在默认情况下(安全缩减模式)会采取保护行为,将所有具有匹配名称格式的资源标记为可能已使用,无法移除。资源缩减器还会查看代码中的所有字符串常量以及各种 res/raw/ 资源,以查找格式类似于 file:///android\_res/drawable//ic\_plus\_anim\_016.png 的资源网址。如果它找到与此类似的字符串,或找到其他看似可用来构建与此类似的网址的字符串,则不会将它们移除。
- 例如,以下代码会将所有带 img\_ 前缀的资源标记为已使用:
val name = String.format("img_%1d", angle + 1) val res = resources.getIdentifier(name, "drawable", packageName)
- 启用严格引用检查: 将 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 资源缩减器只会移除未由应用代码引用的资源,这意味着,它不会移除用于不同设备配置的备用资源;
- 例如使用的是包含语言资源的库(如 AppCompat 或 Google Play 服务),那么应用中将包含这些库中消息的所有已翻译语言的字符串,可以使用 resConfigs 属性移除应用不需要的备用资源文件,如设置只保留英语和法语的语言资源
android { defaultConfig { ... resConfigs "en", "fr" } }
合并重复资源
- 默认情况下,Gradle 还会合并同名的资源(多个文件具有完全相同的资源名称、类型和限定符时)。这一行为不受 shrinkResources 属性控制,也无法停用,因为当多个资源与代码查询的名称匹配时,有必要利用这一行为避免错误。
- Gradle 会在重复项中选择它认为最合适的文件(根据下述优先顺序),并且只将这一个资源传递给 AAPT,以便在最终工件中分发
- Gradle 会按以下级联优先顺序合并重复资源:库项目依赖项 → 主资源 → 构建变种 → 构建类型, 如某个重复资源同时出现在主资源和构建变种中,Gradle 会选择构建变种中的资源。
- 如果完全相同的资源出现在同一源代码集中,Gradle 无法合并它们,并且会发出资源合并错误,或者在 build.gradle 文件的 sourceSet 属性中定义了多个源代码集,src/main/res/ 和 src/main/res2/ 包含完全相同的资源也会报错。
由于文章篇幅有限,文档资料内容较多,需要《2022最新Android面试真题+解析》、数据结构与算法面试题、Java 面试题、Android四大组件、Android 面试题、UI控件篇、网络通信篇、架构设计篇、性能优化篇、源码流程篇、 Kotlin方面、第三方框架、大厂面经,可以【点击这里免费获取】,希望能够共同进步,共同学习,共勉!
本文转自 https://juejin.cn/post/7052636587288428557,如有侵权,请联系删除。
标签:--,代码,R8,D8,Android,android,资源 来源: https://www.cnblogs.com/jiajia246/p/15843891.html