0x01 加载后的类存在的期限
昨天说到的问题是,ClassLoader在一个App中至少有两个实例,一个是系统启动时创建的Boot类型的,一个是App中fork出来的;而且如果一个类被加载过,那么这个类永远不会被重新加载。 这个「永远」的期限是什么呢?
我们可以看看ClassLoader的实现。ClassLoader是Abstract类型,我们要使用它的子类DexClassLoader、PathClassLoader 来实现类加载。
从网络上可以查到,DexClassLoader、PathClassLoader 的区别是:
- DexClassLoader 可以加载 jar/apk/dex,可以从 SD 卡中加载未安装的 apk;
- PathClassLoader 只能加载系统中已经安装过的 apk;
// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
DexClassLoader的构造参数有dexPath,optimizedDirectory,libraryPath和parent。
// PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
PathClassLoader的构造参数就只有dexPath和libraryPath,少了一个optimizedDirectory(super中传了null)。那么,看来这个optimizedDirectory就是为什么PathClassLoader「只能加载系统中已经安装过的 apk」的原因。
看看他们共同的父类BaseDexClassLoader里面的实现:
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.originalPath = dexPath;
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
创建了系统自动loadClass之后的dexPath,以及一个DexPathList对象。
在DexPathList.java中有这样的方法:
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
以及:
/**
* Converts a dex/jar file path and an output directory to an
* output file path for an associated optimized dex file.
* 为关联的最优化dexfile把dex/jar文件路径和输出目录转换成一个output文件路径
*/
private static String optimizedPathFor(File path,
File optimizedDirectory) {
String fileName = path.getName();
if (!fileName.endsWith(DEX_SUFFIX)) {
int lastDot = fileName.lastIndexOf(".");
if (lastDot < 0) {
fileName += DEX_SUFFIX;
} else {
StringBuilder sb = new StringBuilder(lastDot + 4);
sb.append(fileName, 0, lastDot);
sb.append(DEX_SUFFIX);
fileName = sb.toString();
}
}
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}
也就是optimizedDirectory用来存储加载的dex文件,比如想要加载sd卡上的dex,就填写对应的文件路径。
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
所以昨天的问题大概清楚了,既然是创建了一个文件来保存,而且这个文件是保存到应用内的(file.getPath()),所以类加载之后保存的「期限」就是在应用清空缓存或者卸载应用前。
前面我们了解到,凡是被父母加载过的类都不会重新被加载。这样的话,如果想要动态更新一个类,比如想要用到更新插件apk来实现「热修复」,就必须用一个不同的类,否则classloader会使用加载过的类。所以我们在使用新的插件的时候,要构造一个新的classLoader来加载这个插件的dex。或者,也许可以先清空之前加载过的dex的缓存路径。
时间不够了,明天再说吧。
-NOV22