【Android篇】ProGuard, D8, R8编译器及代码优化

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」

为了减小安卓应用的大小,Google官方在Android Gradle插件中提供了几种不同的优化编译器:ProguardD8R8。它们的主要作用包括:

  1. 代码缩减(Code Shrinking):从App应用及其依赖中检测并安全地移除不使用的类、字段、方法和属性;
  2. 资源缩减(Resource Shrinking):从封装应用和应用库依赖项中移除不使用的资源;
  3. 混淆(Obfuscation):缩短类和成员的名称,从而减小 DEX 文件的大小;
  4. 优化(Optimization):检查并重写代码,以进一步减小应用的 DEX 文件的大小。

Proguard

Proguard是Guardsquare公司最先推出的一个免费的,可以提供缩减和优化处理的编译器,其工作流程为:

可以看到,首先.java文件会被Java编译器编译成.class文件,之后Proguard则会对.class文件进行缩减和优化,再通过Android运行环境中的Dalvik虚拟机将其编译成可以运行的.dex文件。也就是:

SourceCode(.java) → javac → Java Bytecode(.class) → Proguard → Optimized Java bytecode(.class) → Dex → *Dalvik Optimized Bytecode(.dex)

在这之后,Google推出了Jack&Jill编译器,它可以将以上步骤缩减成一步,从.java文件直接编译成.dex文件。但是这个编译器的效果并不理想,于是2017年Google决定重新使用之前的这套Proguard工作流程,但这次,Google将dx编译器进行了优化,产生了一个新的编译器: D8

D8

D8相对于Proguard而言,最大的变化就是Google优化了dx编译器,参考Google自己的基准测试,D8相比之前的dx编译器,编译时间缩短了20%,而且编译产生的.dex文件更小。在JakeWharton的Android’s Java 8 Support中有对于D8的详细介绍,其工作流程为:

此时虽然工作流程得到了简化,但是由于Kotlin语言的出现和普及,使得Google不得不再次对于D8编译器进行改进和优化,于是在D8的基础上又出现了新的编译器:R8

R8

R8是目前Gradle 3.4.0及以上版本的默认编译器,其使用方法和Proguard完美通用,都是通过proguard-rules.pro这个文件来声明编译规则并执行,其工作流程为:

可以看出R8同时支持Java和Kotlin代码,通过Google提供的关于Proguard和R8的对比测试可以看出,R8比不仅在编译时间上比Proguard快了将近一半,在生成的apk文件大小上也稍小于Proguard。但有意思的是,GuardSquare公司自己也出过相同关于Proguard和R8的对比测试,所以这个就是见仁见智了。

R8编译器的使用

启用R8编译器

启用R8编译器需要在project根目录下的build.gradle加入:

android {
    buildTypes {
        release {
            // 对于release版本是否启用代码缩减,混淆处理和优化
            minifyEnabled true

            // 是否启用资源缩减(基于Android Gradle plugin)
            shrinkResources true
            
            // 根据proguard-android-optimize.txt和proguard-rules.pro文件定义处理规则
            proguardFiles getDefaultProguardFile(
                    'proguard-android-optimize.txt'),
                    'proguard-rules.pro'
        }
    }
    ...
}
复制代码

“proguard-android-optimize.txt”是Gradle PlugIn里面默认的处理规则文件,而”proguard-rules.pro“是处理规则文件,创建新项目时Android Studio也会自动生成。当需要添加一系列自定义规则时,可以在”proguard-rules.pro“中进行添加。

添加单独模块的proguard规则文件

对于一个多模块的项目,各个模块可以在模块根目录下分别定义自己独立的proguard规则文件proguard-rules.pro,然后在模块根目录的build.gradle中加入如下内容:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile(
                    'proguard-android-optimize.txt'),
                    'proguard-rules.pro'
          
          	// Using module ProGuard rules files and add it into any module which
          	// is dependent on this module
          	consumerProguardFiles 'proguard-rules.pro'
        }
    }
    ...
}
复制代码

简单来说,如果一个Library Module被App Module所依赖,那么通过在library module中声明consumerProguardFiles属性,app module就会将自己根目录下的proguard-rules.pro和library module的proguard-rules.pro合并作为代码缩减,混淆等处理的规则来运行。

添加自定义Proguard-rules.pro规则

keep规则

主要用于规定对于类(class)和类成员(members)是否要进行缩减,混淆的操作。默认状态缩减和混淆都是开启的,也就是:

此时,R8会对这个类进行缩减(remove unused code)和混淆(rename things)操作。

-keep class com.foo.library.** { *; }

禁止R8对类和类成员的所有操作

要注意这种情况非常不推荐,因为它禁止了所有对于这个类的操作。而事实上在大部分情况中总是可以选择性的进行一些优化操作的。

-keepclassmembers com.foo.library.** { *; }

禁止R8对类成员的操作,但允许对类本身进行缩减和混淆

如果这个类本身没有被使用,它会被删掉;如果它被使用了,则会将其重命名(混淆处理),而对于其中的类成员没有任何操作

-keepnames com.foo.library.** { *; }

只检查是否有没有被使用的类或类成员,如果有的话则删掉它们。但不进行任何重命名混淆操作。

-keepclassmembernames com.foo.library.** { *; }

检查没有被使用的类和类成员,删掉没有用的,然后对类名进行重命名,但保留类成员的名字不变

其他常用规则命令

-verbose:打印详细的混淆信息;

-dontnote com.foo.bar.*:不打印foo.bar包内的notes信息,例如typo或者missing useful options;

-dontwarn com.foo.bar.*:不打印foo.bar包内的warning信息,轻易不推荐使用

-dontpreverify:不进行检查校验,主要针对使用Java Micro Edition或Java 6+版本的Java Library需要进行检查校验,对于安卓平台上运行的Library来说可以添加这个规则来加快编译速度;

-keepclasseswithmembers:和keep规则基本一致,唯一的区别就是它只作用于存在类成员的类,例如keep所有含有main method的Application Class;

-keepclasseswithmembersnames:同理,和keep规则的唯一区别也是它只作用于存在类成员的类

-printusage[filename]:在standard output或者文件中打印出所有被缩减的内容

-keepattributes[attribute_filter]禁止重命名class中的参数(attributes),比如使用第三方library时要禁止混淆Exceptions, InnerClasses和Signature;打印stack trace的时候要禁止混淆SourceFiles和LineNumberTable等;

-dontusemixedcaseclassnames:进行混淆时不同时使用大小写来重命名。

总结

这里只是简单介绍了一下Proguard和R8代码优化需要了解的一些内容,对于日常安卓开发来说,只要了解这些常用的规则和如何使用即可。对于R8编译器更深层的介绍在此不作展开了,希望能对你有所帮助。

参考文章

Google官方文档

Android Journey: Proguard, D8, R8 what are they?

Jake Wharton – Android’s Java 8 Support

Distinguishing between the different ProGuard “-keep” directives

Proguard的使用

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享