「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」
Android应用本身基于魔改后的Java虚拟机,而有插件是未安装的Apk,系统不会处理其中的类,所有需要使用ClassLoader加载Apk,然后反射里面的代码。
1.java中的ClassLoader
- BootstrapClassLoader 负责加载 JVM 运行时的核心类,比如 JAVA_HOME/lib/rt.jar 等等
- ExtensionClassLoader 负责加载 JVM 的扩展类,比如 JAVA_HOME/lib/ext 下面的 jar 包
- AppClassLoader 负责加载 classpath 里的 jar 包和目录
2.Android中的ClassLoader
在Android系统中的ClassLoader是用来加载dex文件的,也可以加载包含dex的apk文件和jar文件,dex文件是一种对class文件优化的产物,在Android中应用打包时会把所有class文件进行合并。优化(把不同class文件重复的东西只保留一份),然后生成一个最终的class.dex文件。
PathClassLoader用来加载系统类和应用程序类,可以加载已经安装的apk目录下的dex文件
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
DexClassLoader用来加载dex文件,可以从存储空间加载dex文件
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
在插件化中,一般使用DexClassLoader。
3.双亲委派机制
每一个ClassLoader中都有个一个parent对象,代表的是父类加载器,在加载一个类的时候,会先使用父类加载器去加载,如果在父类加载器中没有找到,自己再进行加载,如果parent为空,那么就用系统类加载器来加载。通过这样的机制可以保证系统类都是由系统类加载器加载的。下面是ClassLoader的loadClass方法
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) {
//父类未找到,使用自己加载
c = findClass(name);
}
}
return c;
}
4.如何加载插件中的类
要加载插件中的类,我们首先创建一个DexClassLoader,DexClassLoader构造函数如下:
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
构造方法的4个参数:
- dexPath:dex相关文件(dex、apk、jar)的路径,多个使用冒号分隔
- optimizedDirectory:解压的dex文件存储路径,路径必须为内部存储路径,一般使用程序私有路径:/data/data/{package_name}/
- librarySearchPath:包含c/c++库的路径集合,多个路径用分隔符分隔,可为null
- parent:父加载器
创建DexClassLoader实例后,只要调用其loadClass(className)方法就可以加载插件中的类了。具体如下:
//从assets中将插件apk复制到内部空间
private void extractPlugin() throws IOException {
InputStream inputStream = mContext.getAssets().open("plugin.apk");
Path srcPath = Paths.get(mContext.getFilesDir().getAbsolutePath(), "plugin.apk");
Files.copy(inputStream, srcPath);
}
private void init() throws IOException {
extractPlugin();
String pluginPath = getFilePath(mContext.getFilesDir().getAbsolutePath(), "plugin.apk");
String nativePath = getFilePath(mContext.getFilesDir().getAbsolutePath(), "pluginlib");
String dexOutPath = getFilePath(mContext.getFilesDir().getAbsolutePath(), "dexout");
//使用DexClassLoader加载插件apk
DexClassLoader pluginClassLoader = new DexClassLoader(pluginPath, dexOutPath, nativePath, mContext.getClassLoader())
}
加载流程如下:
5.执行插件类的方法
通过反射来执行类的方法
Class loadClass = pluginClassLoader.loadClass("aclass");
loadClass.getMethod("test", null).invoke(loadClass);
这个过程交过ClassLoader注入,完成注入后,所有来自宿主的类都是用宿主的ClassLoader进行加载,所有来着插件Apk的类使用插件ClassLoader加载,而由于ClassLoader的双亲委派机制,实际上系统类不会收ClassLoader的类隔离机制的影响,这样宿主Apk就可以在宿主进程中使用来自插件的组件类了。