插件化开发 -- 加载APK
2020-12-05 本文已影响0人
NengLee
在App项目中需要加载sd里面的一个APK,显而易见就是调用插件方法。
需要先了解Android的Dalvik/ART虚拟机,和java的JVM虚拟机几乎一样的标准,了解类加载的流程,通过反射Hook启动插件类。把插件的dex加到主dex里面中,从而调用。
类加载时序图
关注Android的类加载时序图,其中 DexPathLis 和 Element 以及 DexFile
类加载的时序图核心流程通过:
- BaseDexClassLoader --> findClass
- DexPathList -for- findClass || findClass
- DexFile --> loadClassBinaryName
核心源码
BaseDexClassLoader.源码
@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.源码
/**
* Finds the named class in one of the dex files pointed at by
* this instance. This will find the one in the earliest listed
* path element. If the class is found but has not yet been
* defined, then this method will define it in the defining
* context that this instance was constructed with.
*
* @param name of class to find
* @param suppressed exceptions encountered whilst finding the class
* @return the named class or {@code null} if the class is not
* found in any of the dex files
*/
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;
}
public Class<?> findClass(String name, ClassLoader definingContext,List<Throwable> suppressed) {
return dexFile != null ?
dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
步骤
目标:宿主dexElements = 宿主dexElements + 插件dexElements
- 获取宿主dexElements
- 获取插件dexElements
- 合并两个dexElements
- 将新的dexElements 赋值到 宿主dexElements
package com.example.myapplication;
import android.content.Context;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import dalvik.system.DexClassLoader;
public class LoadUtil {
//存放apk的路径
private final static String apkPath = "/sdcard/dxpak-debug.apk";
public static void loadClass(Context context) {
/**
* 宿主dexElements = 宿主dexElements + 插件dexElements
*
* 获取的是宿主的类加载器 --- 反射 dexElements 宿主
*
* 获取的是插件的类加载器 --- 反射 dexElements 插件
*/
try {
Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
Field pathListField = clazz.getDeclaredField("pathList");
pathListField.setAccessible(true);
Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 1.宿主的 类加载器
ClassLoader pathClassLoader = context.getClassLoader();
// DexPathList类的对象
Object hostPathList = pathListField.get(pathClassLoader);
// 宿主的 dexElements
Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
// 2.插件的 类加载器
ClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(),
null, pathClassLoader);
// DexPathList类的对象
Object pluginPathList = pathListField.get(dexClassLoader);
// 插件的 dexElements
Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
// 3.宿主dexElements = 宿主dexElements + 插件dexElements
// 创建一个新数组
Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),
hostDexElements.length + pluginDexElements.length);
System.arraycopy(hostDexElements, 0, newDexElements,
0, hostDexElements.length);
System.arraycopy(pluginDexElements, 0, newDexElements,
hostDexElements.length, pluginDexElements.length);
// 4.赋值
dexElementsField.set(hostPathList, newDexElements);
} catch (Exception e) {
e.printStackTrace();
}
}
}
加载调用:
public void onButtonApk(View view) {
try {
Class<?> clazz = Class.forName("com.example.dxpak.Test");
Method print = clazz.getMethod("loginStar");
print.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}