简单记录一下上D8、R8之后的坑,之前业务上热修复一直用的是Tinker,因为大厂加成加上支持加固(至于为啥上加固就不说了,该懂的都懂,要么加固要么不能上架)。
我们其实一直是用tinker作为灰度发布工具的。在长达一年多的时间里一直很稳定,后面上了加固之后偶尔会出现异常,不过大多数情况其实是加固的自有升级导致的边缘case,然后在今年使用D8+R8之后,加固的失败率明星上升了。尤其是是在高版本的安卓设备上(7.0+)。跟了一下原因,主要是R8的过度优化导致的。R8会把整个项目中的所有代码,包括依赖库中的代码都进行全局优化,其优化程度堪称恐怖,这里就不细说了,有兴趣可以可以看下Jake Wharton大神的博客。
其中一个导致问题的优化是全局公共方法抽出,R8会把全局中代码相似度非常高的代码抽出来,放到一个新生成的类里,从而减少方法数。然后这个方法可能会被放到别的dex里。于是当启用热修复patch之后会导致tinker里的一部分代码因为被优化到别的dex里,在新合成的dex里并不存在,从而导致ClassNotFoundException,然后用户就崩溃了。
分析问题,主要是Tinker的相关代码被优化处理了,并且这个优化并没有提供开关,keep也没用,keep并不能阻止R8的优化。于是有了一个邪道方案:
第一步,先keep tinker的全部相关代码。因为package name不同,自然就会被放到一个package里。
第二步,在一个独立的项目里放入tinker 并且不启动R8并且打包完成,然后把包拆开把相关的tinker代码捞出来备用。
第三步,在主项目每次编译完成后解开包,把主dex里的tinker代码删除掉再把独立项目里的备用没被r8处理过的代码提出来替换进去。
最后,打包回去,再签名加固,万事大吉。
似乎听起来没什么毛病,结果后面还是炸了。
再次跟进问题,还是R8的优化,tinker一个类的某个变量是私有的,有一个set方法,然而R8优化的时候把他换成public的了(估计还是为了节约方法数,当初的dex 65535的坑真的大)。然而我们替换回去的tinker还是私有的,于是,再次出现了方法不存在错误。
所有问题又绕回来了,归根结底还是要阻止R8对项目里tinker代码的优化。于是再上一个方案的基础上,把主项目里对tinker的引用换成 compileOnly 不存在你总不能优化了吧?再去查看了一下之前备用的tinker代码块方法数,大概2000以内,于是手动造2000个空方法并且keep到主dex里,再之前的第三步里,把插入的桩方法全部干掉,换成没被处理过的tinker的代码。最后打包回去。
到此R8跟tinker的冲突暂时告一段落,不知道后面会不会还有新的坑。至于为什么步换成proguard?我也想啊,但是现在主项目已经到了不上R8 主dex就一定会超出65535的地步了,这是另一个坑,这里就不说了。
相关代码就不贴了,毕竟涉及到公司项目。希望这个思路可以帮助遇到相关问题的人。