揭秘Android自定义ClassLoader背后的耗时真相
2023-12-25 06:43:45
随着移动互联网的蓬勃发展,应用程序变得越来越复杂,为了满足不同业务场景的需求,Android开发中经常需要使用自定义ClassLoader来扩展类加载路径,隔离加载不同版本依赖等。然而,自定义ClassLoader的运用也容易引入耗时问题,影响应用程序的启动速度和性能。
最近在优化西瓜视频客户端冷启动速度时,发现在关闭插件ClassLoader注入的情况下,启动速度提升了300ms左右。但是西瓜在启动阶段并没有使用到插件,那么这么大的耗时是怎么来的呢?让我们深入剖析这个问题。
首先看下西瓜目前使用的插件ClassLoader是怎么注入的。大致代码如下:
public static void injectClassLoader(Context context) {
try {
// 获取系统的PathClassLoader
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
// 获取系统的BaseDexClassLoader
BaseDexClassLoader baseDexClassLoader = (BaseDexClassLoader) pathClassLoader.getParent();
// 创建自定义的ClassLoader
ClassLoader newClassLoader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 如果是插件的类,则从插件的dex中加载
// 否则,委托给系统ClassLoader加载
...
}
};
// 将自定义ClassLoader设置到BaseDexClassLoader的parent字段中
setField(baseDexClassLoader, "parent", newClassLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
通过反射将自定义的ClassLoader设置到BaseDexClassLoader的parent字段中,使之成为系统的父ClassLoader。这样,在加载插件的类时,会优先从插件的dex中加载,从而达到隔离加载的目的。
然而,这样做也带来了一个问题:在加载系统的类时,也会委托给自定义ClassLoader去加载,这就会导致额外的耗时。
为了解决这个问题,我们需要在加载系统的类时,绕过自定义ClassLoader。我们可以通过以下方法来实现:
public static void injectClassLoader(Context context) {
try {
// 获取系统的PathClassLoader
PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
// 获取系统的BaseDexClassLoader
BaseDexClassLoader baseDexClassLoader = (BaseDexClassLoader) pathClassLoader.getParent();
// 创建自定义的ClassLoader
ClassLoader newClassLoader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 如果是插件的类,则从插件的dex中加载
// 否则,委托给系统ClassLoader加载
...
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 如果是系统的类,则直接调用父ClassLoader加载
// 否则,委托给自定义ClassLoader加载
if (name.startsWith("android") || name.startsWith("androidx")) {
return super.loadClass(name, resolve);
} else {
return super.findClass(name);
}
}
};
// 将自定义ClassLoader设置到BaseDexClassLoader的parent字段中
setField(baseDexClassLoader, "parent", newClassLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
通过重写ClassLoader的loadClass方法,我们可以控制类加载的流程。如果加载的类是系统的类,则直接调用父ClassLoader加载,否则委托给自定义ClassLoader加载。这样就可以避免在加载系统的类时,经过自定义ClassLoader的耗时过程。
在优化了西瓜视频客户端的ClassLoader注入后,冷启动速度得到了显著提升,启动速度提升了300ms左右。
通过这个案例,我们可以看出,在使用自定义ClassLoader时,需要考虑其对性能的影响,并采取适当的措施来优化性能。