加载插件中类

748 阅读3分钟

如果文章有问题,请及时指出

一 DexClassLoader 和 PathClassLoader的区别

android 28源码 [https://www.androidos.net.cn/sourcecode](https://www.androidos.net.cn/sourcecode)
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}
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);
    }
}
android28:DexClassLoader和PatchCloasLoader都是继承BaseDexClassLoader,只是构建方法中参数区别,使用无区别。
android 26源码
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}
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);
    }
}
android 26源码:DexClassLoader可以可以设置optimizedDirectory参数。PathClassLoader不可以,只为空。android26及以后,optimizedDirectory就不用了,没有传给DexPathList。

二 Android 类加载器

android中总共3中类加载器:
BootClassLoader:加载sdk下类
PathClassLoader:加载apk中类,依赖库, 加载自定义apk中类
DexClassLoader:加载自定义apk中类

1 使用的类加载器

public void click(View view) {
    ClassLoader classLoader = getClassLoader();
    while(classLoader != null) {
        Log.i(TAG, "click: " + classLoader);
        classLoader = classLoader.getParent();
    }
    Log.i(TAG, "click: Activity.class " + Activity.class.getClassLoader());
    Log.i(TAG, "click: String.class " + String.class.getClassLoader());
    Log.i(TAG, "click: Context.class " + Context.class.getClassLoader());
    Log.i(TAG, "click: View.class " + View.class.getClassLoader());
    Log.i(TAG, "click: ActivityCompat.class " + ActivityCompat.class.getClassLoader());
    Log.i(TAG, "click: AppCompatActivity.class " + AppCompatActivity.class.getClassLoader());
    Log.i(TAG, "click: MainActivity.class" + this.getClassLoader());
}
日志如下:
2020-08-13 11:38:03.740 30845-30845/com.example.myplugintest I/liuwei13: click: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.myplugintest-9D9w8JkKF_M-NbI5eVEq7g==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myplugintest-9D9w8JkKF_M-NbI5eVEq7g==/lib/arm64, /system/lib64, /system/product/lib64]]]
2020-08-13 11:38:03.740 30845-30845/com.example.myplugintest I/liuwei13: click: java.lang.BootClassLoader@4d14932
2020-08-13 11:38:03.740 30845-30845/com.example.myplugintest I/liuwei13: click: Activity.class java.lang.BootClassLoader@4d14932
2020-08-13 11:38:03.740 30845-30845/com.example.myplugintest I/liuwei13: click: String.class java.lang.BootClassLoader@4d14932
2020-08-13 11:38:03.740 30845-30845/com.example.myplugintest I/liuwei13: click: Context.class java.lang.BootClassLoader@4d14932
2020-08-13 11:38:03.740 30845-30845/com.example.myplugintest I/liuwei13: click: View.class java.lang.BootClassLoader@4d14932
2020-08-13 11:38:03.741 30845-30845/com.example.myplugintest I/liuwei13: click: ActivityCompat.class dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.myplugintest-9D9w8JkKF_M-NbI5eVEq7g==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myplugintest-9D9w8JkKF_M-NbI5eVEq7g==/lib/arm64, /system/lib64, /system/product/lib64]]]
2020-08-13 11:38:03.741 30845-30845/com.example.myplugintest I/liuwei13: click: AppCompatActivity.class dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.myplugintest-9D9w8JkKF_M-NbI5eVEq7g==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myplugintest-9D9w8JkKF_M-NbI5eVEq7g==/lib/arm64, /system/lib64, /system/product/lib64]]]
2020-08-14 16:21:33.557 9161-9161/com.example.myplugintest I/liuwei13: click: MainActivity.classdalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.myplugintest-2NFvCRXDe1LWgjfRoGlcPg==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.myplugintest-2NFvCRXDe1LWgjfRoGlcPg==/lib/arm64, /system/lib64, /system/product/lib64]]]
PathClassLoader的parent是BootClassLoader。
SDK中类由BootClassLoader加载:
Activity.class、Context.class、View.class、String.class 等类加载器 java.lang.BootClassLoader@4d14932
依赖库中类由PathClassLoader加载:
AppCompatActivity.class、ActivityCompat.class类加载器 dalvik.system.PathClassLoader

2 android类加载过程

双亲委派机制:
PathClassLoader的父加载器为BootClassLoader。我们使用DexClassLoader加载插件,把patchClassLoader设置成parent。
这样就形成了一个链表。大致流程如下:

版本名称: Pie API Level: 28
public abstract class ClassLoader {

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            // 1 先查找是否已经加载了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                         // 2 调用父加载器的loadClass
                        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.
                    // 3 自己加载类
                    c = findClass(name);
                }
            }
            return c;
    }
}
BootClassLoader重写了loadClass方法。
class BootClassLoader extends ClassLoader {
.......
    @Override
    protected Class<?> loadClass(String className, boolean resolve)
           throws ClassNotFoundException {
        // 1 查找已经加载的类
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
             // 2 自己加载类
            clazz = findClass(className);
        }

        return clazz;
    }
......
}

类加载器类图:

三 加载插件中类

插件加载思路:

1 自己定义DexClassloader加载apk,获得dexElements 。(这里也可以用PathClassLoader加载apk)

2 获得宿主的dexElementst,与1 中dexElements合并成一个数组。
3 把合并的数组设置给宿主。
**加载插件类的源码:注意sdcard权限**
private void installPlugin() {

    try {
        // 1 获取dalvik.system.DexPathList.dexElements
        Class dexPathListClass = Class.forName("dalvik.system.DexPathList");
        Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);

        // 2 获取dalvik.system.BaseDexClassLoader.pathList
        Class baseClass = Class.forName("dalvik.system.BaseDexClassLoader");
        Field pathListField = baseClass.getDeclaredField("pathList");
        pathListField.setAccessible(true);

        // 3 获得宿主dexElements数组对象
        PathClassLoader classLoader = (PathClassLoader)getClassLoader();
        Object hostPathList = pathListField.get(classLoader);
        Object[] hostDexElements = (Object[])dexElementsField.get(hostPathList);

        // 4 获得插件的dexElements数组对象
        String apkPath = "/sdcard/plugin.apk";  // 插件apk路径
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath, getCacheDir().getAbsolutePath(), null, classLoader);
        Object pluginPathList = pathListField.get(dexClassLoader);
        Object[] pluginDexElements = (Object[])dexElementsField.get(pluginPathList);

        // 5 合并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);

        // 把合并后的dexElements数组对象,设置到宿主classLoader
        dexElementsField.set(hostPathList, newDexElements);

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