一、什么是插件化
在android开发中,所谓插件化,就是在android运行中,我们可以动态加载dex文件到app的进程中。这样的做的好处是app能在不升级的情况下更新有问题的dex模块。
二、如何加载一个dex插件
- android给我们提供了一个加载插件的工具ClassLoader。为了理解这个东西,我们先做个实验。你可以新建个android工程,并在onCreate()方法中加上如下代码:
ClassLoader classLoader = getClassLoader();
Log.d("MainActivity",classLoader.toString());
打印出的日志应该类似这样
dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.debug.pluginhost-2/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
这个日志告诉我们,android系统给每个app进程分配了一个PathClassLoader对象,这个对象包含了两部分信息,一部分dex文件的路径集合,也就是DexPathList(/data/app/com.debug.pluginhost-2/base.apk),一部分是nativeLibrary路径集合(/vender/lib,/system/lib)。
我们把上面的代码稍微改一下
ClassLoader classLoader = getClassLoader();
Log.d("MainActivity",classLoader.toString());
Log.d("MainActivity",classLoader.getClass().getSuperclass().toString());//BaseDexClassLoader
Log.d("MainActivity",classLoader.getClass().getSuperclass().getSuperclass().toString());//ClassLoadr
看下日志
D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.debug.pluginhost-2/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
D/MainActivity: class dalvik.system.BaseDexClassLoader
D/MainActivity: class java.lang.ClassLoader
我们看下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);
}
}
只能看BaseDexClassLoader的源码了,我们挑重要的看
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
@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;
}
//下面还有一些源代码就省略了
}
我们看到BasedexClassLoader维护了一个DexPathList 列表(结合开头的日志),并复写了父类的findClass方法。我们看下它的父类ClassLoader的代码,我们还是挑重要的看
public abstract class ClassLoader {
...
private final ClassLoader parent;
...
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) {
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;
}
...
}
上面的loadClass方法就是著名的双亲委派。先检查系统是否加载了这个类,如果没加载,看看自己parent是否加载了,如果parent也没加载,那么直接从自己的pathList里找。我们再把最初的代码改下,看看这个parent成员变量是个啥
ClassLoader classLoader = getClassLoader();
Log.d("MainActivity",classLoader.toString());
Log.d("MainActivity",classLoader.getClass().getSuperclass().toString());//BaseDexClassLoader
Log.d("MainActivity",classLoader.getClass().getSuperclass().getSuperclass().toString());//ClassLoadr
try {
Field[] fields = classLoader.getClass().getSuperclass().getSuperclass().getDeclaredFields();
for (int i=0;i < fields.length;i++){
if(fields[i].getName().equals("parent")){
Field parent = fields[i];
parent.setAccessible(true);
Log.d("MainActivity",parent.get(classLoader).toString());
return;
}
}
Log.d("MainActivity","没查到");
}catch (Exception e){
e.printStackTrace();
}
看下日志
D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.debug.pluginhost-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
D/MainActivity: class dalvik.system.BaseDexClassLoader
D/MainActivity: class java.lang.ClassLoader
D/MainActivity: java.lang.BootClassLoader@360f4a86
我们看到ClassLoader的parent成员变量是个BootClassLoader对象。顾名思义,应该是系统启动时加载类的工具,它是ClassLoader的非静态内部类,并继承自ClassLoader。它是所有ClassLoader的根部。我们结合一个例子来理解下加载dex插件的过程。
try {
HttpDownloader downloader = new HttpDownloader();
String url = "http://192.168.2.192/static/script/plugin.jar";
String path = "dex";
String name = "plugin.jar";
int res = downloader.downloadFiles(url, path, name);
Log.d("MainActivity","download result="+res+"");
//上面的代码是把插件下载下来,我们主要看下面代码
PathClassLoader pcl = (PathClassLoader)MainActivity.this.getClassLoader();
DexClassLoader dexClassLoader = new DexClassLoader(MainActivity.this.getCacheDir()+"/dex/plugin.jar",MainActivity.this.getCacheDir()+"/dex/",null,pcl);
Class<?> clz = dexClassLoader.loadClass("com.debug.qclibrary.Plugin");
Method debug = clz.getDeclaredMethod("debug",new Class[0]);
debug.invoke(null);
}catch (Exception e){
e.printStackTrace();
}
- DexClassLoader就是简单的继承了BaseClassCloader
- 初始化的时候,我们把当前app的ClassLoader对象(一个PathClassLoader对象)作为parent传递给了DexClassLoader
- 我们调用dexClassLoader.loadClass("com.debug.qclibrary.Plugin")方法,由于BaseClassLoader没有复写这个方法,所以直接调用了ClassLoader的loadclass 方法,这个方法,我们上面贴过了。执行流程就是先检查系统是否加载了这个类,如果加载了,直接返回,如果没加载就调用parent的loadClass方法,这里的parent应该是pcl变量。
- 调用pcl的loadClass方法时,依然是先检测系统是否加载,如果没加载就用parent的loadClass方法,注意,我们上面通过实验得知,pcl的parent是个BootClassLoader对象,我们看下 BootClassLoader的loadClass方法的实现
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findClass(className);
}
return clazz;
}
如果pcl的parent的loadClass方法也没返回非空,那么直接调用pcl的findClass方法,也就是去"/data/app/com.debug.pluginhost-2/base.apk"找目标class,如果还没找到,那么让dexClassload去自己的dexPathList里找,如果找到就返回,找不到就抛出异常。
三、总结双亲委派
- 如果我们通过DexClassLoader动态加载dex插件,那么这个任务都是优先让自己的parent去加载,只有parent没正确返回时才尝试自己从自己的dexPathList去加载。
- 为什么要用双亲委派?
- 防止已经加载的类再次加载。