Android(安卓)插件化分析(5)- 加载外部dex
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会优先查找排在前面的类。所以如果 我们将修改后的类插入到原来有问题的类的前面,那么原来的类将会被新类所覆盖,这就是热更新的基础。
更多相关文章
- Android(安卓)开发佳站3
- 使用MAT查看Android内存泄露
- day2
- Android打开外部DB文件
- Android(安卓)SDCard操作(文件读写,容量计算)
- Hbuild项目Android本地打包
- Sense4+ Android(安卓)4.1.1去除拨号连接线办法
- android skia 解析gif图片
- Flutter 在Android(安卓)Studio中找不到真机设备