1. 类加载流程
1.1 类加载的过程
加载 -> 验证 -> 准备 -> 解析 -> 初始化
在类加载过程中,虚拟机主要完成以下三件事:
- 通过一个全限定名来获取定义此类的二进制字节流、
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在Java堆中生成一个代表这个类的Class对象,做为方法区域数据的访问入口
2. 类加载器
2.1 Android ClassLoader继承关系
2.2 PathClassLoader与DexClassLoader区别
在安卓8.0之前二者的区别在于第二个参数optimizedDirectory,表示生成的odex存放的路径。来看下具体代码
#PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
/*
* optimizedDirectory传入null则使用默认的存储路径 /data/dalvik-cache
/
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
#DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
/**
* optimizedDirectory需要自定义保存的目录
*/
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
#BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {
private static volatile Reporter reporter = null;
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
}
//...
}
2.3 PathClassLoader与BootClassLoader区别
一句话总结,应用的类加载器是PathClassLoader,SDK的类加载器是BootClassLoader
2.4 自定义类加载器
DexClassLoader classLoader = new DexClassLoader(dexPath, context.getCacheDir().getAbsolutePath(), null, context.getClassLoader());
//使用
Class<?> cla = classLoader.loadClass("com.xx.Test");
2.4.1 loadClass
看一下loadClass( )具体实现
#ClassLoader.loadClass(String name)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 注释1
Class<?> c = findLoadedClass(name);
// 注释2
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
}
// 注释3
if (c == null) {
c = findClass(name);
}
}
return c;
}
注释
-
检查该类是否已经加载过
-
判断parent是否为null,并调用parent.loadClass( ),委派parent加载该类,注意:parent是成员变量,而不是继承的意思。
-
如果parent未能加载该类,则调用自身的findClass( )去加载
#BootClassLoader.loadClass(String name)
@Override
protected Class<?> loadClass(String className, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findClass(className);
}
return clazz;
}
可以看见到BootClassLoader中并没有parent变量,而是调用自身的findClass方法尝试加载类,不管是否加载成功,都返回。
整体流程如下:
这就是类加载的双亲委派机制
双亲委派机制的优点:
- 避免重复加载,若父加载器已经加载该类,子加载器就没必要再去加载一遍
- 安全性,防止核心API被随意篡改
2.4.2 findClass
接下来我们看下findClass的具体实现
#BootClassLoader.findClass(String name)
protected Class<?> findClass(String name) throws ClassNotFoundException {
return Class.classForName(name, false, null);
}
DexClassLoader和PathClassLoader并没有重写findClass方法,直接看其父类BaseDexClassLoader
#BaseDexClassLoader.findClass(String name)
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);//注释1
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是一个DexPathList对象
final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String zipSeparator = "!/";
/** class definition context */
private final ClassLoader definingContext;
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private Element[] dexElements;
//...
public Class<?> findClass(String name, List<Throwable> 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;
}
}
pathList.findClass中又会循环调用element.findClass( )
element又是个什么东西,它是DexPathList的静态内部类,继续往下看
#Element
static class Element {
private final File path;
//dex文件
private final DexFile dexFile;
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;
//...
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
}
element的findClass通过dexFile和类名加载该类。
注意 :dexElements中的所有Dex文件都是属于宿主app的
那么我们如果想要加载插件中的类,那么就需要通过反射把插件的dex文件合并到宿主的dexElements中。
3. 插件化实现
主要步骤
- 创建插件的DexClassLoader类加载器,然后通过反射获取插件的dexElements
- 获取宿主的PathClassLoader类加载器,然后通过反射获取宿主的dexElements
- 合并宿主与插件的dexElements,生成新的Element数组
- 最后通过反射将新的Element[]赋值给宿主的dexElements
public static final String apkPath = "plugin-debug.apk";
dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElements = dexPathListClass.getDeclaredField("dexElements");
dexElements.setAccessible(true);
baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathList = baseDexClassLoaderClass.getDeclaredField("pathList");
pathList.setAccessible(true);
获取宿主与插件的DexPathList和Elements
PathClassLoader classLoader = (PathClassLoader) context.getClassLoader();
//获取宿主DexPathList
Object hostPathList = pathList.get(classLoader);
//获取宿主Elements
Object[] hostElements = (Object[]) dexElements.get(hostPathList);
File extractFile = context.getFileStreamPath(apkPath);
String dexpath = extractFile.getPath();
File fileRelease = context.getDir("dex", 0); //0 表示Context.MODE_PRIVATE
//用于加载插件的类加载器
DexClassLoader dexClassLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(),
null, classLoader);
//获取插件的DexPathList
Object pluginPathList = pathList.get(dexClassLoader);
//获取插件的Elements
Object[] pluginElements = (Object[]) dexElements.get(pluginPathList);
合并Elements
//合并 因为无法获取Element类,所以只能利用反射创建数组
Object[] mergeElements = (Object[]) Array.newInstance(
hostElements.getClass().getComponentType(),hostElements.length + pluginElements.length);
System.arraycopy(hostElements, 0, mergeElements, 0, hostElements.length);
System.arraycopy(pluginElements, 0, mergeElements, hostElements.length, pluginElements.length);
将合并后的Elemments替换宿主的Elements
dexElements.set(hostPathList, mergeElements);
插件
public class Test {
public static String startPlugin(){
return "插件已启动";
}
}
打包放入/data/data/files目录下
Activity中调用
try {
Class<?> clazz = Class.forName("com.brins.plugin.Test");
Method method = clazz.getMethod("startPlugin");
String str = (String) method.invoke(null);
mText.setText(str);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
运行结果: