0%

第7章:资源初探

Activity 与资源是一对孪生兄弟,想彻底解决Activity 插件化,就要面对如何使用插件中资源的问题。

资源加载机制

资源分类

Android资源分为两类:

  • 第一类是res目录下存放的可编译资源文件,编译时,系统会自动在R.java中生成资源文件的十进制值,这种访问比较简单,只需要获取Resources对象,进而通过Resources的getxxx即可得到资源
  • 第二类是assets目录下存放的原始资源文件,因为apk在编译的时候不会编译它们,所以我们也不能通过 R.xx 来访问,通过绝对路径呢?也不行,因为apk不会解压到本地,所以我们无法直接获取,只能通过AssetManager类的open方法去获取,类似如下代码:
1
2
3
Resources resources = getResources();
AssetManager am = getResources().getAssets();
InputStream is = getResources().getAssets().open("filename");

由此可见啊,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 个步骤:

  1. loadResources 。通过反射,创建 AssetManager 对象,调用 addAssetPath 方法,把插件 Plugin1 的路径添加到 AssetManager 对象中,从此,这个AssetManager 就只为这个插件 Plugin1 服务了。在这个 AssetManager 基础上,创建相应的 Resources 和 Theme 对象。
  2. 重写 Activity 的getAsset ,getResources 和 getTheme 方法,它们的思路都是一样的,如果插件的对象中相应的对象为空,则使用默认的,即类似: if(mAssetManager == null) { return super.getAssets(); }
  3. 加载外部的插件,生成这个插件的对应的 ClassLoader:
1
2
3
4
5
File extractFile = this.getFileStreamPath(apkName);
dexPath = extractFile.getPath();

fileRelease = getDir("dex", 0);//0代表Context.MODE_PRIVATE
classLoder = newDexClassLoader(dexPath, fileRelease.getAbsolutePath(), null, getClassLoder());
  1. 通过反射,获取插件中的类,构造出插件类的对象 dynamicObject ,然后就可以让插件中的类读取插件中的资源了。
1
2
//整个章节的示例代码可以参考: https://github.com/BaoBaoJianqiang/Dynamic2

换肤

在学习了插件化编程后,换肤其实是可以把图片放到插件App中,然后生成R文件来动态读取这些资源:

因为前面已经讲过原理,所以这里暂且不表

1
//示例代码可以参考: https://github.com/BaoBaoJianqiang/Dynamic3.2
谢谢你的鼓励