动态加载技术,也叫插件化技术,在技术驱动型公司中扮演着相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和CPU占用,还可以实现热插拔,即不在发布新版本的情况下更新某些模块。动态加载时一项很复杂的技术。这主要介绍其中的三个基础性问题。
- 开源插件化框架 DL。传送门
资源访问
问题描述
宿主程序调起未安装的插件apk,一个很大的问题就是资源如何访问,具体来说就是插件中凡是以R开头的资源都不能访问了。这是因为宿主程序中并没有插件的资源,所以通过R来加载插件的资源时行不通的,程序会抛出异常:无法找到某某id 对应的资源。
解决思路分析
-
将插件中的资源在宿主程序中也预置一份,这虽然能解决问题,但是就会产生一些弊端:
- 首先,这样就需要宿主和插件同时持有一份相同的资源,增加了宿主apk的大小;
- 其次,在这种模式下,每次发布一个插件都需要将资源复制到宿主程序中,这意味着没发布一个插件都要更新一下宿主程序,这就和插件化的思想相违背了。
- 因为,插件化的目的就是要减小宿主程序apk包的大小,同时降低宿主程序的更新频率并做到自由装载模块。所以这一思路不可取,它限制了插件化的线上更新这一重要特性。
-
首先将插件中的资源解压出来,然后通过文件流去读取资源。
这样做理论上可行的,但是实际操作起来还是有很大的困难。
- 首先,不同资源有不用的文件流格式,比如图片、XML等;
- 其次,针对不同设备加载的资源可能是不一样的的,如何选择合适的资源也是一个需要解决的问题。
基于这两点这种方法也是不可取的,因为实现起来有较大的难度。
-
参考模仿系统加载访问资源的方式
Activity的工作主要是通过 ContextImpl 来完成,Activity中有一个叫 mBase 的成员变量,它的类型就是 ContextImpl。注意到Context中有两个抽象方法,看起来是和资源有关的,实际上Context 就是通过它们来获取资源的。这两个抽象方法的真正实现是在 ContextImpl中,也就是说,只要实现了这两个方法,就可以解决资源问题了。
public abstract AssetManager getAssets(); public abstract Resources getResources();
代码实现
protected fun loadResources() {
try {
val assetManager = AssetManager::class.java.newInstance()
val addAssetPath = assetManager.javaClass.getMethod("addAssetPath", String::class.java)
addAssetPath.invoke(assetManager, mDexPath)
mAssetManager = assetManager
} catch (e: Exception) {
e.printStackTrace()
}
val superRes = super.getResources()
val mResources = Resources(mAssetManager, superRes.displayMetrics, superRes.configuration)
val mTheme = mResources.newTheme()
mTheme.setTo(super.getTheme())
}
从 loadResources() 的实现可以看出,加载资源的方法是通过反射,通过调用 AssetManager 中的 addAssetPath 方法,我们可以将一个 apk 中的资源加载到Resources 对象中,由于 addAssetPath 是隐藏 API 我们无法直接调用,所以只能通过反射。下面是它的声明,通过注释我们可以看出,传递的路径可以是zip 文件也可以是一个资源目录,而 apk 通过AssetManager 来创建一个新的Resources对象,通过这个对象我们就可以访问插件apk中的资源了,这样一来问题就解决了。
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
然后在代理Activity 中实现 getAssets() 和 getResources()
override fun getResources(): Resources {
return if (mResources == null) super.getResources() else mResources!!
}
override fun getAssets(): AssetManager {
return if (mAssetManager == null) super.getAssets() else mAssetManager!!
}
Activity生命周期的管理
- 反射方式
- 接口方式
反射方式
override fun onResume() {
super.onResume()
val onResume: Method? = mActivityLifecircleMethods.get("onResume")
try {
onResume?.invoke(mRemoteActivity, arrayOf(Any()))
} catch (e: Exception) {
e.printStackTrace()
}
}
缺点:
- 反射代码写起来比较复杂
- 过多使用反射会影响性能
接口方式
interface DLPlugin{
fun onCreate()
fun onStart()
fun onResume
...
}
在代理Activity中调用即可
override fun onResume() {
mRemoteActivity.onResume()
super.onResume()
}
...