加载插件Apk中的类

本文介绍了一种通过类加载器实现的插件化加载机制,包括如何构建DexClassLoader加载插件Apk,并通过反射机制调用插件中的类、方法及属性。此外,还详细解析了如何将插件包的dex加入到宿主应用中。
摘要由CSDN通过智能技术生成

每次使用都利用类加载器加载

将插件Apk打包放到手机中,这里路径是PluginApkPath。然后构建DexClassLoader类加载器加载该Apk,之后可以该类加载器的 loadClass(全类名) 方法来加载apk中的类。之后通过反射来获取他的成员变量和方法。

                DexClassLoader pluginClassLoader = new DexClassLoader(PluginApkPath, getDir("my_dir", MODE_PRIVATE).getAbsolutePath(), null, getClassLoader());
                try {
                    Class<?> cls = pluginClassLoader.loadClass("com.demo.plugin.FixClass");
                    Object obj = cls.newInstance();
                    //反射私有方法
                    Method method = cls.getDeclaredMethod("toast", Context.class);
                    method.setAccessible(true);
                    method.invoke(obj, MainActivity.this);
                    //反射私有变量
                    Field field = cls.getDeclaredField("version");
                    field.setAccessible(true);
                    String version = (String) field.get(obj);
                    System.out.println("version="+version);
                }catch (Exception e) {
                    e.printStackTrace();
                }

一步到位,通过反射将插件包的dex加入到宿主中

系统是通过BootClassLoader加载Android核心库的类,通过PathClassLoader来加载我们写的类。

分析PathClassLoader的loadClass源码,发现loadClass的过程是通过双亲委托机制实现的,先判断是否已经加载过,如果没有加载过,先让父类类加载器去findClass如果有就返回,没有再自己去findClass。

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

PathClassLoader没有重写findClass,在它的父类BaseDexClassLoader可以找到findClass方法的实现。分析这里的源码可以发现,findClass是从pathList中去寻找的,而pathList的类型是DexPathList。

private final DexPathList pathList;

  @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        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;
    }

我们接着看DexPathList的源码,可以看到他的成员变量dexElements存的是Element的数组,这个Element里面就保存这我们apk中dex相关的信息。

/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String zipSeparator = "!/";

    /** class definition context */
    private final ClassLoader definingContext;

    /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private final Element[] dexElements;

    /** List of native library path elements. */
    private final Element[] nativeLibraryPathElements;
    ...
}

Element是DexPathList的内部类,这里的File file指的就是dex文件。

/*package*/ final class DexPathList {
    ...

    /**
     * Element of the dex/resource file path
     */
    /*package*/ static class Element {
        private final File file;
        private final boolean isDirectory;
        private final File zip;
        private final DexFile dexFile;

        private ZipFile zipFile;
        private boolean initialized;

        public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
            this.file = file;
            this.isDirectory = isDirectory;
            this.zip = zip;
            this.dexFile = dexFile;
        }
    }
}

因此,我们可以构一个类加载器去加载插件包,利用反射获取到他的dexElements,然后将它加入到宿主Apk的类加载器的dexElements中即可。

具体实现

public class ClassUtil {
    public static void loadClass(Context context, String apkPath) {
        try {
            //源码分析可知宿主的dex存在dexElements这个数组中,而它属于pathList,而pathList属于BaseDexClassLoader,我们需要通过反射一步步去拿到他,然后将我们插件的dex加入到数组中
            Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
            Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);

            Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = classLoaderClass.getDeclaredField("pathList");
            pathListField.setAccessible(true);

            //宿主的类加载器
            ClassLoader pathClassLoader = context.getClassLoader();
            //获取宿主pathList
            Object hostPathList = pathListField.get(pathClassLoader);
            //通过pathList获取宿主dexElements
            Object[] hostDexElements = (Object[])dexElementsField.get(hostPathList);

            //构造插件的类加载器
            ClassLoader pluginClassLoader = new DexClassLoader(apkPath, context.getDir("my_dir", 0).getAbsolutePath(), null, pathClassLoader);
            //获取插件的pathList
            Object pluginPathList = pathListField.get(pluginClassLoader);
            //通过pathList获取插件dexElements
            Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
            //构建一个新的数组将宿主和插件的Elements都放进来
            Object[] newElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginDexElements.length);

            //从hostDexElements的0号位开始拷贝hostDexElements.length个数据,到newElements的0号位开始插入
            System.arraycopy(hostDexElements, 0, newElements, 0, hostDexElements.length);
            //pluginDexElements中的数据到newElements
            System.arraycopy(pluginDexElements, 0, newElements, hostDexElements.length, pluginDexElements.length);

            dexElementsField.set(hostPathList, newElements);

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

Demo地址:MyLoadPlugin: 加载插件包中的类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值