美文网首页
插件化(二)-dex文件合并

插件化(二)-dex文件合并

作者: 涛涛123759 | 来源:发表于2022-12-08 18:56 被阅读0次

    Android知识总结

    一、加载插件类

    那我们如何使用类加载器去加载一个类呢?
    非常的简单,例如:我们有一个apk文件,路径是 apkPath,然后里面有个类 com.enjoy.plugin.Test,那么我们可以通过如下方式去加载 Test 类:

    • 插件里的类
    public class Test {
        public static void print() {
            Log.e("--->", "print: 启动插件方法");
        }
    }
    
    DexClassLoader dexClassLoader = newDexClassLoader(dexPath,context.getCacheDir().getAbsolutePath(), null,context.getClassLoader());
    try {
        Class<?> clazz = dexClassLoader.loadClass("com.enjoy.plugin.Test");
        Method print = clazz.getMethod("print");
        print.invoke(null);
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    

    PathClassLoader dexClassLoader = new PathClassLoader("/sdcard/test.dex",null);
    try {
        Class<?> clazz = dexClassLoader.loadClass("com.enjoy.plugin.Test");
        Method print = clazz.getMethod("print");
        print.invoke(null);
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    因为我们需要将插件的 dex 文件加载到宿主里面,所以我们接下来分析源码,看 DexClassLoader 类加载器到底是怎么加载一个 apk 的 dex 文件的。

    通过查找发现,DexClassLoader 类中没有 loadClass 方法,一路向上查找,最后在 ClassLoader 类中找到了改方法,源码如下:(后续源码如无标明,都是 API 26 Android 8.0

    // /libcore/ojluni/src/main/java/java/lang/ClassLoader.java
    //parent 是父类 BootClassLoader
    private final ClassLoader parent;
    
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
        // 检测这个类是否已经被加载 --> 1
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //如果parent不为null,则调用parent的loadClass进行加载
                    c = parent.loadClass(name, false);
                } else {
                    //正常情况下不会走这儿,因为 BootClassLoader 重写了 loadClass 方法,结束了递归
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }
            if (c == null) {
                // 如果仍然找不到,就调用 findClass 去查找 --> 2
                long t1 = System.nanoTime();
                c = findClass(name);
            }
        }
        return c;
    }
    
    // -->1 检测这个类是否已经被加载
    protected final Class<?> findLoadedClass(String name) {
        ClassLoader loader;
        if (this == BootClassLoader.getInstance())
            loader = null;
        else
            loader = this;
        // 最后通过 native 方法实现查找
        return VMClassLoader.findLoadedClass(loader, name);
    }
    
    
    
    // -->2 加载器一般都会重写这个方法,定义自己的加载规则
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
    // /libcore/libart/src/main/java/java/lang/VMClassLoader.java
    native static Class findLoadedClass(ClassLoader cl, String name);
    

    首先检测这个类是否已经被加载了,如果已经加载了,直接获取并返回。如果没有被加载,parent 不为 null,则调用parent的loadClass进行加载,依次递归,如果找到了或者加载了就返回,如果即没找到也加载不了,才自己去加载。这个过程就是我们常说的 双亲委托机制。

    根据前面的打印结果可以知道,BootClassLoader 是最后一个加载器,所以我们来看下它是如何结束向上递归查找
    的。

    class BootClassLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            return Class.classForName(name, false, null);
        }
        
        @Override
        protected Class<?> loadClass(String className, boolean resolve)throws ClassNotFoundException {
            Class<?> clazz = findLoadedClass(className);
            if (clazz == null) {
                clazz = findClass(className);
            }
            return clazz;
        }
    }
    

    我们发现 BootClassLoader 重写了 findClass 和 loadClass 方法,并且在 loadClass 方法中,不再获取 parent,从而结束了递归。

    接着我们再来看下,在所有 parent 都没加载成功的情况下,DexClassLoader 是如何加载的。通过查找我们发现在它的父类 BaseDexClassLoader 中,重写了 findClass 方法。

    // /libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(parent);
        // 初始化 pathList
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 在 pathList 中查找指定的 Class
        Class c = pathList.findClass(name, suppressedExceptions);
        return c;
    }
    

    接着再来看 DexPathList 类中的 findClass 方法。

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

    进入Element #findClass方法

    private final DexFile dexFile;
    
    public Class<?> findClass(String name, ClassLoader definingContext,
            List<Throwable> suppressed) {
        return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                : null;
    }
    

    接下来看DexFile文件

    //native 方法
    private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,  DexFile dexFile)
    
    @UnsupportedAppUsage
    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }
    
    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
                    //进入 native 进行加载
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
    
    • 类加载时序图


    我们发现 Class 对象就是从 Element 中获得的,而每一个 Element 就对应一个 DexFile 文件,这个 DexFile 文件就是我们的 dex 文件,因为我们的 dex 文件可能有多个,所以这儿使用数组 Element[]。到这儿我们的思路就出来了,分为以下几步:

    • 1、创建插件的 DexClassLoader 类加载器,然后通过反射获取插件的 dexElements 值。
    • 2、获取宿主的 PathClassLoader 类加载器,然后通过反射获取宿主的 dexElements 值。
    • 3、合并宿主的 dexElements 与 插件的 dexElements,生成新的 Element[]。
    • 4、最后通过反射将新的 Element[] 赋值给宿主的 dexElements 。

    具体代码如下:

    public class LoadUtil {
        private final static String apkPath = "/sdcard/plugin-debug.apk";
    
        public static void loadClass(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);
    
                // 插件的 类加载器
                ClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(), //优化存放的路径
                        null, pathClassLoader);
                // DexPathList类的对象
                Object pluginPathList = pathListField.get(dexClassLoader);
                // 插件的 dexElements
                Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
    
                // 宿主dexElements = 宿主dexElements + 插件dexElements
                // 创建一个新数组
                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);
    
                // 赋值
                // hostDexElements = newDexElements
                dexElementsField.set(hostPathList, newDexElements);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 注意:Android10 apk 放到 /Sdcard 中会报错,放/data/data/.../cache 私有目录中就可以了。

    相关文章

      网友评论

          本文标题:插件化(二)-dex文件合并

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