聊聊Android的静态代理插件框架原理[03]--Activity静态代理之资源加载(上)

上一篇文章我们介绍了如何偷梁换日,用HostBaseActivity去在Activity运行中代理PluginBaseActivity,并让PluginBaseActivity具有Android中Activity组件的生命周期。也同时留了一个问题,插件的资源加载。这篇文章就和大家一起分享下,插件资源加载涉及到的知识点和解决方案。


Activity静态代理之资源加载

在我介绍解决方案前,我们有必要一起了解下Android的资源相关的知识。

Android应用程序主要由两部分内容组成:代码和资源。资源主要指的是与UI相关的图片啊,布局啊,字符串啊等文件。

Android应用程序资源可以分为两大类,分别是assets和res。

assets类资源放在工程根目录的Assets子目录下,这些文件最终会被原装不动地打包在apk文件中。如果我们要在程序中访问这些文件,那么就需要指定文件名来访问。

res资源也比较好理解,上一个Android官网的介绍 Resource Types

Animation Resources
    Define pre-determined animations.
    Tween animations are saved in res/anim/ and accessed from the R.anim class.
    Frame animations are saved in res/drawable/ and accessed from the R.drawable class.

Color State List Resource
    Define a color resources that changes based on the View state.
    Saved in res/color/ and accessed from the R.color class.

Drawable Resources
    Define various graphics with bitmaps or XML.
    Saved in res/drawable/ and accessed from the R.drawable class.

Layout Resource
    Define the layout for your application UI.
    Saved in res/layout/ and accessed from the R.layout class.

Menu Resource
    Define the contents of your application menus.
    Saved in res/menu/ and accessed from the R.menu class.
    String Resources

Define strings, string arrays, and plurals (and include string formatting and styling).
    Saved in res/values/ and accessed from the R.string, R.array, and R.plurals classes.
    Style Resource

Define the look and format for UI elements.
    Saved in res/values/ and accessed from the R.style class.
    Font Resources

Define font families and include custom fonts in XML.
    Saved in res/font/ and accessed from the R.font class.

More Resource Types
    Define values such as booleans, integers, dimensions, colors, and other arrays.
    Saved in res/values/ but each accessed from unique R sub-classes (such as R.bool, R.integer, R.dimen, etc.).

在编译打包的过程中,AAPT会把资源文件打包成二进制文件,并对除了assets资源之外所有的资源赋予一个资源ID常量,并且会生成一个资源索引表resources.arsc。
我们熟知的apk文件其实只是一个zip压缩文件,里面有包含全部java类的文件(classes.dex)和全部编译后的资源文件(resources.arsc).

了解了Android中的资源,我们接下来看一下,我们平时到底是如何使用这些资源的呢?

这里就必须要提到两个类,AssetManagerResources

简单来说,AssetManager用于访问应用原始的asset文件,相对来说是一个封装层次较低的类。而Resources则提供了高封装层次的API用于获取类型数据,

现在我们可以解释上一篇文章末尾提出的问题了,为什么插件里的资源,宿主里无法直接访问呢?很简单,因为宿主apk打包的时候,resources.arsc文件里面并没有插件里面的资源文件ID,也就访问不到插件里面的资源。也就是说,用宿主的AssetManager和Resources实例是get不到插件的资源的。

聪明的小伙伴已经想到了,那既然宿主的AssetManager和Resources实例不行,那我们用插件的的AssetManager和Resources实例不就可以了?!

是的,『静态代理』的另一层含义就是,访问插件资源的时候,用插件的AssetManager和Resources代理宿主的AssetManager和Resources。上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private AssetManager createAssetManager(String dexPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath);
return assetManager;
} catch (Exception e) {
return null;
}
}

private Resources createResources(AssetManager assetManager) {
Resources superRes = mAppContext.getResources();
return new Resources(assetManager,
superRes.getDisplayMetrics(), superRes.getConfiguration());
}

在HostActivity中,替换对应的方法为插件的AssetManager和Resources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HostBaseActivity extends FragmentActivity {

@Override
public Resources getResources() {
//返回插件的Resources
}

@Override
public AssetManager getAssets() {
//返回插件的AssetManager
}

...

}

这下,宿主里可以获取到插件里对应的资源了。不过插件化带来的资源加载的问题远没那么简单。比如说我们知道,Android系统中通过LayoutInflater加载布局会根据view的包名对view进行相应缓存,在app开发中本来是没有任何问题的。但是我们插件开发,加载了多个apk的代码和资源,势必会有可能不同的插件下不同的view恰好有相同的包名,如果统一用默认的LayoutInflater的话,势必会有问题。下一篇,我们结合LayoutInflater缓存机制,介绍一下解决方案。谢谢大家。