提起热修复以及插件化,相信大家肯定不陌生,而无论是热修复还是插件化,其理论依据就是Android 类加载机制。今天我们从源码的角度一起学习下。
简单来讲,Android中的ClassLoader主要分为BootClassLoader、PathClassLoader和DexClassLoader这三种类型。BootClassLoader:主要负责加载Android FrameWork层中的字节码文件; PathClassLoader:负责加载已经安装到系统APK文件中的字节码文件;DexClassLoader:负责加载指定目录中的字节码文件;我们先来看下其源码实现:
#BootClassLoader
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
public static synchronized BootClassLoader getInstance() {
if (instance == null) {
instance = new BootClassLoader();
}
return instance;
}
public BootClassLoader() {
super(null);
}
...
}
由上述代码可以看出,BootClassLoader 继承自ClassLoader抽象类,实现方式为单例模式,需要注意的是BootClassLoader的访问修饰符是默认的,只有在同一个包中才可以访问,所以我们在应用程序中是无法直接调用到的。
#PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
}
PathClassLoader继承自BaseDexClassLoader ,由上述代码,很显然,PathClassLoader中的方法实现都在其父类BaseDexClassLoader 中,在这里我们分析下PathClassLoader构造方法中各个参数的含义:
dexPath:dex文件以及包含dex的apk文件或jar文件的路径集合,多个路径用文件分隔符分隔,默认文件分隔符为‘:’。
librarySearchPath:所使用到的C/C++库存放的路径
parent:该ClassLoader所对应的父ClassLoader
#DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
}
同样,DexClassLoader 也是继承自BaseDexClassLoader ,相比较PathClassLoader而言,DexClassLoader的构造方法中多了一个参数optimizedDirectory,我们看下这个参数的含义:
optimizedDirectory:Android系统将dex文件进行优化后所生成的ODEX文件的存放路径,该路径必须是一个内部存储路径。PathClassLoader中使用默认路径“/data/dalvik-cache”,而DexClassLoader则需要我们指定ODEX优化文件的存放路径。
和Java中的ClassLoader类似,Android中的ClassLoader同样遵循双亲委托机制。上述三种ClassLoader中,PathClassLoader的parent为BootClassLoader,DexClassLoader的parent同样为BootClassLoader,下面我们来验证下:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader classLoader = getClassLoader();
if (classLoader != null){
Log.e("MainActivity", classLoader.toString());
while (classLoader.getParent() != null){
classLoader = classLoader.getParent();
Log.e("MainActivity", classLoader.toString());
}
}
Log.e("MainActivity", "------------------");
TextView mText = findViewById(R.id.tv_text);
Log.e("MainActivity-TextView", mText.getClass().getClassLoader().toString());
}
}
输出日志为:
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.administrator.mdtest-2/base.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_dependencies_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_0_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_1_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_2_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_3_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_4_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_5_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_6_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_7_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_8_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: java.lang.BootClassLoader@245c18f6
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: ------------------
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity-TextView: java.lang.BootClassLoader@245c18f6
由上述输出日志,我们不仅可以验证,PathClassLoader的parent为BootClassLoader,同时还验证了我们文章开始所说的应用程序的ClassLoader为PathClassLoader,FrameWork层的ClassLoader为BootClassLoader。
照例我们打开源码,看下BootClassLoader是在哪里作为parent参与构建PathClassLoader对象的:
#ClassLoader
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
// TODO Make this a java.net.URLClassLoader once we have those?
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
没错,正是在ClassLoader类中的createSystemClassLoader方法中。
好了,我们既然知道Android的ClassLoader遵循双亲委托机制,那么肯定要看下ClassLoader类中的loadClass方法了:
#ClassLoader
protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
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
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
关于双亲委托机制,相信大家都了解,在这里我就不详细介绍了,需要注意的是,在Java中,类加载是通过defineClass方法,而在Android中,类加载则是通过findClass方法,我们跟进去findClass方法看下类加载的过程(由于BaseDexClassLoader对findClass方法进行了重写,所以我们需要跟进到BaseDexClassLoader类中,而Android Studio中无法查看到BaseDexClassLoader的具体源码,所以笔者在这里通过源码在线查看网站:https://www.androidos.net.cn/sourcecode):
#BaseDexClassLoader
@Override
protected Class findClass(String name) throws ClassNotFoundException {
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;
}
可以看到,在BaseDexClassLoader的findClass方法中直接调用到pathList的findClass方法进行类加载操作,pathList是个什么东东呢?我们看下它的定义:
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
if (reporter != null) {
reporter.report(this.pathList.getDexPaths());
}
}
由上述代码可以看到,pathList被定义为final类型,其对象是在BaseDexClassLoader的构造方法中创建的,也就是说在PathClassLoader对象创建的时候就创建了DexPathList对象,并将相应参数传入。我们跟进去看下DexPathList的构造方法:
//定义所要加载文件后缀
private static final String DEX_SUFFIX = ".dex";
//构造方法中传入的ClassLoader
private final ClassLoader definingContext;
//Element为 DexPathList 中的内部类,其主要的成员变量为 dexFile
//DexFile:dex文件在安卓虚拟机中的具体实现
private Element[] dexElements;
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
异常判断操作...
//接收classloader对象
this.definingContext = definingContext;
ArrayList suppressedExceptions = new ArrayList();
// save dexPath for BaseDexClassLoader
//重点,通过 makeDexElements 方法初始化 dexElements数组
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext);
...
}
我们跟进去看下makeDexElements方法的实现:
private static Element[] makeDexElements(List files, File optimizedDirectory,
List suppressedExceptions, ClassLoader loader) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/*
* 遍历所有的dex文件
*/
for (File file : files) {
if (file.isDirectory()) { //判断file是否为文件夹
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) { //判断file是否为文件
//获取文件名称
String name = file.getName();
//判断文件名称是否以“.dex”结尾
if (name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
//将dex文件转换为DexFile对象
DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
//创建Element对象,将DexFile对象作为参数传入,
//并将该Element对象添加到elements数组中
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
DexFile dex = null;
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
/*
* IOException might get thrown "legitimately" by the DexFile constructor if
* the zip file turns out to be resource-only (that is, no classes.dex file
* in it).
* Let dex == null and hang on to the exception to add to the tea-leaves for
* when findClass returns null.
*/
suppressedExceptions.add(suppressed);
}
if (dex == null) {
elements[elementsPos++] = new Element(file);
} else {
elements[elementsPos++] = new Element(dex, file);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
由上述代码,我们可以知道makeDexElements方法的主要作用为:遍历指定路径下的所有文件,将其中的.dex文件转换成DexFile对象,最终存储到elements数组中。
由上述分析,我们知道类加载操作最终是由pathList的findClass方法来实现的,我们继续跟进去pathList的findClass方法看下:
#DexPathList
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;
}
可以看到, DexPathList 的 findClass方法中简单粗暴,对dexElements数组进行遍历,调用element的findClass方法来寻找当前需要的class字节码,简单来讲就是Android在进行类加载的时候,会遍历我们的每一个dex文件,来寻找所需的Class。
我们接着跟进去element的findClass方法去看下:
#Element
public Class findClass(String name, ClassLoader definingContext,
List suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
可以看到,最终是调用到 DexFile 的loadClassBinaryName方法,我们接着跟:
#DexFile
public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
...
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
...
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
可以看到最终是通过DexFile类中的defineClassNative方法来完成所需Class的查找。
好了,Android ClassLoader源码解析到这里就结束了,欢迎大家一起交流。