App加载dex文件源码分析

570 阅读4分钟

App加载dex文件源码分析

App加载dex文件是通过PathClassLoader,我们就来看看它的源码。本人看的是7.0的源码,其他的版本的没有看,有兴趣的,可以去看看。

1,我们首先找到PathClassLoader源码打开,如下代码:

可以看到PathClassLoader里面,挺简单的,它继承BaseDexClassLoader

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 librarySearchPath 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 librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

2,我们点开BaseDexClassLoader,代码如下:

可以看到PathClassLoader,它是去调用BaseDexClassLoader里面的findClass方法,但是方法里面又去调用pathList的findClass方法 。而我们看到pathList是DexPathList的对象。

public class BaseDexClassLoader extends ClassLoader {
//pathList成员变量
private final DexPathList pathList;

/**
 * Constructs an instance.
 *
 * @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; may be {@code null}
 * @param librarySearchPath the list of directories containing native
 * libraries, delimited by {@code File.pathSeparator}; may be
 * {@code null}
 * @param parent the parent class loader
 */
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}
//当调用PathClassLoader,它去调用此方法,查找需要的类名
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
    //可以看到是由pathList再去调用findClass方法。
    Class c = pathList.findClass(name, suppressedExceptions);
    if (c == null) {
        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
        for (Throwable t : suppressedExceptions) {
            cnfe.addSuppressed(t);
        }
        throw cnfe;
    }
    return c;
}

/**
 * @hide
 */
public void addDexPath(String dexPath) {
    pathList.addDexPath(dexPath, null /*optimizedDirectory*/);
}

@Override
protected URL findResource(String name) {
    return pathList.findResource(name);
}

@Override
protected Enumeration<URL> findResources(String name) {
    return pathList.findResources(name);
}

@Override
public String findLibrary(String name) {
    return pathList.findLibrary(name);
}

/**
 * Returns package information for the given package.
 * Unfortunately, instances of this class don't really have this
 * information, and as a non-secure {@code ClassLoader}, it isn't
 * even required to, according to the spec. Yet, we want to
 * provide it, in order to make all those hopeful callers of
 * {@code myClass.getPackage().getName()} happy. Thus we construct
 * a {@code Package} object the first time it is being requested
 * and fill most of the fields with dummy values. The {@code
 * Package} object is then put into the {@code ClassLoader}'s
 * package cache, so we see the same one next time. We don't
 * create {@code Package} objects for {@code null} arguments or
 * for the default package.
 *
 * <p>There is a limited chance that we end up with multiple
 * {@code Package} objects representing the same package: It can
 * happen when when a package is scattered across different JAR
 * files which were loaded by different {@code ClassLoader}
 * instances. This is rather unlikely, and given that this whole
 * thing is more or less a workaround, probably not worth the
 * effort to address.
 *
 * @param name the name of the class
 * @return the package information for the class, or {@code null}
 * if there is no package information available for it
 */
@Override
protected synchronized Package getPackage(String name) {
    if (name != null && !name.isEmpty()) {
        Package pack = super.getPackage(name);

        if (pack == null) {
            pack = definePackage(name, "Unknown", "0.0", "Unknown",
                    "Unknown", "0.0", "Unknown", null);
        }

        return pack;
    }

    return null;
}

/**
 * @hide
 */
public String getLdLibraryPath() {
    StringBuilder result = new StringBuilder();
    for (File directory : pathList.getNativeLibraryDirectories()) {
        if (result.length() > 0) {
            result.append(':');
        }
        result.append(directory);
    }

    return result.toString();
}

@Override public String toString() {
    return getClass().getName() + "[" + pathList + "]";
}
}

3,我们点击开DexPathList进去看看,代码如下:

所以最后是调用DexPathList里面的findClass方法,在里面对dexElements数组进行遍历dex,加载。

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).
 */
 //这里是一个数组,里面存放的就是dex文件
private Element[] dexElements;

//....................省略不相干的代码.............................
 /**
 * Finds the named class in one of the dex files pointed at by
 * this instance. This will find the one in the earliest listed
 * path element. If the class is found but has not yet been
 * defined, then this method will define it in the defining
 * context that this instance was constructed with.
 *
 * @param name of class to find
 * @param suppressed exceptions encountered whilst finding the class
 * @return the named class or {@code null} if the class is not
 * found in any of the dex files
 */
 //pathList调用的是这里的findClass方法。
public Class findClass(String name, List<Throwable> suppressed) {
//遍历dex文件加载
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

//....................省略不相干的代码.............................
}

4,至此分析完毕。

如有不对,请指正。