美文网首页
30. 插件化实现方式-加载插件中的类

30. 插件化实现方式-加载插件中的类

作者: 任振铭 | 来源:发表于2021-02-06 08:33 被阅读0次

什么是插件化

插件化就是以插件下发的到本地的方式,然后通过宿主apk加载插件apk以实现相应的功能的方法,很多大型项目都采用了插件化的方式,如支付宝,微信,甚至最近在研究广点通广告sdk的时候偶然发现,连广点通广告sdk都使用了插件化开发(虽然广点通广告的主流地位已被取代,但是不排除它的技术含量是最高的)

插件化的好处

  1. 通过插件下发的方式,减小安装包体积
  2. 降低模块耦合度,提高协同开发效率
  3. 缓解方法数目可能超过65535的风险
  4. 为应用之间的互相调用提供方便

插件化与组件化

插件化开发是将整个app拆分成多个模块, 这些模块包括一个宿主和多个插件,每个模块都是一个apk,最终打包的时 候宿主apk和插件apk分开打包。
组件化开发同样是将一个app分成多个模块,每个模块都是一个组件,开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发 布的时候是将这些组件合并统一成一个apk

如何加载插件中的类

安卓中的类加载器.png

要知道怎么加载类,首先要了解类加载器,其次还要了解双亲委托机制。

PathClassLoader和DexClassLoader的区别

安卓中加载器的继承关系如上,在系统设计的初期,安卓系统考虑的是PathClassLoader用于加载正在运行中的apk,如你的app从启动到执行各种功能,它的内部都是通过PathClassLoader加载;而如果你要加载一个没有运行的apk中的类,安卓希望你使用DexClassLoader,虽然二者都是继承自BaseDexClassLoader,并且区别不大,但这样区分二者的功能也更加清晰明了。但是随着版本的更新,两者的区别逐渐被抹杀,已经到了可以相互替换的地步。在8.0(API 26)之前,它们二者的唯一区别是 第二个参数 optimizedDirectory,这个参数的意 思是生成的 odex(优化的dex)存放的路径。在8.0(API 26)及之后,二者就完全一样了

BootClassLoader

除特殊说明外,这些源码都是api28
BootClassLoader 是 PathClassLoader 的 parent,这里要注意 parent 与父类的区别。

ClassLoader classLoader = getClassLoader();
while (classLoader != null) {
    System.out.println("classLoader = " + classLoader);
    classLoader = classLoader.getParent();
}
ClassLoader activityClassLoader = Activity.class.getClassLoader();
System.out.println("activityClassLoader = " + activityClassLoader);
类加载

从ClassLoader的loadClass方法跟下去,会找到BootClassLoader的findClass方法

    .....
    private final DexPathList pathList;
    .....
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        .....
        Class c = pathList.findClass(name, suppressedExceptions);
        .....
        return c;
    }

然后进入这里,我们只要知道,一个Element对应一个dex文件即可,因为一个app可能包含多个dex

    private Element[] dexElements
    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
        ......
        return null;
    }

可以看到class就是从这个dexElements数组中加载出来的。那么我们可以考虑下,宿主apk要调用插件apk的class,是不是可以把插件apk的dexElements和宿主的dexElements合并呢?当然可以

开始合并dexElements并加载类

a.创建插件的 DexClassLoader 类加载器,然后通过反射获取插件的 dexElements 值
b.获取宿主的 PathClassLoader 类加载器,然后通过反射获取宿主的 dexElements 值。
c.合并宿主的 dexElements 与 插件的 dexElements,生成新的 Element[]。
d.最后通过反射将新的 Element[] 赋值给宿主的 dexElements

        //3.获取BaseDexClassLoader对象,宿主的classloader
        ClassLoader classLoader = context.getClassLoader();

        //2.获取DexPathList对象,需要用到BaseDexClassLoader对象
        Class<?> baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField = baseDexClassLoaderClass.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathListObj = pathListField.get(classLoader);

        //1.获取dexElements对象(宿主的),需要用到DexPathList对象
        Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
        Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        Object[] dexElementsObj = (Object[]) dexElementsField.get(pathListObj);

        //4.获取插件的dexElements对象,需要用到DexPathList对象,由上边的123部反推,DexPathList对象需要插件
        //的BaseDexClassLoader对象,那么我们需要构建一个ClassLoader去加载插件
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath,
                context.getCacheDir().getAbsolutePath(), null, classLoader);

        //5.插件的dexElements对象
        Object pluginPathListObj = pathListField.get(dexClassLoader);
        Object[] pluginDexElementObj = (Object[]) dexElementsField.get(pluginPathListObj);


        // 创建一个新数组
        Object[] newDexElements = (Object[]) Array.newInstance(dexElementsObj.getClass().getComponentType(),
                dexElementsObj.length + pluginDexElementObj.length);

        System.arraycopy(dexElementsObj, 0, newDexElements,
                0, dexElementsObj.length);
        System.arraycopy(pluginDexElementObj, 0, newDexElements,
                dexElementsObj.length, pluginDexElementObj.length);

        // 赋值
        dexElementsField.set(pathListObj, newDexElements);

那么接下来你就可以通过反射调用到插件中的类了

Class<?> aClass = Class.forName("com.rzm.plugin.PluginClass1");
Method print = aClass.getDeclaredMethod("print");
Object o = aClass.newInstance();
print.invoke(o);
System.out.println("MainActivity o = " + o);

相关文章

  • 30. 插件化实现方式-加载插件中的类

    什么是插件化 插件化就是以插件下发的到本地的方式,然后通过宿主apk加载插件apk以实现相应的功能的方法,很多大型...

  • 插件化Activity&Receiver组件

    插件化框架实现:基于kotlin的插件化框架 插件化组件的问题 通过类加载可以实现加载插件的代码,但是在Andro...

  • 从零开始实现一个插件化框架(二)

    上一篇讲了插件化的概念和类加载机制,并实现了从插件apk中合并,并加载一个类。不知道大家还记不记得,实现插件化,只...

  • 从零开始实现一个插件化框架(二)

    上一篇讲了插件化的概念和类加载机制,并实现了从插件apk中合并,并加载一个类。不知道大家还记不记得,实现插件化,只...

  • 13.VirtualApk原理总结

    插件化的核心之处,一言以蔽之,就是插件中类和资源的加载,类通过构造插件对应的ClassLoader加载,而插件资源...

  • Android插件化原理(二):Activity的插件化

      上一节插件类的加载中我们解决了插件类加载的问题,插件中的类在需要的时候可以正常被加载并实例化,但是对于四大组件...

  • 插件化类加载

    插件化框架实现:基于kotlin的插件化框架 Java类加载 我们知道Java代码通过编译成class文件后,需要...

  • 金蝶云苍穹如何设置页面插件?

    注册方式 注册JAVA插件 注册JavaScript插件 实现方式 Java实现方式 实现步骤 新建插件类 注册插...

  • 插件化

    加载插件中的类 1、创建插件的DexClassLoader类加载器,通过反射获取插件的dexElements2、获...

  • 插件化笔记

    看这个就够了啊,深入理解Android插件化技术 插件化技术核心 类的加载机制和反射机制。 类加载 https:/...

网友评论

      本文标题:30. 插件化实现方式-加载插件中的类

      本文链接:https://www.haomeiwen.com/subject/wapntltx.html