请点赞,你的点赞对我意义重大,满足下我的虚荣心。
🔥常在河边走,哪有不湿鞋。或许面试过程中你遇到的问题就在这呢?
🔥关注我个人简介,面试不迷路~
一、PathClassLoader与DexClassLoader的区别是什么?
这道题想考察什么?
Android中类加载机制的原理
考察的知识点
ClassLoader类加载机制
考生如何回答
ClassLoader就是我们常说的类加载器。
ClassLoader简介
任何一个 Java 程序都是由一个或多个 class 文件组成,在程序运行时,需要将 class 文件加载到 JVM 中才可以使用,负责加载这些 class 文件的就是 Java 的类加载机制。ClassLoader 的作用简单来说就是加载 class 文件,提供给程序运行时使用。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。
class Class<T> {
...
private transient ClassLoader classLoader;
...
}
ClassLoader是一个抽象类,而它的具体实现类很多,最为主要被使用的有:
-
BootClassLoader
用于加载Android Framework层class文件。
-
PathClassLoader
用于Android应用程序类加载器。可以加载指定的dex,以及jar、zip、apk中的classes.dex
-
DexClassLoader
用于加载指定的dex,以及jar、zip、apk中的classes.dex
-
InMemoryDexClassLoader
Android8.0新增,用于加载内存中的dex
DexClassLoader与PathClassLoader
在Android 5.0以下的版本中,两者之间的区别为:
- DexClassLoader : 可加载jar、apk和dex,可以从SD卡中加载
- PathClassLoader : 只能加载已安裝到系統中(即/data/app目录下)的apk文件
但是随着Android版本的升级,到Android 5.0及以后就已经不是这样了。
我们先来看看在Android 中的PathClassLoader
与DexClassLoader
:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
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);
}
}
其中PathClassLoader
与DexClassLoader
都是继承自同一个父类:BaseDexClassLoader
,而两者的区别则是DexClassLoader
必须传递一个optimizedDirectory 用于存放dexopt的结果,后者则不用。
dexopt:
在Dalvik中虚拟机在加载一个dex文件时,对 dex 文件 进行 验证 和 优化的操作,其对 dex 文件的优化结果变成了 odex(Optimized dex) 文件,这个文件使用了一些优化操作码,和 dex 文件很像。
其实无论是PathClassLoader
还是DexClassLoader
,大家可以看到并没有重写父类的其他方法。如果optimizedDirectory 优化目录为Null,即PathClassLoader
,则Android5.0以下,则会使用默认的优化目录: /data/dalvik-cache/ 。
而这个目录在安装某个APK时,系统会自动在其中存放odex文件:data@app@packagename.apk@classes.dex
而在使用PathClassLoader
加载时,如果加载的不是已经安装在手机中的APK,则会报出:Dex cache directory isn't writable: /data/dalvik-cache,这个目录我们的应用自身并不具备写的权限。因此PathClassLoader只能加载已经安装的APK中的dex文件。
而到了ART下,加载方式发生了截然不同的变化,在安装时对 dex 文件执行dex2oat(AOT 提前编译操作),编译为OAT(实际上是ELF文件)可执行文件(机器码)。而如果在加载时,无法成功加载oat文件,仍然会尝试从原dex中加载,因此ART下,PathClassLoader
与DexClassLoader
都能加载任意指定的dex,以及jar、zip、apk中的classes.dex。但是从原dex加载会导致无法dex2oat,加快加载速度,降低运行效率。
到了Android N之后采用解释,AOT与JIT 混合模式。
到了Android 8.1及以后,DexClassLoader
变为:
public class DexClassLoader extends BaseDexClassLoader {
35 /**
36 * Creates a {@code DexClassLoader} that finds interpreted and native
37 * code. Interpreted classes are found in a set of DEX files contained
38 * in Jar or APK files.
39 *
40 * <p>The path lists are separated using the character specified by the
41 * {@code path.separator} system property, which defaults to {@code :}.
42 *
43 * @param dexPath the list of jar/apk files containing classes and
44 * resources, delimited by {@code File.pathSeparator}, which
45 * defaults to {@code ":"} on Android
46 * @param optimizedDirectory this parameter is deprecated and has no effect
47 * @param librarySearchPath the list of directories containing native
48 * libraries, delimited by {@code File.pathSeparator}; may be
49 * {@code null}
50 * @param parent the parent class loader
51 */
52 public DexClassLoader(String dexPath, String optimizedDirectory,
53 String librarySearchPath, ClassLoader parent) {
54 super(dexPath, null, librarySearchPath, parent);
55 }
56}
此时DexClassLoader中optimizedDirectory同样固定传递null,因此两者没有任何区别了。
总结
-
Android 4.4及以下:
- DexClassLoader : 可加载jar、apk和dex,可以从SD卡中加载
- PathClassLoader : 只能加载已安裝到系統中(即/data/app目录下)的apk文件
-
Android 5.0~Android 8.0:
- DexClassLoader : 可加载jar、apk和dex,可以从SD卡中加载
- PathClassLoader : 可加载jar、apk和dex,可以从SD卡中加载,但会导致无法进行dex2oat操作
-
Android 8.1及以上:
- DexClassLoader 与 PathClassLoader 完全一致
二、什么是双亲委托机制,为什么需要双亲委托机制?
这道题想考察什么?
类加载机制原理
考察的知识点
类加载机制
考生如何回答
双亲委托机制
双亲委托机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。
public abstract class ClassLoader{
//父类加载器
ClassLoader parent;
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
if (parentLoader == null && !nullAllowed) {
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//先找缓存
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
if(parent !=null){
try {
//交给父类加载器加载
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
}
if (clazz == null) {
try {
//父类加载器加载不到,自己加载
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
}
双亲委托机制的作用
1、防止重复加载
2、安全,保证系统类不能被篡改。
Java虚拟机只会在不同的类的类名相同且加载该类的加载器均相同的情况下才会判定这是一个类。如果没有双亲委派机制,同一个类可能就会被多个类加载器加载,如此类就可能会被识别为两个不同的类,相互赋值时问题就会出现。
双亲委派机制能够保证多加载器加载某个类时,最终都是由一个加载器加载,确保最终加载结果相同。
没有双亲委派模型,让所有类加载器自行加载的话,假如用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,系统就会出现多个不同的Object类, Java类型体系中基础行为就无法保证,应用程序就会变得一片混乱。
三、Android中加载类的方法有哪些?有什么区别?
这道题想考察什么?
类加载机制中类加载的过程
考察的知识点
- 类加载过程
- 类加载时机
考生应该如何回答
Android加载类的方式实际上就是Java的类加载。虚拟机把描述类的信息从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终才能够变成可以被虚拟机直接使用的 Java 类型。 类加载过程见 《5.1 描述JVM类加载过程》 。
类加载时机
在以下情况下,类会自动加载:
- 使用 new 实例化对象,创建子类的实例会首先加载其父类
- 访问类的静态方法
- 访问类的静态属性
- 对类进行反射调用
- Java程序中定义了main的类,在启动main方法时该类会被加载
以上五种情况都会触发类的加载并且完成对类的初始化。除了以上情况之外,我们也可以主动调用ClassLoader#loadClass(name)
或者Class.forName(name)
进行加载。实际上Class.forName(name)
也是通过ClassLoader完成对指定类的加载。
public static Class<?> forName(String className) throws ClassNotFoundException {
//得到调用者的类,如main方法所在类
Class<?> caller = Reflection.getCallerClass();
//ClassLoader.getClassLoader(caller):获得main方法所在类的类加载器,使用其完成className的加载
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
但是ClassLoader#loadClass(name)
与Class.forName(name)
不同的是,后者除了完成类加载之外,还会对类进行初始化,执行类中的static块。而 ClassLoader#loadClass
只会完成类的加载。
当然 ,我们也可以使用:Class.forName(String name, boolean initialize, ClassLoader loader)
。通过第二个参数initialize ,从而选择是否需要对类进行初始化。
四、ClassNotFound的有可能的原因是什么?
这道题想考察什么?
类加载流程
考察的知识点
- 反射机制
- 反射在框架中的应用
- 获取Class类的主要方式
考生应该如何回答
ClassNotFoundException表示虚拟机加载指定的类时,如果没有找到对应的Class文件时,抛出的异常。以Android为例,出现此异常的原因可能有:
1、APK中dex未包含需要加载的类数据,导致类加载器无法加载;
2、Android Dalvik下,使用dex分包,在Secondary DEX 加载之前,需要使用不在主dex中的类
3、类数据不合法验证失败导致加载失败。此时能够在Log中看到:
Failure to verify dex file '/data/app/xxxxx/base.apk'
今天的面试分享到此结束拉~下期在见
网友评论