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生成DexFile
private 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,返回给ClassLoader
public 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的加载机制就完结了。