Android 插件化能从外部下载apk并加载主要依赖于ClassLoader。

ClassLoder是一个抽象类,其中最重要的是BaseDexClassLoader及其子类PathClassLoader和DexClassLoader.

    public DexClassLoader(String dexPath, String optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(dexPath, new File(optimizedDirectory), libraryPath, parent);    }    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {        super(dexPath, null, librarySearchPath, parent);    }

构造函数一共涉及到四个parent参数

  • dexPath :需要加载的dex的路径
  • optimizedDirectory:缓存加载的dex文件
  • libraryPath: 加载的.so文件路径
  • parent:父类加载器. ClassLoader会优先去从父类加载器去查找类,如果已加载,则直接使用,不过没加载 ,再自己加载,提高了性能。

PathClassLoader和DexClassLoader很相似,区别在于构造函数DexClassLoader 多了一个optimizedDirectory参数,而PathClassLoader会直接传递null给父类。

optimizedDirectory是用来缓存加载的dex文件的,如果为null会直接使用dex原有的路径作为缓存目录。所以DexClassLoader可以缓存指定的路径的dex,而PathClassLoader通常是用来加载Android系统类和应用的类。

大部分逻辑都在他们的父类BaseDexClassLoader里,BaseDexClassLoader里维护了一个DexPathList,顾名思义是Dex的一个集合类,里面维护了一个Element的数组,Element是表示dex或者包含dex的目录。

public class BaseDexClassLoader extends ClassLoader {    // 需要加载的dex列表    private final DexPathList pathList;        public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(parent);        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);    }    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        List suppressedExceptions = new ArrayList();        // 使用pathList对象查找name类        Class c = pathList.findClass(name, suppressedExceptions);        return c;    }}

对于DexPathList,通过其内部的静态方法makeDexElements创建了Element数组以及findClass去查询某个类

private static Element[] makeDexElements(ArrayList files, File optimizedDirectory,                                         ArrayList suppressedExceptions) {    ArrayList elements = new ArrayList();    // 所有从dexPath找到的文件    for (File file : files) {        File zip = null;        DexFile dex = null;        String name = file.getName();        // 如果是文件夹,就直接将路径添加到Element中        if (file.isDirectory()) {            elements.add(new Element(file, true, null, null));        } else if (file.isFile()){            // 如果是文件且文件名以.dex结束            if (name.endsWith(DEX_SUFFIX)) {                try {                    // 直接从.dex文件生成DexFile对象                    dex = loadDexFile(file, optimizedDirectory);                } catch (IOException ex) {                    System.logE("Unable to load dex file: " + file, ex);                }            } else {                zip = file;                try {                    // 从APK/JAR文件中读取dex文件                    dex = loadDexFile(file, optimizedDirectory);                } catch (IOException suppressed) {                    suppressedExceptions.add(suppressed);                }            }        } else {            System.logW("ClassLoader referenced unknown path: " + file);        }        if ((zip != null) || (dex != null)) {            elements.add(new Element(file, false, zip, dex));        }    }    return elements.toArray(new Element[elements.size()]);}public Class findClass(String name, List suppressed) {    // 遍历从dexPath查询到的dex和资源Element    for (Element element : dexElements) {        DexFile dex = element.dexFile;        // 如果当前的Element是dex文件元素        if (dex != null) {            // 使用DexFile.loadClassBinaryName加载类            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);            if (clazz != null) {                return clazz;            }        }    }    if (dexElementsSuppressedExceptions != null) {        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));    }    return null;}

如何使用DexClassLoader加载外部dex

首先我们要创建自己的DexClassLoader ,根据其构造函数,我们需要传入四个参数

    public static ClassLoader extractPlugin(String plugin) {        Context context = PluginApplication.sContext;        File extractFile = null;        try {    // 讲assets下的apk复制到file目录            extractFile = context.getFileStreamPath(plugin);            try (InputStream is = context.getAssets().open(plugin);                 FileOutputStream fos = new FileOutputStream(extractFile)) {                byte[] buffer = new byte[1024];                int count;                while ((count = is.read(buffer)) > 0) {                    fos.write(buffer, 0, count);                }                fos.flush();            }        } catch (IOException e) {            e.printStackTrace();        }        String dexPath = extractFile.getPath();// 将apk解压,把.so放在extractFile.getParent()目录,如果没有.so文件,直接传入null        String libPath = unzipLibraryFile(dexPath, extractFile.getParent());// dex目录作为缓存dex目录        File fileRelease = context.getDir("dex", Context.MODE_PRIVATE);// 创建ClassLoader        return new DexClassLoader(dexPath, fileRelease.getAbsolutePath(), libPath, context.getClassLoader());    }

注意在解压缩库文件的时候,只解压缩对应平台的库文件即可,否则报错!

获取到ClassLoader只要通过反射就能调用插件里的类了

    @Test    public void testPlugin() {        try {            ClassLoader classLoader = PluginLoader.extractPlugin("plugin-debug.apk");            Class clz = classLoader.loadClass("justwen.plugin.PluginDemo");            assertEquals("Hello World", clz.newInstance().toString());        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {            e.printStackTrace();        }    }public class PluginDemo {    @Override    public String toString() {        return "Hello World";    }}

实际上我们也可以将我们的dex 通过ClassLoader的静态方法makeDexElements去生成一个Element数组,然后通过反射,将Element数组插入默认ClassLoader的DexPathList。

如果DexPathList里包含的多个相同的类,ClassLoader会优先查找排在前面的类。所以如果 我们将修改后的类插入到原来有问题的类的前面,那么原来的类将会被新类所覆盖,这就是热更新的基础。

更多相关文章

  1. Android(安卓)开发佳站3
  2. 使用MAT查看Android内存泄露
  3. day2
  4. Android打开外部DB文件
  5. Android(安卓)SDCard操作(文件读写,容量计算)
  6. Hbuild项目Android本地打包
  7. Sense4+ Android(安卓)4.1.1去除拨号连接线办法
  8. android skia 解析gif图片
  9. Flutter 在Android(安卓)Studio中找不到真机设备

随机推荐

  1. mysql通配符(sql 高级过滤)
  2. mysql 计算函数详情
  3. 关于MySQL与Golan分布式事务经典的七种解
  4. 详细聊聊关于Mysql联合查询的那些事儿
  5. mysql事务对效率的影响分析总结
  6. mysql事务隔离级别详情
  7. mysql主从复制的实现步骤
  8. 记一次Mysql不走日期字段索引的原因小结
  9. Mysql关于数据库是否应该使用外键约束详
  10. Android(安卓)- Manifest 文件 详解