每次使用都利用类加载器加载
将插件Apk打包放到手机中,这里路径是PluginApkPath。然后构建DexClassLoader类加载器加载该Apk,之后可以该类加载器的 loadClass(全类名) 方法来加载apk中的类。之后通过反射来获取他的成员变量和方法。
DexClassLoader pluginClassLoader = new DexClassLoader(PluginApkPath, getDir("my_dir", MODE_PRIVATE).getAbsolutePath(), null, getClassLoader());
try {
Class<?> cls = pluginClassLoader.loadClass("com.demo.plugin.FixClass");
Object obj = cls.newInstance();
//反射私有方法
Method method = cls.getDeclaredMethod("toast", Context.class);
method.setAccessible(true);
method.invoke(obj, MainActivity.this);
//反射私有变量
Field field = cls.getDeclaredField("version");
field.setAccessible(true);
String version = (String) field.get(obj);
System.out.println("version="+version);
}catch (Exception e) {
e.printStackTrace();
}
一步到位,通过反射将插件包的dex加入到宿主中
系统是通过BootClassLoader加载Android核心库的类,通过PathClassLoader来加载我们写的类。
分析PathClassLoader的loadClass源码,发现loadClass的过程是通过双亲委托机制实现的,先判断是否已经加载过,如果没有加载过,先让父类类加载器去findClass如果有就返回,没有再自己去findClass。
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;
}
PathClassLoader没有重写findClass,在它的父类BaseDexClassLoader可以找到findClass方法的实现。分析这里的源码可以发现,findClass是从pathList中去寻找的,而pathList的类型是DexPathList。
private final DexPathList pathList;
@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;
}
我们接着看DexPathList的源码,可以看到他的成员变量dexElements存的是Element的数组,这个Element里面就保存这我们apk中dex相关的信息。
/*package*/ 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 final Element[] dexElements;
/** List of native library path elements. */
private final Element[] nativeLibraryPathElements;
...
}
Element是DexPathList的内部类,这里的File file指的就是dex文件。
/*package*/ final class DexPathList {
...
/**
* Element of the dex/resource file path
*/
/*package*/ static class Element {
private final File file;
private final boolean isDirectory;
private final File zip;
private final DexFile dexFile;
private ZipFile zipFile;
private boolean initialized;
public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
this.file = file;
this.isDirectory = isDirectory;
this.zip = zip;
this.dexFile = dexFile;
}
}
}
因此,我们可以构一个类加载器去加载插件包,利用反射获取到他的dexElements,然后将它加入到宿主Apk的类加载器的dexElements中即可。
具体实现
public class ClassUtil {
public static void loadClass(Context context, String apkPath) {
try {
//源码分析可知宿主的dex存在dexElements这个数组中,而它属于pathList,而pathList属于BaseDexClassLoader,我们需要通过反射一步步去拿到他,然后将我们插件的dex加入到数组中
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = classLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
//宿主的类加载器
ClassLoader pathClassLoader = context.getClassLoader();
//获取宿主pathList
Object hostPathList = pathListField.get(pathClassLoader);
//通过pathList获取宿主dexElements
Object[] hostDexElements = (Object[])dexElementsField.get(hostPathList);
//构造插件的类加载器
ClassLoader pluginClassLoader = new DexClassLoader(apkPath, context.getDir("my_dir", 0).getAbsolutePath(), null, pathClassLoader);
//获取插件的pathList
Object pluginPathList = pathListField.get(pluginClassLoader);
//通过pathList获取插件dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
//构建一个新的数组将宿主和插件的Elements都放进来
Object[] newElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginDexElements.length);
//从hostDexElements的0号位开始拷贝hostDexElements.length个数据,到newElements的0号位开始插入
System.arraycopy(hostDexElements, 0, newElements, 0, hostDexElements.length);
//pluginDexElements中的数据到newElements
System.arraycopy(pluginDexElements, 0, newElements, hostDexElements.length, pluginDexElements.length);
dexElementsField.set(hostPathList, newElements);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Demo地址:MyLoadPlugin: 加载插件包中的类