Activity 与资源是一对孪生兄弟,想彻底解决Activity 插件化,就要面对如何使用插件中资源的问题。
资源加载机制
资源分类
Android资源分为两类:
- 第一类是res目录下存放的可编译资源文件,编译时,系统会自动在R.java中生成资源文件的十进制值,这种访问比较简单,只需要获取Resources对象,进而通过Resources的getxxx即可得到资源
- 第二类是assets目录下存放的原始资源文件,因为apk在编译的时候不会编译它们,所以我们也不能通过 R.xx 来访问,通过绝对路径呢?也不行,因为apk不会解压到本地,所以我们无法直接获取,只能通过AssetManager类的open方法去获取,类似如下代码:
1 | Resources resources = getResources(); |
由此可见啊,Resources 就能搞定一切!
剪不断理还乱:Resources 和 AssetManager
AssetManager 中有一个 addAssetPath(String path) 方法,App启动时,会把当前的Apk路径穿进去,接下来AssetManager 和 Resources 就能访问当前apk的所有资源了。addAssetPath 方法是不对外的,不过我们可以通过反射的方式,把插件apk的路径传入这个方法,就把插件资源添加到一个资源池了,当然,当前App的资源早已经在这个池子中了。App有几个插件,就调用几次addAssetPath ,把插件资源都塞到池子里。
apk打包时,每个资源都会在R文件中有一个十六进制值,并且会生成一个 resources.arsc 文件,它是一个 Hash 表,存放着每个十六进制值和资源的对应关系,这样在运行时,就能知道十六进制值对应res目录下哪个目录哪个资源。
资源插件化解决方案
以 在宿主App 中读取插件里面的字符串资源 为例,说明这个解决方案,总共会分为 4 个步骤:
- loadResources 。通过反射,创建 AssetManager 对象,调用 addAssetPath 方法,把插件 Plugin1 的路径添加到 AssetManager 对象中,从此,这个AssetManager 就只为这个插件 Plugin1 服务了。在这个 AssetManager 基础上,创建相应的 Resources 和 Theme 对象。
- 重写 Activity 的getAsset ,getResources 和 getTheme 方法,它们的思路都是一样的,如果插件的对象中相应的对象为空,则使用默认的,即类似: if(mAssetManager == null) { return super.getAssets(); }
- 加载外部的插件,生成这个插件的对应的 ClassLoader:
1 | File extractFile = this.getFileStreamPath(apkName); |
- 通过反射,获取插件中的类,构造出插件类的对象 dynamicObject ,然后就可以让插件中的类读取插件中的资源了。
1 | //整个章节的示例代码可以参考: https://github.com/BaoBaoJianqiang/Dynamic2 |
换肤
在学习了插件化编程后,换肤其实是可以把图片放到插件App中,然后生成R文件来动态读取这些资源:
因为前面已经讲过原理,所以这里暂且不表
1 | //示例代码可以参考: https://github.com/BaoBaoJianqiang/Dynamic3.2 |