###前言
类加载机制是做热修复以及插件化的很重要的理论基础。
###Java中的ClassLoader回顾
如下图所示:
###ClassLoader的特性
ClassLoader的主要特性是双亲委托机制,即加载一个类的时候,先判断已经存在的类是否被加载过,如果没有,先去委托父亲去加载。如果父亲都没有加载成功的话,那么最终由自己加载。最终这个类最终没有合适的CLassLoader加载,那么就会抛出异常,相关的ClassLoader源码如下:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 先判断这个类是否已经被加载过
Class c = findLoadedClass(name);
if (c == null) {
// 如果没有被加载过,那么委托父亲去加载
long t0 = System.nanoTime();
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.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
}
}
return c;
}
复制代码
其中CLassLoader的findClass方法是空实现,需要自己继承然后实现:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
复制代码
这种双亲委托机制有两种好处:
- 实现类加载的共享功能,提升类加载的效率。
- 实现类加载的隔离功能,提升系统的安全性。比如,通过这种方式,系统的String类只能由系统的ClassLoader加载。
###Android中的ClassLoader整体概述
Android中的ClassLoader的整体架构继承关系如下:
其中,ClassLoader分别为:
- BootClassLoader:与Java中的Bootstrap ClassLoader类似,主要加载Android Framework中的字节码文件。
- PathClassLoader:与Java中的App ClassLoader类似,主要加载已经安装到系统中的APK中的字节码文件。
- DexClassLoader:与Java中的Customer ClassLoader类似,主要加载自定义路径下的APK或者JAR中的字节码文件(Android中主要是指dex文件,即classes.dex)。
其中,BaseDexClassLoader是PathClassLoader以及DexClassLoader的父类,PathClassLoader以及DexClassLoader的逻辑都在这个父类中实现。
BootClassLoader和PathClassLoader是一个普通的APP所需要的(定制ROM另外说),最基本的ClassLoader,通过下面的代码可以证明:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader cl = this.getClassLoader();
Log.e(TAG, "onCreate: " + cl);
while (cl.getParent() != null) {
cl = cl.getParent();
Log.e(TAG, "onCreate: " + cl);
}
}
复制代码
打印结果只有BootClassLoader和PathClassLoader:
09-06 14:29:01.571 3165-3165/com.nan.loadapkdemo E/MainActivity: onCreate: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.nan.loadapkdemo-1/base.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_dependencies_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_0_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_1_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_2_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_3_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_4_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_5_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_6_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_7_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_8_apk.apk", zip file "/data/app/com.nan.loadapkdemo-1/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/data/app/com.nan.loadapkdemo-1/lib/x86, /system/lib, /vendor/lib]]]
09-06 14:29:01.571 3165-3165/com.nan.loadapkdemo E/MainActivity: onCreate: java.lang.BootClassLoader@b173b34
复制代码
###ClassLoader源码分析
先来给出源码的位置:
- BootClassLoader在ClassLoader.java文件的后面(直接通过Android Studio查看源码即可)
- BaseDexClassLoader地址:http://androidxref.com/7.1.1_r6/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
- PathClassLoader地址:http://androidxref.com/7.1.1_r6/xref/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
- DexClassLoader地址:http://androidxref.com/7.1.1_r6/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
先来看看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);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
@Override
protected URL findResource(String name) {
return VMClassLoader.getResource(name);
}
@SuppressWarnings("unused")
@Override
protected Enumeration<URL> findResources(String resName) throws IOException {
return Collections.enumeration(VMClassLoader.getResources(resName));
}
@Override
protected Package getPackage(String name) {
if (name != null && !name.isEmpty()) {
synchronized (this) {
Package pack = super.getPackage(name);
if (pack == null) {
pack = definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0",
"Unknown", null);
}
return pack;
}
}
return null;
}
@Override
public URL getResource(String resName) {
return findResource(resName);
}
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findClass(className);
}
return clazz;
}
@Override
public Enumeration<URL> getResources(String resName) throws IOException {
return findResources(resName);
}
}
复制代码
BaseDexClassLoader主要负责加载Framework的东西,没什么好说的,不是我们的重点。
下面来分析PathClassLoader以及DexClassLoader。
PathClassLoader如下:
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
复制代码
DexClassLoader如下:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
复制代码
需要说明的是一些参数信息:
- dexPath:需要加载含有dex文件(classes.dex)的路径,一般是jar,apk。
- optimizedDirectory:解压dex文件后临时存放内容的文件夹路径,一般放在内存储中,PathClassLoader不需要这个参数(因为不能加载指定路径的字节码),所以传null
- librarySearchPath:包含native lib的目录路径,没有传null
- parent:父类加载器
下面看父类BaseDexClassLoader的构造函数实现:
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
复制代码
主要就是构造了DexPathList对象,这个对象是用来存储一个或多个dex文件的信息,即内部有一个Element[]数组,数组存放的是DexFile对象。DexPathList对象构造的时候会通过make(Dex、Path)Elements方法去初始化这个Element[]数组:
private static Element[] makeElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions,
boolean ignoreDexFiles,
ClassLoader loader) {
Element[] elements = new Element[files.size()];
int elementsPos = 0;
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
zip = new File(split[0]);
dir = new File(split[1]);
} else if (file.isDirectory()) {
// We support directories for looking up resources and native libraries.
// Looking up resources in directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file, true, null, null);
} else if (file.isFile()) {
if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else {
zip = file;
if (!ignoreDexFiles) {
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
if ((zip != null) || (dex != null)) {
elements[elementsPos++] = new Element(dir, false, zip, dex);
}
}
if (elementsPos != elements.length) {
elements = Arrays.copyOf(elements, elementsPos);
}
return elements;
}
复制代码
这个方法的核心就是把初始化DexFile,并且装载到Element[]数组中。
上面就分析完了classes.dex文件是如何通过ClassLoader的初始化装载进来的,下面继续分析一个类是如何通过PathClassLoader或者DexClassLoader进行加载的,这时候就需要看父类BaseDexClassLoader的findClass方法了:
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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;
}
复制代码
这里面看到,实际上就是调用了PathList的findClass方法:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
复制代码
这个方法就是遍历初始化创建好的内部Element[]数组里面的DexFile对象,最终是通过DexFile的loadClassBinaryName进行加载的:
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
复制代码
defineClass的实现如下:
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> 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;
}
复制代码
到最后,defineClassNative是一个native方法:
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
复制代码
defineClassNative是通过C/C++实现,这个方法去dex文件中查找指定name的类,并且拼接成Class字节码,返回给Java层。通过native实现是为了提高效率。
整个加载流程一气呵成,如下图所示:
###实现动态加载
如下面所示,演示了最简单的动态加载的方案:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String apkPath = getExternalCacheDir().getAbsolutePath() + "/bundle.apk";
loadApk(apkPath);
}
private void loadApk(String apkPath) {
File optFile = getDir("opt", MODE_PRIVATE);
// 通过DexClassLoader加载制定的APK文件
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, optFile.getAbsolutePath(), null, getClassLoader());
try {
// 通过反射去使用对象
Class clz = dexClassLoader.loadClass("com.loubinfeng.www.boundle.Printer");
if (clz != null) {
Object instance = clz.newInstance();
Method method = clz.getMethod("print");
method.invoke(instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
###实现动态加载的难点
实现动态加载难点比较多:
- 资源的访问问题
- 四大组件的生命周期管理问题
- 插件ClassLoader的管理
- so库的动态加载等等
相关文章:http://www.infoq.com/cn/articles/android-dynamic-loading
如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:
我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)。