美文网首页 移动 前端 Python Android Java
插件化(一)插件化思想与类加载

插件化(一)插件化思想与类加载

作者: zcwfeng | 来源:发表于2020-11-25 17:56 被阅读0次

最开始的起源:插件化技术最初源于免安装运行 apk 的想法。

免安装的 apk 我们称它为 插件
支持插件的 app 我们称它为 宿主

免安装的 apk 我们称它为 插件
支持插件的 app 我们称它为 宿主

插件话解决的问题

  1. APP的功能模块越来越多,体积越来越大
  2. 模块之间的耦合度高,协同开发沟通成本越来越大
  3. 方法数目可能超过65535,APP占用的内存过大
  4. 应用之间的互相调用

由于维护成本高,技术难点大,大公司一线公司用的比较多,而且兼容问题比较多,所以维护起来难点大。

插件话与组件化, 模块化的区别

组件化开发就是将一个app分成多个模块,每个模块都是一个组件,开发的 过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发 布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
再具体一些,就是 组件化分模块纵向依赖公共库,横向彼此之间没有直接依赖关系。

插件化开发和组件化略有不同,插件化开发是将整个app拆分成多个模块, 这些模块包括一个宿主和多个插件,每个模块都是一个apk,最终打包的时 候宿主apk和插件apk分开打包。

模块化,组件化和模块化似乎类似。但是目的不一样,模块话是业务为主,用业务划分模块,但是传统的这种做法导致多个业务关联耦合。

插件话的实现思路,面临的几个难题

  1. 如何加载插件的类?
  2. 如何启动插件的四大组件?
  3. 如何加载插件的资源?

可以做的功能,换肤,热修复,多开,ABTest

类声明周期简单看

我们抽象一个类Person
我们抽象一个类Car
这些都是类Class
我们的Class也是类Class

加载------> 验证 ----->  准备------> 解析
                                    |->初始化->使用->卸载

加载阶段,虚拟机做三件事:
1.通过一个类的全限定名来获取定义此类的二 进制字节流。
2.将这个字节流所代表的静态存储结构转化为 方法区域的运行时数据结构。
3.在Java堆中生成一个代表这个类的Class对象, 作为方法区域数据的访问入口

为什么我们说反射会有一定的降低效率

  1. 产生大量的临时对象
  2. 检查可见性
  3. 会生成字节码 --- 没有优化
  4. 类型转换

ClassLoader 继承的关系

ClassLoader 继承的关系.png

PathClassLoader & DexClassLoader

在8.0(API 26)之前,它们二者的唯一区别是 第二个参数 optimizedDirectory,这个参数的意 思是生成的 odex(优化的dex)存放的路径。
在8.0(API 26)及之后,二者就完全一样了。
高版本合并了,所以区别不大了

这就是兼容问题,以后有没有,每次更新都要查看,所以说维护成本高

public class DexClassLoader extends BaseDexClassLoader {
    
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}
package dalvik.system;

public class PathClassLoader extends BaseDexClassLoader {
    
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

写一个测试代码:

private void printClassLoader(){
        ClassLoader classLoader = getClassLoader();
        while (classLoader != null) {
            Log.e("zcw_plugin", "classLoader:" + classLoader);
            classLoader = classLoader.getParent();
        }

        //pathClassLoader 和 BootClassLoader 分别加载什么类
        Log.e("zcw_plugin", "Activity 的 classLoader:" + Activity.class.getClassLoader());
        Log.e("zcw_plugin", "Activity 的 classLoader:" + AppCompatActivity.class.getClassLoader());

    }

打印

2020-11-25 12:18:01.911 7566-7566/top.zcwfeng.plugin E/zcw_plugin: classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/top.zcwfeng.plugin-FzOugxhVZye2nIoxyC1IPg==/base.apk"],nativeLibraryDirectories=[/data/app/top.zcwfeng.plugin-FzOugxhVZye2nIoxyC1IPg==/lib/x86, /system/lib]]]
2020-11-25 12:18:01.911 7566-7566/top.zcwfeng.plugin E/zcw_plugin: classLoader:java.lang.BootClassLoader@e9e6661
2020-11-25 12:18:01.911 7566-7566/top.zcwfeng.plugin E/zcw_plugin: Activity 的 classLoader:java.lang.BootClassLoader@e9e6661
2020-11-25 12:18:01.912 7566-7566/top.zcwfeng.plugin E/zcw_plugin: Activity 的 classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/top.zcwfeng.plugin-FzOugxhVZye2nIoxyC1IPg==/base.apk"],nativeLibraryDirectories=[/data/app/top.zcwfeng.plugin-FzOugxhVZye2nIoxyC1IPg==/lib/x86, /system/lib]]]

PathClassLoader --》 parent(ClassLoader类型的对象),BootClassLoader 没有parent

PathClassLoader --- 应用的 类 -- 第三方库
BootClassLoader --- SDK的类

Activity 是SDK 而不是FrameWork,而AppCompatActivity 是依赖库中的
类似Glide 都是第三方集成的依赖。

测试加载dex

dex 的文件生成命令

dx --dex --output=output.dex input.class

dx --dex --output=test.dex top/zcwfeng/plugin/Test.class 
----------
source class

package top.zcwfeng.plugin;

import android.util.Log;

public class Test {
    public Test() {
    }

    public static void print() {
        Log.e("zcw_plugin", "print:启动插件中方法");
    }
}

load dex

private void testLoadDex(){
        DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/test.dex",
                MainActivity.this.getCacheDir().getAbsolutePath(),
                null,
                MainActivity.this.getClassLoader());
        try {
            Class<?> clazz = dexClassLoader.loadClass("top.zcwfeng.plugin.Test");
            Method clazzMethod = clazz.getMethod("print");
            clazzMethod.invoke(null);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

ClassLoader.Java 核心,双亲委派
先判断是否已经加载,如果没有委派双亲去加载,如果没有加载出来那么在自己查找

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

作用
1.避免重复加载
2.安全考虑,不能攥改

双亲委派.png

Hook 点

查找 Hook 反射 启动插件的类

一个dexFile -> 对应一个dex文件
Element --> 对应 dexFile 而 一个APK-> 多个dex文件

Elements[] dexElements ---> 一个app的所有class文件都在dexElements 里面

关注这些类的流程

ClassLoader----DexPathList---Element----DexFile----BootClassLoader---VMClassLoader----Class

因为 宿主的MainActivity 在 宿主 的 dexElements 里面

1.获取宿主dexElements
2.获取插件dexElements
3.合并两个dexElements
4.将新的dexElements 赋值到 宿主dexElements

合并.png

ps:热修复原理类似,就是更换加载顺序,把修复好的elements放在未曾修复的前面加载,就不会在加载一个错误的了

目标:dexElements -- DexPathList类的对象 -- BaseDexClassLoader的对象,类加载器

获取的是宿主的类加载器 --- 反射 dexElements 宿主

获取的是插件的类加载器 --- 反射 dexElements 插件

public
class LoadUtil {
    private final static String apkPath = "/sdcard/plugin-debug.apk";

    public static void load(Context context) {
        /**
         * 宿主dexElements = 宿主dexElements + 插件dexElements
         *
         * 1.获取宿主dexElements
         * 2.获取插件dexElements
         * 3.合并两个dexElements
         * 4.将新的dexElements 赋值到 宿主dexElements
         *
         * 目标:dexElements  -- DexPathList类的对象 -- BaseDexClassLoader的对象,类加载器
         *
         * 获取的是宿主的类加载器  --- 反射 dexElements  宿主
         *
         * 获取的是插件的类加载器  --- 反射 dexElements  插件
         */

        try {
            Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = clazz.getDeclaredField("pathList");// 只和类有关和对象无关
            pathListField.setAccessible(true);

            Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
            Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);

            // 宿主的类加载器
            ClassLoader pathClassLoader = context.getClassLoader();
            // DexPathList 类对象
            Object hostPathList = pathListField.get(pathClassLoader);
            // 宿主的dexElements
            Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);


            // plugin的类加载器
            ClassLoader dexClassLoader = new DexClassLoader(apkPath,
                    context.getCacheDir().getAbsolutePath(),
                    null
                    , pathClassLoader);//parent 考虑适配问题,不要传null

            // DexPathList 类对象
            Object pluginPathList = pathListField.get(dexClassLoader);
            // plugin的dexElements
            Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);


            //将新的dexElements 赋值到 宿主dexElements
            // 不能直接Object[] obj = new Object[] 因为我们要把obj放到反射的elements里面去,所以不行
            Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
                    hostDexElements.length + pluginDexElements.length);

            System.arraycopy(hostDexElements, 0, newDexElements, 0,
                    hostDexElements.length);
            System.arraycopy(pluginDexElements, 0, newDexElements, hostDexElements.length,
                    pluginDexElements.length);

            //赋值
            dexElementsField.set(hostPathList, newDexElements);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

加载 apk插件在application

        LoadUtil.load(this);

写测试方法

private void testLoadDex(){
        DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/test.dex",
                MainActivity.this.getCacheDir().getAbsolutePath(),
                null,
                MainActivity.this.getClassLoader());
        try {
            Class<?> clazz = dexClassLoader.loadClass("top.zcwfeng.plugin.Test");
            Method clazzMethod = clazz.getMethod("print");
            clazzMethod.invoke(null);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

各大插件的介绍和对比

我们在选择开源框架的时候,需要根据自身的需求来,如果加载的插件不需要和宿主有任何耦合,也无须和宿主进行通信,比如加载第三方 App,那么推荐使用 RePlugin,其他的情况推荐使用 VirtualApk。

特性 DynamicAPK dynamic-load-apk Small DroidPlugin RePlugin VirtualAPK
支持四大组件 只支持Activity 只支持Activity 只支持Activity 全支持 全支持 全支持
组件无需在宿主manifest中预注册 ×
插件可以依赖宿主 ×
支持PendingIntent × × ×
Android特性支持 大部分 大部分 大部分 几乎全部 几乎全部 几乎全部
兼容性适配 一般 一般 中等
插件构建 部署aapt Gradle插件 Gradle插件 Gradle插件

相关文章

  • 插件化(三) 插件资源加载

    大话插件化系列目录插件化(一) 插件化思想与类加载[https://www.jianshu.com/p/4318c...

  • 插件化(一)插件化思想与类加载

    最开始的起源:插件化技术最初源于免安装运行 apk 的想法。 免安装的 apk 我们称它为 插件支持插件的 app...

  • 插件化Activity&Receiver组件

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

  • 插件化笔记

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

  • 13.VirtualApk原理总结

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

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

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

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

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

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

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

  • Android插件化

    插件化涉及的东西很多,下面从基础知识、插件化技术和主流的插件化框架来介绍 基础知识 类加载器原理 反射原理 代理模...

  • Android组件化开发,Small应用实践

    插件化与组件化 插件化就是将一个app分为一个宿主和多个模块(插件),宿主是被真正安装到设备的apk,负责加载插件...

网友评论

    本文标题:插件化(一)插件化思想与类加载

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