无论是插件化开发还是热修复,都有一个绕不过的关键技术难度。那就是——如何动态加载资源?

在插件化方案中,我们可以将资源和代码一起打包在插件中,宿主工程动态加载插件中的dex和资源,宿主工程和插件工程的开发流程实现了解耦。

在热修复中,我们除了常见的代码修复,还可以修改诸如文案,图标等资源文件,甚至可以利用资源热修复的能力在native上不发版玩一些资源运营。

关键技术

在上面两种主要使用场景中,资源动态加载技术侧重点也有不同。

插件化

插件化注重的是插件与插件之间、插件与宿主之间资源的隔离。我们知道,插件的打包是和宿主工程分开打包,两个apk包生成的R.java是互相独立的,如果不做资源隔离,资源id很容易发生冲突。除非,在插件中只使用自己的资源,不共享宿主资源,并且插件只管理自己apk的Resource加载。

如果要合并资源的话,资源Id冲突的问题就不可避免了。插件化资源隔离的一种实现方式是改写aapt的代码,重新划分插件的packageId。packageId 占用两个字节,理论上我们可以使用0x01~0xFF之间的任何值,其中系统应用已经占用了0x01,共享库占用了0x00, 第三方应用则默认占用了0x7F

资源热修复

资源热修复则关注于另一个要求:patch包和release包的资源ResId必须要一致。但是,我们知道,在生成的apk中,代码里的通过name去获取资源的地方,比如

context.getString(R.string.helloword);

因为R.string.xxxx是整形常量,会被编译器inline优化掉。

context.getString(2131427369)

所以运行时是没有name信息的。因此我们不能简单通过替换相同name的资源去做热修复,这里必须通过替换相同resId的资源才可以。

所以在热修复中,资源修复的关键是,如何在 patch 包中打出拥有相同资源id的resource.arsc。也就是所谓的“资源Id固化”。

思路是通过修改processResourcesTask,给aapt传递 --stable-ids public.txt参数。(aapt 和 aapt2的具体参数有点不一样,但是大致思路是一致的)。