如上篇所说,我们不管要起 Activity、Service,其实都是需要注入自定义的 Classloader。而 Service 没有一个很好的简单注入点,所以才有了 Hook 上层 Classloader 的方案。这种方案有两种,都是解决多 Dex 加载的情况(不管插件化与否其实只要方法数超 65535 都是需要做多 Dex 加载):
<!--more-->
目前淘宝、微店等都是使用多 Classloader 形式来实现 Dex 文件的动态加载,隔离性强、鲁棒性好,但实现上有所不同:
本系列的尿性就是要简单,稳定,尽量不 Hook 任何系统服务,所以下面以替换 PathClassloader 的 parent 思路来讲:
很明显我们应该在应用还没启动的时候就把这事干了,所以参考 Instant-Run,Hook 时机在 Application 的 attachBaseContext
里:
public class MultiClassloaderApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
replacePathClassloaderParent(base);
}
private void replacePathClassloaderParent(Context context) {
ClassLoader pathClassloader = context.getClassLoader();
DispatchClassloader dispatchClassloader = new DispatchClassloader(pathClassloader, context);
final Class<?> clz = ClassLoader.class;
try {
final Field parentField = clz.getDeclaredField("parent");
parentField.setAccessible(true);
parentField.set(pathClassloader, dispatchClassloader);
} catch (Exception e) {
e.printStackTrace();
}
}
}
[DispatchClassloader.java]
public class DispatchClassloader extends ClassLoader {
private BundleClassloader dexClassLoader;
private Context context;
private ClassLoader origin;
public DispatchClassloader(ClassLoader origin, Context context) {
super(origin.getParent());
this.origin = origin;
this.context = context;
installDex();
}
private void installDex() {
// 这里目前只装载了一个测试 Dex,正常情况下需要装载某个目录下的所有 dex 文件(通常每个 Bundle 有一个 Dex)
File optimizedDexOutputPath = new File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/7-MultiClassloader.dex");
File dexOutputDir = context.getDir("dex", 0);
dexClassLoader = new BundleClassloader(
optimizedDexOutputPath.getAbsolutePath(),
dexOutputDir.getAbsolutePath(),
null,
origin);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 需要在这里遍历所有 Bundle 的 Classloader,或者用包名等来做查找分发
Class<?> clz = dexClassLoader.findClass(name);
return clz;
}
}
[BundleClassloader.java]
public class BundleClassloader extends DexClassLoader {
public BundleClassloader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
}
// 仅仅是用来改写 protected 签名
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
几个注意点:
Demo 工程打包过程:
./gradlew installDebug
安装宿主工程;本系列为笔记文,文中有大量的源码解析都是引用的其他作者的成果,详见下方参考资料。
欢迎关注我的公众号和微博。