前言
最近调研了下升级android gradle plugin的可行性,在调研过程中发现了R8的几个坑,记录下解决方法。
升级Android Gradle Plugin
首先需要升级android gradle plugin 到 3.4.1
1 | buildscript { |
然后将项目中所有禁用选项移除,如d8,r8,如下
1 | #android.enableR8=false |
ClassNotFoundException
尝试升级后遇到的第一个问题就是R8开启后在multidex启用的情况下,且minSdkVerison<21时,在Dalvik上启动发生crash,出现ClassNotFoundException。
通过阅读android gradle plugin源码发现,构建R8Transform时,传递给R8Transform构造函数的multidex keep proguard文件未将构建时aapt计算产生的manifest multidex proguard file传递进去,导致很多应该出现在maindex中的类实际上跑到了second dex中去了,在Daivik上出现启动crash的问题。
具体关键源码如下:
1 | @NonNull |
对于该问题,我们可以简单的hook下R8Transform中的mainDexRulesFiles字段进行修复,判断mainDexRulesFiles中是否已经有manifest multidex proguard file, 如果没有,则将该文件添加进去并替换mainDexRulesFiles字段。
1 |
|
关于mainDexRulesFiles和mainDexListFiles的区别,可以查看文档:https://developer.android.com/studio/build/multidex?hl=zh-cn
mainDexRulesFiles对应于multiDexKeepProguard属性,该文件使用与proguard相同的格式,并且支持整个proguard语法,我们可以在gradle中手动配置该文件
1 | android { |
mainDexListFiles对应于multiDexKeepFile属性,该文件应该每一行包含一个类,并且采用/来分割包路径,如
1 | com/example/MyClass.class |
我们可以在gradle中手动配置该文件
1 | android { |
至此maindex的问题已经解决。
关于tinker R8的支持,可以见 PR https://github.com/Tencent/tinker/pull/1112
Illegal invoke-super to 某某某方法返回值 某某某方法名() from class 某某某类
解决完maindex的问题后发现又遇到另一个问题,当构建patch时,会apply基线的mappping文件,此时,如果R8开启,就会报如下错误
1 | Illegal invoke-super to 某某某方法返回值 某某某方法名() from class 某某某类 |
经过测试发现,Android Gradle Plugin 3.5.0-beta02已经修复了该问题,但是对于Android Gradle Plugin 3.4.0-3.4.1该问题依旧存在,那么是否就无解呢?其实不是的,Google此处留了一个小后门,可以独立更新R8。
首先有个前提条件,就是R8是被打包在android gradle plugin插件中的,并非使用的远程依赖。R8的所有相关类被打入了如下依赖中:
1 | com.android.tools.build:builder:3.4.1 |
而该依赖是被如下依赖传递依赖的:
1 | com.android.tools.build:gradle:3.4.1 |
这个问题的解决得从gradle的classloader机制说起,我们只需要明确如下概念即可:
- buildscript中classpath声明的一级依赖,声明在前的,classloader会优先查找
- buildscript中classpath声明的一级依赖,其优先级高于任何传递依赖
我们可以用如下代码打印一下加载R8相关类的classloader中相关文件路径
1 | project.afterEvaluate { |
假设我们classpath依赖有如下依赖
1 | buildscript { |
输出如下
1 | url: file:/Users/lizhangqu/.gradle/caches/modules-2/files-2.1/com.android.tools.build/gradle/3.1.4/8f9a726f69c0c8fa3f447566717a21e6b394ed9/gradle-3.1.4.jar |
从日志可以看到,一级依赖,先声明的会优先进行查找,任何传递依赖,都位于一级依赖之后。
有了以上理论知识,我们就可以修复该问题了。
google为我们提供了R8的远程依赖,将 http://storage.googleapis.com/r8-releases/raw 添加到maven中并声明r8远程依赖(com.android.tools:r8:1.5.37) 为一级依赖
1 | buildscript { |
再输出classloader中的url
1 | url: file:/Users/lizhangqu/.gradle/caches/modules-2/files-2.1/com.android.tools.build/gradle/3.4.1/195bd39d36b255d333d6493dcac0d542258d2a3d/gradle-3.4.1.jar |
从日志输出再次验证了我们的结论
- buildscript中classpath声明的一级依赖,声明在前的,classloader会优先查找
- buildscript中classpath声明的一级依赖,其优先级高于任何传递依赖
如果对R8源码感兴趣,可以见代码库:https://r8.googlesource.com/r8/
这里注意一个问题,目前可用的r8远程依赖版本有很多,android gradle plugin 3.4.1打包的版本为1.4.94,我们使用的远程依赖必须高于这个版本,测试下来,1.5.25、1.5.26、1.5.27、1.5.33、1.5.34这几个版本是正常的,1.5.36、1.5.36、1.5.37及以上版本会卡死在R8Tranform任务上,使用的时候需要留意。可以使用如下类似代码进行约束
1 | buildscript { |
那为什么android gradle plugin 3.5.0-beta02没有这个问题呢?因为3.5.0用的R8版本就是1.5.34,该版本在android gradle plugi 3.4.1上经过验证确实没问题。
总结
R8的坑还很多,目前不建议启用,但可以踩踩坑先,看看有什么未知问题,提前进行解决。目前建议在gradle.properties中加入如下配置进行禁用
1 | android.enableR8=false |
