Android中的dex、apk、ClassLoader详解
来源:互联网 发布:python循环读取文件 编辑:程序博客网 时间:2024/05/22 13:26
dalvik加载、运行过程
我们编写java代码都是.java格式的,但是jvm并不能识别.java文件,它只能加载、执行.class文件,所以我们要通过javac命令将.java文件编译成.class文件,然后通过java命令运行.class文件。其实,如果用C或者Python编写的程序正确转换成.class文件后,java虚拟机也是可以识别运行的。
dalvik与jvm差不多,区别就是dalvik只能加载、运行.dex文件(至于如何识别、运行,后面会讲到)。我们的Android程序也是用java编写的,生成的也是.java文件,所以需要把.java文件转换成.dex文件,dalvik才能执行。IDE编译、打包的过程,就是将.java文件转换成.dex文件的过程,我们可以简单看一下编译过程,加深理解。
下面是官方介绍的打包流程图:
总结一下,主要就是这几步:
1、根据res目录下的资源文件、AndroidManifest.xml生成R.java文件;
2、处理aidl,生成对应的java文件,如果没有aidl,则跳过;
3、将前两步生成的java文件和src目录下源码一起编译成class文件;
4、通过class文件生成成dex文件;
5、将资源文件和dex文件一起打包,生成初始apk;
6、对初始apk签名 ;
由此可见,项目编译后,主要结晶就是dex文件。apk的安装过程,就是把apk解压成第4步中的dex文件和原始资源文件(比如图片),运行过程就是dalvik加载、运行dex文件的过程。这里有两个过程,一个是加载,一个是运行,它们又是怎样运作的呢?
dex文件的加载是通过DexClassLoader、PathClassLoader等类来完成的,下面将会从源码角度对这个过程详细分析,这也是热修复技术、插件化技术的核心。
dex的运行就涉及到比较底层的东西了,本文只做一定介绍,了解一下dex文件的大概。
Dex文件
通过命令“javac HelloWorld.java”可以生成HelloWorld.class文件。
再通过命令“dx –dex –output=HelloWorld.dex HelloWorld.class”就会生成HelloWorld.dex文件了。
我们通过十六进制文本编辑器打开HelloWorld.dex文件,如下图:
注意看下面的“Name、Value”,这就是dex文件的标准格式。就像通信协议一样,dalvik虚拟机读到什么内容,就按照预定好的协议执行,这就是dalvik运行dex文件的过程。
ClassLoader
先把我们需要分析的类列出来,捋一捋继承关系、类的主要作用。
ClassLoader
所有XXXClassLoader的基类,负责加载apk/dex/jar文件;
BootClassLoader
继承自ClassLoader,负责加载Android系统类库,我们不会用到;
BaseDexClassLoader
继承自ClassLoader,看名字就知道是对类加载的抽象;
PathClassLoader
继承自BaseDexClassLoader,负责加载宿主apk/dex/jar文件;
DexClassLoader
继承自BaseDexClassLoader,这个比较灵活,每个参数都可以自定义,我们一般用这个来加载自定义的apk/dex/jar文件;
DexPathList
这个类有两个作用:
① 把dex文件解压到宿主程序的私有目录中,因为jvm只能运行宿主程序的dex文件;
② 通过apk/dex/jar文件生成Element[]数组,方便ClassLoader使用;
由于BootClassLoader是加载系统类库的,我们就不分析了。我们主要分析PathClassLoader加载apk中的dex文件,分析完这个过程,自定义一个ClassLoader来加载自定义dex文件就不成问题了。
1、PathClassLoader
先看构造方法:
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { // 调用父类的构造方法 super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { // 调用父类的构造方法 super(dexPath, null, libraryPath, parent); }}// 参数dexPath:待加载的apk/dex/jar文件路径;// 参数optimizedDirectory:dex的输出路径,将apk/dex/jar解压出dex文件,复制到指定路径,用于dalvik运行// 参数librarySearchPath:加载时候需要用到的lib库,这个一般不用,可以传入Null// 参数parent:指定父加载器public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.originalPath = dexPath; this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);}
这里有两个“疑点”,一是PathClassLoader是无法指定optimizedDirectory参数的,也就是说,无法保证解压出来的dex文件在宿主程序中,dalvik就无法运行。另一个就是new一个对象还必须传一个父类对象作为参数,为什么呢?下面分析loadClass()方法时再说明。
我们再看一下加载的方法,加载方法在基类ClassLoader中:
// 通过类名加载protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { // 在已加载的类中查找 Class<?> clazz = findLoadedClass(className); if (clazz == null) { ClassNotFoundException suppressed = null; try { // 如果没有,就让parent去加载 clazz = parent.loadClass(className, false); } catch (ClassNotFoundException e) { suppressed = e; } if (clazz == null) { try { // 如果parent也没有加载到,就自己加载 clazz = findClass(className); } catch (ClassNotFoundException e) { e.addSuppressed(suppressed); throw e; } } } return clazz;}
逻辑是这样的:
1、先去已加载的列表中查找,如果有(已经加载过),就直接返回;如果没有,就让parent去加载;
2、父类仍旧是调用基类ClassLoader的loadClass()方法(如果parent没有重写该方法,一般parent都会传系统自带的类,甚至是基类,所以基本不存在被重写的情况),等于是把第一步重复一次;
3、一直找到最顶层的parent,如果顶层parent在已加载列表中还是没有找到,就会调用findClass()进行加载,并返回;
4、通过parent一层一层地返回,如果最终还是没有(所有parent都没有加载到),就自己进行加载;
这样设计的逻辑就是防止多次加载,一个类只永远只会被加载一次。
另外要注意的是,只有“PackageName + ClassName + 加载它的ClassLoader”这三个元素一致,才认为它是同一个类。所以,指定系统的parent能最大限度地保证类的一致性。
上面的逻辑中可以看到,如果没有加载过,就会调用findClass()方法进行加载,BaseDexClassLoader重载了这个方法:
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class clazz = pathList.findClass(name); if (clazz == null) { throw new ClassNotFoundException(name); } return clazz;}
最终调用了DexPathList的findClass()方法,那我们分析一下DexPathList的主要逻辑:
// 构造方法,BaseDexClassLoader的构造方法中会new出DexPathList实例public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { …… // 通过dexPath路径下的apk/jar/dex文件解压到optimizedDirectory目录中,并生成Elements[]数组 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);}private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory) { ArrayList<Element> elements = new ArrayList<Element>(); // 遍历该路径下的文件 for (File file : files) { ZipFile zip = null; DexFile dex = null; String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { // 如果是dex文件,就加载该文件 dex = loadDexFile(file, optimizedDirectory); } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) || name.endsWith(ZIP_SUFFIX)) { // 如果是压缩文件,就生成ZipFile zip = new ZipFile(file); } …… if ((zip != null) || (dex != null)) { // 通过上面生成的DexFile或ZipFile,生成Element对象,添加到List中 elements.add(new Element(file, zip, dex)); } } // List转换成数组返回 return elements.toArray(new Element[elements.size()]);}// 通过File生成DexFileprivate static DexFile loadDexFile(File file, File optimizedDirectory) throws IOException { if (optimizedDirectory == null) { // 如果输出路径为空,就会使用默认路径(当前File所在路径) return new DexFile(file); } else { // 生成解压路径 String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), optimizedPath, 0); }}/** * Converts a dex/jar file path and an output directory to an * output file path for an associated optimized dex file. */private static String optimizedPathFor(File path, File optimizedDirectory) { String fileName = path.getName(); if (!fileName.endsWith(DEX_SUFFIX)) { int lastDot = fileName.lastIndexOf("."); if (lastDot < 0) { fileName += DEX_SUFFIX; } else { StringBuilder sb = new StringBuilder(lastDot + 4); sb.append(fileName, 0, lastDot); sb.append(DEX_SUFFIX); fileName = sb.toString(); } } File result = new File(optimizedDirectory, fileName); return result.getPath();}// 根据name查找dex,将其转换成Class,返回给ClassLoaderpublic Class findClass(String name) { // 遍历Element[]数组 for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { // 通过name尝试加载Class Class clazz = dex.loadClassBinaryName(name, definingContext); if (clazz != null) { // 如果加载成功,就返回 return clazz; } } } return null;}
分析到这里ClassLoader的加载机制就完结了。
- Android中的dex、apk、ClassLoader详解
- android classloader dex odex
- Android中的Java,class,dex,apk
- Android中的ClassLoader与dex文件加密实现分析
- Android中的ClassLoader与dex文件加密实现分析
- Android系统中的.apk文件和dex文件
- Android系统中的.apk文件和.dex文件
- Android系统中的.apk文件和dex文件
- Android系统中的.apk文件和dex文件
- Android系统中的.apk文件和dex文件
- Android系统中的.apk文件和dex文件
- Android apk dex与odex
- android apk odex dex反编译
- Android apk dex与odex
- [APK] Android dex分包方案
- Android APK DEX分包总结
- Android APK DEX分包总结
- java中的ClassLoader详解
- JS 图片转Base64编码
- LMAX架构
- 蓝桥杯之反幻方
- egret 必须先添加监听再设置skin
- BZOJ 4399: 魔法少女LJJ
- Android中的dex、apk、ClassLoader详解
- multiselect选项框
- 软考知识点
- Pandas 通用方法
- js排序后的状态如何在后端保存
- 对于LayoutInflater.from(parent.getContext()).inflate()方法的笔记
- 正确使用第三方iOS代码
- java数组--冒泡排序
- 安卓高仿IOS按钮