当前位置: 代码迷 >> 综合 >> ClassLoader 三 Android 类加载
  详细解决方案

ClassLoader 三 Android 类加载

热度:27   发布时间:2023-12-08 11:54:41.0

一 前言

Android类加载同样遵循jvm虚拟机规范,不同点是class不是被类加载器直接装载,而是先被打包成dex文件,然后交由BaseDexClassLoader来完成类加载。

二 分类

BaseDexClassLoader 包含两个子类
1. DexClassLoader
2. PathClassLoader

下面分别看下对应的源码 :

package dalvik.system;import java.io.File;/*** A class loader that loads classes from {
    @code .jar} and {
    @code .apk} files* containing a {
    @code classes.dex} entry. This can be used to execute code not* installed as part of an application.** <p>This class loader requires an application-private, writable directory to* cache optimized classes. Use {
    @code Context.getDir(String, int)} to create* such a directory: <pre> {
    @code* File dexOutputDir = context.getDir("dex", 0);* }</pre>** <p><strong>Do not cache optimized classes on external storage.</strong>* External storage does not provide access controls necessary to protect your* application from code injection attacks.*/
public class DexClassLoader extends BaseDexClassLoader {
    /*** Creates a {
    @code DexClassLoader} that finds interpreted and native* code. Interpreted classes are found in a set of DEX files contained* in Jar or APK files.** <p>The path lists are separated using the character specified by the* {
    @code path.separator} system property, which defaults to {
    @code :}.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {
    @code File.pathSeparator}, which* defaults to {
    @code ":"} on Android* @param optimizedDirectory directory where optimized dex files* should be written; must not be {
    @code null}* @param libraryPath the list of directories containing native* libraries, delimited by {
    @code File.pathSeparator}; may be* {
    @code null}* @param parent the parent class loader*/public DexClassLoader(String dexPath, String optimizedDirectory,String libraryPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), libraryPath, parent);}
}
package dalvik.system;/*** Provides a simple {@link ClassLoader} implementation that operates on a list* of files and directories in the local file system, but does not attempt to* load classes from the network. Android uses this class for its system class* loader and for its application class loader(s).*/
public class PathClassLoader extends BaseDexClassLoader {/*** Creates a {@code PathClassLoader} that operates on a given list of files* and directories. This method is equivalent to calling* {@link #PathClassLoader(String, String, ClassLoader)} with a* {@code null} value for the second argument (see description there).** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param parent the parent class loader*/public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}/*** Creates a {@code PathClassLoader} that operates on two given* lists of files and directories. The entries of the first list* should be one of the following:** <ul>* <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as* well as arbitrary resources.* <li>Raw ".dex" files (not inside a zip file).* </ul>** The entries of the second list should be directories containing* native library files.** @param dexPath the list of jar/apk files containing classes and* resources, delimited by {@code File.pathSeparator}, which* defaults to {@code ":"} on Android* @param libraryPath the list of directories containing native* libraries, delimited by {@code File.pathSeparator}; may be* {@code null}* @param parent the parent class loader*/public PathClassLoader(String dexPath, String libraryPath,ClassLoader parent) {super(dexPath, null, libraryPath, parent);}
}

从源码对比来看,二者只是在构造函数中传入的参数不同,最终都是通过父类BaseDexClassLoader构造方法完成初始化,代码如下:

public BaseDexClassLoader(String dexPath, File optimizedDirectory,String libraryPath, ClassLoader parent) {super(parent);this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);}

BaseDexClassLoader构造方法有四个参数:
1. dexPath dex文件存放的路径
2. optimizedDirectory 优化后的dex存放路径
3. libraryPath 动态库的存放路径
4. parent 父亲类加载器

1 3 4都好理解,2相对特别一些,也是PathClassLoader想较DexClassLoader的不同的地方,PathClassLoader传入的optimizedDirectory是null。optimizedDirectory有什么作用呢?先继续跟踪代码看下去

BaseDexClassLoader构造方法里面只执行了一句

 this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

创建一个DexPathList对象,跟踪这个类


/*** A pair of lists of entries, associated with a {
    @code ClassLoader}.* One of the lists is a dex/resource path &mdash; typically referred* to as a "class path" &mdash; list, and the other names directories* containing native code libraries. Class path entries may be any of:* a {
    @code .jar} or {
    @code .zip} file containing an optional* top-level {
    @code classes.dex} file as well as arbitrary resources,* or a plain {
    @code .dex} file (with no possibility of associated* resources).** <p>This class also contains methods to use these lists to look up* classes and resources.</p>*/
/*package*/ final class DexPathList {
    /*** List of dex/resource (class path) elements.* Should be called pathElements, but the Facebook app uses reflection* to modify 'dexElements' (http://b/7726934).*/private final Element[] dexElements;/** List of native library directories. */private final File[] nativeLibraryDirectories;

DexPathList提供以下功能:
1. dex/资源元素列表
2. 动态库文件列表
3. 提供了查找类和资源的方法

2 3 相对容易理解,主要看1,对应代码如下:

 /*** Makes an array of dex/resource path elements, one per element of* the given array.*/private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions) {ArrayList<Element> elements = new ArrayList<Element>();/** Open all files and load the (direct or contained) dex files* up front.*/for (File file : files) {File zip = null;DexFile dex = null;String name = file.getName();if (name.endsWith(DEX_SUFFIX)) {// Raw dex file (not inside a zip/jar).try {dex = loadDexFile(file, optimizedDirectory);} catch (IOException ex) {System.logE("Unable to load dex file: " + file, ex);}} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)|| name.endsWith(ZIP_SUFFIX)) {zip = file;try {dex = loadDexFile(file, optimizedDirectory);} catch (IOException suppressed) {/** IOException might get thrown "legitimately" by the DexFile constructor if the* zip file turns out to be resource-only (that is, no classes.dex file in it).* Let dex == null and hang on to the exception to add to the tea-leaves for* when findClass returns null.*/suppressedExceptions.add(suppressed);}} else if (file.isDirectory()) {// We support directories for looking up resources.// This is only useful for running libcore tests.elements.add(new Element(file, true, null, null));} else {System.logW("Unknown file type for: " + file);}if ((zip != null) || (dex != null)) {elements.add(new Element(file, false, zip, dex));}}return elements.toArray(new Element[elements.size()]);}

该方法主要目标是创建一个dexfile,然后转换成element数组。下面来看dexfile的加载

 /*** Constructs a {@code DexFile} instance, as appropriate depending* on whether {@code optimizedDirectory} is {@code null}.*/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);}}

根据optimizedDirectory是否为null,分别创建不同的DexFile对象。

现在回头来看optimizedDirectory的作用。一个apk文件在安卓设备上有两种存在状态,一种是已经安装了,一种是作为普通文件存放在sdcard上面。apk安装以后会在/data/目录生成一些文件,比如:

/data/app 存放了apk文件

/data/data 存放apk运行过程生成的一些缓存文件

/data/dalvik-cache 存放apk里面的dex文件

因此,假如apk已经安装了,那么jvm加载dex的时候无须指定optimizedDirectory,只需要到/data/dalvik-cache来查找就行了,因此这种情况可以使用PathClassLoader。反之,apk没有安装,那么在加载apk文件的时候需要将zip包里面的dex文件解压出来,存放到一个内部目录供后续jvm加载,这也就是optimizedDirectory的存在意义。

三 总结

至此,通过源码跟踪大致了解了android类加载机制的组成。主要是由BaseDexClassLoader来处理,根据apk是否安装到了设备上面又分为两种类加载器:DexClassLoader和PathClassLoader。

后面可以对DexClassLoader进一步学习,看看它是怎么如何在热修复上发挥重要作用的。

  相关解决方案