ART与Dalvik
什么是Dalvik
Dalvik是Google公司自己设计用于Android平台的Java虚拟机。支持已转换为.dex(Dalvik Excutable)格式的Java应用程序的运行,.dex格式是专为Dalvik应用设计的一种压缩格式,适合内存和处理器速度有限的系统。
JVM的指令集是基于栈的(通用性更好、即跨平台性更好)
Dalvik的指令集是基于寄存器的(执行效率更好)。
什么是ART
Android Runtime,Android4.4中引入的一个开发者选项,也是Android5.0及更高版本的默认模式。在应用安装的时候Ahead-Of-Time(AOT)预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)预编译。应用程序安装会变慢,但是执行将更有效率,启动更快。
- 在Dalvik下,应用运行需要解释执行,常用热点代码通过即时编译器(JIT)将字节码转换为机器码,安装慢了,但执行效率会提高
- JIT:让频繁需要运行的代码(热点代码),在运行时编译成机器码。
- ART占用空间比Dalvik大(字节码变成机器码),空间换时间
- 预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了CPU的使用频率,降低了能耗。
Dexopt与DexAot
ART会执行AOT,但针对Dalvik开发的应用也能在ART环境中运作
dexopt(针对Dalvik)
对dex文件进行验证和优化为odex(Optimized dex)文件
.dex --dexopt--> .odex
dex2oat(针对ART)
在安装时对dex文件执行dexopt优化之后再将odex进行AOT提前编译操作,编译为OAT可执行文件(机器码)。
.dex --dex2oat--> .oat(ELF File)
ClassLoader
Java类加载器
Android类加载器
- BootClassLoader
用于加载Android Framework层的class文件,如Activity.class等
//BootClassLoader
ClassLoader classLoader = Activity.class.getClassLoader();
- PathClassLoader
用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex。例如:自己写的代码都是PathClassLoader加载的。
//PathClassLoader
ClassLoader classLoader = MainActivity.this.getClassLoader();
public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent){
super(dexPath, null, libraryPath, parent);
}
- DexClassLoader
加载指定的dex,以及jar、zip、apl中的classes.dex。Android中并没有使用这个类加载器。
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent){
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
- 与PathClassLoader的唯一区别就是多了个optimizedDirectory,意为加载dex时会进行优化,并保存在优化路径下。
而PathClassLoader也会优化,会存到/data/dalvik-cache- optimizedDirectory参数不能传sdcard的目录,只能传data/data/packagename 这样的私有目录,建议使用context.getCodeCacheDir()
- 网上有博客的错误引导,其实PathClassLoader和DexClassLoader功能相同,区别唯一,并不是PathClassLoader只能加载已安装的apk的dex文件。
- inMemoryDexClassLoader
Android 8.0后加入的类加载器,用于加载内存中的dex文件。
双亲委托机制
某个类加载器在加载类时,首先将家在任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务或者没有父类加载器时,才自己去加载。
注意:是父·类加载器,不是父类·加载器,也就是说,是ClassLoader类中的parent属性(ClassLoader是类加载的终极祖先父类,所以它有这个属性,就代表所有子子孙孙有这个属性可以用来判断父·类加载器存不存在),而不是类定义中的父类(例如PathClassLoader、DexClassLoader的父类是BaseDexClassLoader)。
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
//(1)先判断这个类是否被加载过
Class> c = findLoadedClass(name);
if (c == null) {
try {
//(2)如果没被加载,先让父加载器进行加载
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
}
//(3)如果父加载器加载失败,则自身进行加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
总结
- 除了顶层的类加载器外,其他的类加载器都有自己的父类加载器,在加载类时首先判断这个类是否被加载过,如果已经加载则直接返回。
- 如果未被加载过,则先尝试让父加载器进行加载,最终所有加载请求都会传递给顶层的加载器中。
- 当父加载器发现未找到所需的类而无法完成加载请求时,子加载器的findClass方法中进行加载。
4.父类加载是赋值一般在构造函数中传入。
Android加载Class的过程
BaseDexClassLoader的findClass
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent){
super(parent);
//pathList是在构造函数中new出来的
this.pathList = new DexPathList(this, dex, librarySearchPath, null);
if(reporter != null){
reporter.report(this.pathList.getDexPath());
}
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// First, check whether the class is present in our shared libraries.
if (sharedLibraryLoaders != null) {
for (ClassLoader loader : sharedLibraryLoaders) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException ignored) {
}
}
}
// Check whether the class in question is present in the dexPath that
// this classloader operates on.
List suppressedExceptions = new ArrayList();
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的findClass方法
#DexPathList
private Element[] dexElements;
......
DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
......
this.definingContext = definingContext;
ArrayList suppressedExceptions = new ArrayList();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted);
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
this.systemNativeLibraryDirectories =
splitPaths(System.getProperty("java.library.path"), true);
List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
......
public Class> findClass(String name, List suppressed) {
for (Element element : dexElements) {
Class> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
- dexElements是什么
是一个Element数组,Element中包含一个DexFile
DexFile就代表一个Dex文件,里面的native(C/C++)函数 来进行Dex的加载工作- dexElements是在构造函数中创建的:通过遍历传入的file列表,判断.dex结尾,通过loadDexFile(file, optimizedDirectory)方法,
- 类加载的过程,就是遍历dexElements这个数组调用了
DexFile
的loadClassBinaryName
方法,最终调用native
方法defineClassNative
。