Android插件化进阶——插件化原理和插件管理器(二)

上一篇文章我们讲解了如何使用 DexClassLoader 来加载插件文件中的类。

Android插件化进阶——插件化原理和插件管理器(一)

这一节我们来具体讲解一下资源文件的加载,并设计一个简单的插件管理器。要了解这方面的内容,首先得看一下 Android 系统中为我们提供了哪些方法来加载资源,下面来看一下图例。

资源加载

从图中我们可以看出,当资源只有文件名,那么我们通过 AssetManager 就可以直接通过名字加载资源。如果资源有对应的 id,像图片、动画、布局这一类的资源,会生成 ResourceId 的,我们可以通过 Resource 这个类完成对应资源文件加载,最后通过 AssetManager 完成读写。

所以说,资源的加载主要是通过 AssetManager 这个类完成的,是加载资源的核心Resource 这个类完成一个资源 id 到文件名的映射。

安装到系统的 apk,系统会为他创建资源加载类 Resources 和 AssetManager,所以安装到系统的 APK 可以直接通过 Context 获取到这两个类的引用,从而完成资源加载,但插件 APK 没有被安装,所以这些 APK 文件并没有这两个资源加载类,但这两个资源加载类又是必不可少的,所以需要我们手动创建,我们来看一下市面上比较流行的做法:

插件资源加载方法


从这张图上我们可以看出,核心思想就是为每一个插件分别创建一个 AssetManager 来完成对应插件资源的加载。在插件加载的时候,通过反射将资源文件加入到宿主 AssetManager,让宿主 AssetManager 指定插件的资源加载路径。

所以讲到现在,我们可以分析出来插件化需要处理的核心问题也就是核心技术:

  • 处理所有插件 apk 文件中的 Manifest 文件
  • 管理宿主 apk 中所有的插件 apk 信息
  • 为每个插件 apk 创建对应的类加载起和资源管理器,这是最核心最重要的步骤

下面我们就要通过一个插件管理器来模拟这个过程。我们创建一个PluginInfo代表我们每个插件。

/**
 * PluginInfo 插件实体类
 * author:张冠之
 * time: 2017/8/26 下午7:52
 * e-mail: guanzhi.zhang@sojex.cn
 */

public class PluginInfo {
    //插件实体需要管理的资源,也可以管理很多其他的资源
    public DexClassLoader mClassLoader;
    public AssetManager mAsserManager;
    public Resources mResources;
}

类加载器、资源管理器和 Resources 是一个插件中 apk 中需要管理的最基础的类,当然你还可以添加一些其他的信息来管理,比如插件内部的四大组件等,这里的信息越多,说明你设计的插件化框架需要覆盖的功能面越多,功能就越强大,当然复杂性也越高。

有了插件的实体类,我们就需要一个插件管理器PluginManager来管理所有的插件实体类。

/**
 * PluginManager 插件管理器 
 * author:张冠之
 * time: 2017/8/26 下午7:51 
 * e-mail: guanzhi.zhang@sojex.cn
 */

public class PluginManager {
    //单例
    private static volatile PluginManager mInstance;
    private static Context context;
    private static File mOptFile;
    //插件内存存储数据结构
    private static HashMap<String, PluginInfo> mPluginMap;

    private PluginManager(Context context) {
        this.context = context;
        mOptFile = context.getDir("opt", context.MODE_PRIVATE);
        mPluginMap = new HashMap<>();
    }

    //获取单例对象
    public static PluginManager getInstance(Context context) {
        if (mInstance == null) {
            synchronized (PluginManager.class) {
                if (mInstance == null) {
                    mInstance = new PluginManager(context);
                }
            }
        }
        return mInstance;
    }

    //为插件 apk 创建对应的 classLoader
    private static DexClassLoader createPluginDexClassLoader(String apkPath) {
        DexClassLoader classLoader = new DexClassLoader(apkPath,
                mOptFile.getAbsolutePath(), null, null);
        return classLoader;
    }

    //为对应的插件创建AssetManager
    private static AssetManager createPluginAssetManager(String apkPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, apkPath);
            return assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    //为对应的插件创建 Resources 类
    private static Resources createPluginResources(String apkPath) {
        AssetManager assetManager = createPluginAssetManager(apkPath);

        Resources superResources = context.getResources();//宿主应用的 Resources 类
        Resources pluginResources = new Resources(assetManager,
                superResources.getDisplayMetrics(), superResources.getConfiguration());
        return pluginResources;

    }

    //加载插件方法
     public static PluginInfo loadApk(String apkPath) {
        //步骤一
        if (mPluginMap.get(apkPath) != null) {
            return mPluginMap.get(apkPath);
        }
        //步骤二
        PluginInfo pluginInfo = new PluginInfo();
        //步骤三
        pluginInfo.mClassLoader = createPluginDexClassLoader(apkPath);
        //步骤四
        pluginInfo.mAsserManager = createPluginAssetManager(apkPath);
        //步骤五
        pluginInfo.mResources = createPluginResources(apkPath);
        //步骤六
        mPluginMap.put(apkPath,pluginInfo);
        //步骤七
        return pluginInfo;
      }

}

这是一个最基础的插件管理器,对一个插件所必须用到的几个加载类进行了管理。这段源码我们通过loadApk()方法来展开学习。

loadApk(String apkPath)方法是我们插件管理器的入口方法,当我们创建了单例的PluginManager类后,调用该方法即可加载指定 apkPath 路径的插件。这里我们通过一个静态的 HashMap 对象管理插件,步骤一中,如果内存中已经加载过该插件,方法将直接返回这个插件的实体类,这里我们保存 map 的 key 值就是 bundle apk 的路径地址。

如果没有加载过这个插件,将新建一个实体类来存储插件的信息。步骤三通过createPluginDexClassLoader()方法来创建插件的 ClassLoader。

DexClassLoader classLoader = new DexClassLoader(apkPath,
        mOptFile.getAboslutePath(),null,null);

这块的内容我们在之前的 ClassLoader 中已经讲解过了,这里不再继续重复,只需要铭记 DexClassLoader 是我们动态加载未安装到系统 APK 文件的核心。

步骤四中,通过createPluginAssetManager()方法来创建插件 AssetManager。

AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, apkPath);

通过反射构造一个 AssetManager 对象,获取对象的addAssetPath方法,最后将 apkPath 传入该方法,调用方法添加 AssetManager 可管理的资源路径。

步骤五中,通过createPluginResources()创建插件的 Resources 类。

AssetManager assetManager = createPluginAssetManager(apkPath);
Resources superResources = context.getResources();//宿主应用的 Resources 类
Resources pluginResources = new Resources(assetManager,
                superResources.getDisplayMetrics(), superResources.getConfiguration());

Resources 类的创建稍微麻烦点,要先获取插件的 AssetManager ,并通过宿主应用的 Resources 类来构造插件 Resources 类。

最后把创建好的插件实体类存储到 map 中保存,并返回该实体类,完成整个loadApk()的调用。

以上就是一个简单的插件管理器的实现方式,有兴趣的同学可以对此进行拓展,比如在PluginInfo中添加其他信息的管理,比如 Activity,Service 的管理。

源码地址

下面的文章将具体通过 Atlas 的源码来深度学习插件化框架的实现原理。


本文部分内容参考于慕课网实战课程「Android 应用发展趋势必备武器 热修复与插件化」,有兴趣的朋友可以付费学习。
插件化实战课程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容