Android篇

插件化(一):如何加载插件的类

2021-03-16  本文已影响0人  w达不溜w
1.实现思路

Android打包编译过程中会把所有的Java源文件编译成Class文件,然后经过字节码优化处理打包到dex文件中。一个dex文件最多容纳65535个方法,Google推出MutiDex兼容方案,实现原理就是用一个数组存放多个dex文件。
所以,在App启动运行时将我们的插件dexElements与宿主dexElements合并,再将新的dexElements赋值给宿主dexElements。

合并.png
2.Android类加载机制

要实现插件的加载,需要先了解Android中的类加载机制。
Android类加载主要用到下图 几个类


android_classloader.png

ClassLoader为基类,loadClass方法在基类中实现。

BootClassLoader加载SDK中类。

PathClassLoader与DexClassLoader在Android8.0以后实现的功能完全一致,加载应用的类。

BaseDexClassLoader具体实现findClass过程。

先看loadClass方法:

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;
}

loadClass分析:首先执行findLoadedClass()方法,如果类已经加载过直接返回,如果没有加载过会先判断parent是否为空,如果不为空用parent实例来递归加载类(双亲委派机制),如果parent为空则加载Android sdk中的系统类,如果最后还是为空就会调用findClass方法,findClass方法为ClassLoader空实现,具体实现在BaseDexClassLoader中。

我们接着看BaseDexClassLoader的findClass方法。(http://androidos.net.cn/sourcecode 在线看系统源码) API Level: 28

//BaseDexClassLoader.class
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    //——>分析1  pathList.findClass
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException(
                "Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}

——>分析1 调用了 DexPathList的findCLass方法

//DexPathList.class
package dalvik.system;
private Element[] dexElements;

public Class<?> findClass(String name, List<Throwable> suppressed) {
  for (Element element : dexElements) {
    //——>分析2 element.findClass
    Class<?> clazz = element.findClass(name, definingContext, suppressed);
    if (clazz != null) {
      return clazz;
    }
  }

  if (dexElementsSuppressedExceptions != null) {
    suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
  }
  return null;
}

——>分析2 遍历数组dexElements,通过element.findClass查找Class。看下Element:

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

一个Element对应一个dex文件,至此我们找到了类的整个加载过程。

所以我们获取目标dexElements的流程:类加载器—>BaseDexClassLoader对象—>DexPathList对象—>目标dexElements。

3.具体实现
public static void loadPlugin(Context context) {
    /**
     * 宿主dexElements = 宿主dexElements + 插件dexElements
     * 1.获取宿主dexElements
     * 2.获取插件dexElements
     * 3.合并两个dexElements
     * 4.将新的dexElements赋值到宿主dexElements
     */
    try {
        //读取BaseDexClassLoader的pathList字段
        Class<?> dexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField = dexClassLoaderClass.getDeclaredField("pathList");
        pathListField.setAccessible(true);

        //读取DexPathList中的dexElements字段
        Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
        Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);

        //(1)获取宿主的dexElements
        //1.获取宿主ClassLoader实例
        ClassLoader hostClassLoader = context.getClassLoader();
        //2.根据ClassLoader实例获取宿主pathList实例
        Object hostPathList = pathListField.get(hostClassLoader);
        //3.根据宿主pathList实例获取宿主dexElements实例
        Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);

        //(2)获取插件的dexElements
        //1.根据插件apk存放的路径来创建插件ClassLoader
        DexClassLoader pluginClassLoader = new DexClassLoader(pluginApkPath, context.getCacheDir().getAbsolutePath(), null, hostClassLoader);
        //2.根据插件ClassLoader实例获取插件pathList实例
        Object pluginPathList = pathListField.get(pluginClassLoader);
        //3.根据插件pathList实例获取插件dexElements实例
        Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);

        //(3)创建一个新Element数组,合并宿主和插件的dexElements并赋值给宿主的DexElements
        //1.通过反射创建一个新Element数组
        Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginDexElements.length);
        //2.将宿主dexElements拷贝到新dexElements
        System.arraycopy(hostDexElements, 0, newDexElements, 0, hostDexElements.length);
        //3.将插件dexElements拷贝到新dexElements后面
        System.arraycopy(pluginDexElements, 0, newDexElements, hostDexElements.length, pluginDexElements.length);
        //4.将合并好的新dexElements赋值给宿主的DexElements
        dexElementsField.set(hostPathList, newDexElements);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

调用示例:

LoadUtil.loadPlugin(this)
try {
    Class<?> clazz = Class.forName("com.sun.plugin.Test");
    Method method = clazz.getMethod("print");
    method.invoke(null);
} catch (Exception e) {
    e.printStackTrace();
}
上一篇下一篇

猜你喜欢

热点阅读