Android 动态加载技术

动态加载技术,也叫插件化技术,在技术驱动型公司中扮演着相当重要的角色,当项目越来越庞大的时候,需要通过插件化来减轻应用的内存和CPU占用,还可以实现热插拔,即不在发布新版本的情况下更新某些模块。动态加载时一项很复杂的技术。这主要介绍其中的三个基础性问题。

资源访问

问题描述

宿主程序调起未安装的插件apk,一个很大的问题就是资源如何访问,具体来说就是插件中凡是以R开头的资源都不能访问了。这是因为宿主程序中并没有插件的资源,所以通过R来加载插件的资源时行不通的,程序会抛出异常:无法找到某某id 对应的资源。

解决思路分析

  1. 将插件中的资源在宿主程序中也预置一份,这虽然能解决问题,但是就会产生一些弊端:

    • 首先,这样就需要宿主和插件同时持有一份相同的资源,增加了宿主apk的大小;
    • 其次,在这种模式下,每次发布一个插件都需要将资源复制到宿主程序中,这意味着没发布一个插件都要更新一下宿主程序,这就和插件化的思想相违背了。
    • 因为,插件化的目的就是要减小宿主程序apk包的大小,同时降低宿主程序的更新频率并做到自由装载模块。所以这一思路不可取,它限制了插件化的线上更新这一重要特性。
  2. 首先将插件中的资源解压出来,然后通过文件流去读取资源。

    这样做理论上可行的,但是实际操作起来还是有很大的困难。

    • 首先,不同资源有不用的文件流格式,比如图片、XML等;
    • 其次,针对不同设备加载的资源可能是不一样的的,如何选择合适的资源也是一个需要解决的问题。

    基于这两点这种方法也是不可取的,因为实现起来有较大的难度。

  3. 参考模仿系统加载访问资源的方式

    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()
    }
    ...

插件 ClassLoader 的管理 page 468

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容