EP2 加载后的类存在的期限

139 阅读2分钟

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