Android 资源动态加载(一)
无论是插件化开发还是热修复,都有一个绕不过的关键技术难度。那就是——如何动态加载资源?
在插件化方案中,我们可以将资源和代码一起打包在插件中,宿主工程动态加载插件中的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的具体参数有点不一样,但是大致思路是一致的)。
Author: deskid
Link: https://deskid.github.io/2018/11/13/android-resource-hook/
License: 知识共享署名-非商业性使用 4.0 国际许可协议