Android ClassLoader之getSystemResourceAsStream源码分析

《Android ClassLoader之getSystemResourceAsStream源码分析》

简介

本文是由上篇Android LOCAL_JAVA_RESOURCE_FILES 的妙用
延伸出来的,主要讲解ClassLoader.getSystemResourceAsStream的底层实现。本文篇幅较长,主要是源码较多,如果不贴源码,又不好理解,所以为了理解深刻,请慢慢细读。

源码分析

先来看下ClassLoader的代码,然后我们去分析它的实现过程。

libcore/libdvm/src/main/java/java/lang/ClassLoader.java

static private class SystemClassLoader {
    public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}

private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path", ".");

    return new PathClassLoader(classPath, BootClassLoader.getInstance());
}

public static InputStream getSystemResourceAsStream(String resName) {
    return SystemClassLoader.loader.getResourceAsStream(resName);
}

class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null, true);
    }

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

    @Override
    public URL getResource(String resName) {
        return findResource(resName);
    }
}

《Android ClassLoader之getSystemResourceAsStream源码分析》

ClassLoader.getSystemResourceAsStream的具体实现,通过上面的源码,我们知道,它就是调用SystemClassLoader.loader.getResouceAsStream方法。而SystemClassLoader.loader就是new了一个
PathClassLoader类,这里需要注意其传入的参数classPath和BootClassLoader.getInstance(),PathClassLoader的继承关系,如下:

《Android ClassLoader之getSystemResourceAsStream源码分析》

libcore/dalvik/src/main/java/dalvik/system/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);
    }
}

libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

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

通过PathClassLoader.java和BaseDexClassLoader.java的源码,我们知道它都没有实现getResouceAsStream方法,所以其getResouceAsStream调用的还是父类ClassLoadler的getResouceAsStream方法。然后我们来看下ClassLoader的具体实现,如下:

libcore/libdvm/src/main/java/java/lang/ClassLoader.java

public InputStream getResourceAsStream(String resName) {
    try {
        URL url = getResource(resName);
        if (url != null) {
            return url.openStream();
        }
    } catch (IOException ex) {
        // Don't want to see the exception.
    }

    return null;
}

public URL getResource(String resName) {
    URL resource = parent.getResource(resName);
    if (resource == null) {
        resource = findResource(resName);
    }
    return resource;
}

通过上面的源码可以看出getResouceAsStream调用了parent.getResource,那么这个parent是谁呢?同时,如果parent没有找到的时候,才会自己去findResource。先来解决parent的问题,我们在new PathClassLoader的传入了两个参数classPath和BootClassLoader.getInstance(),通过PathClassLoader和BaseDexClassLoader的构造函数的源码,可以知道BaseDexClassLoader在构造的时候最终调用了super(parent),所以它最终会调用ClassLoader类的ClassLoader(ClassLoader parentLoader)构造函数。通过下面的源码我们知道parent就是BootClassLoader.getInstance()。

libcore/libdvm/src/main/java/java/lang/ClassLoader.java

protected ClassLoader(ClassLoader parentLoader) {
    this(parentLoader, false);
}

ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
    if (parentLoader == null && !nullAllowed) {
        throw new NullPointerException("parentLoader == null && !nullAllowed");
    }
    parent = parentLoader;
}

private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path", ".");

    return new PathClassLoader(classPath, BootClassLoader.getInstance());
}

class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null, true);
    }

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

    @Override
    public URL getResource(String resName) {
        return findResource(resName);
    }
}

所以,最终parent.getResource()调用的就是BootClassLoader的getResource方法。它又调用了VMClassLoader.getResource(name)方法。其代码实现如下:

libcore/libdvm/src/main/java/java/lang/VMClassLoader.java

static URL getResource(String name) {
    int numEntries = getBootClassPathSize();
    for (int i = 0; i < numEntries; i++) {
        String urlStr = getBootClassPathResource(name, i);
        if (urlStr != null) {
            try {
                return new URL(urlStr);
            } catch (MalformedURLException mue) {
                mue.printStackTrace();
                // unexpected; keep going
            }
        }
    }
    return null;
}

native private static int getBootClassPathSize();
native private static String getBootClassPathResource(String name, int index);

VMClassLoader的getResource的实现又调用到了getBootClassPathResource的JNI方法。最终调用了java_lang_VMClassLoader.cpp中的Dalvik_java_lang_VMClassLoader_getBootClassPathResource方法,然后它调用了Class.cpp中的dvmGetBootPathResource的方法。具体代码如下:
dalvik/vm/native/java_lang_VMClassLoader.cpp

static void Dalvik_java_lang_VMClassLoader_getBootClassPathResource(
    const u4* args, JValue* pResult)
{
    StringObject* nameObj = (StringObject*) args[0];
    StringObject* result;
    int idx = args[1];
    char* name;

    name = dvmCreateCstrFromString(nameObj);
    if (name == NULL)
        RETURN_PTR(NULL);

    result = dvmGetBootPathResource(name, idx);
    free(name);
    dvmReleaseTrackedAlloc((Object*)result, NULL);
    RETURN_PTR(result);
}

const DalvikNativeMethod dvm_java_lang_VMClassLoader[] = {
    ...
    { "getBootClassPathResource", "(Ljava/lang/String;I)Ljava/lang/String;",
        Dalvik_java_lang_VMClassLoader_getBootClassPathResource },
    ...
};

dalvik/vm/oo/Class.cpp


StringObject* dvmGetBootPathResource(const char* name, int idx)
{
    const int kUrlOverhead = 13;        // worst case for Jar URL
    //这里就是bootClassPath的值,可以通过adb shell $BOOTCLASSPATH查看
    const ClassPathEntry* cpe = gDvm.bootClassPath;
    StringObject* urlObj = NULL;

    ALOGV("+++ searching for resource '%s' in %d(%s)",
        name, idx, cpe[idx].fileName);

    /* we could use direct array index, but I don't entirely trust "idx" */
    while (idx-- && cpe->kind != kCpeLastEntry)
        cpe++;
    if (cpe->kind == kCpeLastEntry) {
        assert(false);
        return NULL;
    }

    char urlBuf[strlen(name) + strlen(cpe->fileName) + kUrlOverhead +1];

    switch (cpe->kind) {
    //jar包类型
    case kCpeJar:
        {
            JarFile* pJarFile = (JarFile*) cpe->ptr;
            //判断jar包中是否存在name文件
            if (dexZipFindEntry(&pJarFile->archive, name) == NULL)
                goto bail;
            sprintf(urlBuf, "jar:file://%s!/%s", cpe->fileName, name);
        }
        break;
    case kCpeDex:
        ALOGV("No resources in DEX files");
        goto bail;
    default:
        assert(false);
        goto bail;
    }

    ALOGV("+++ using URL='%s'", urlBuf);
    urlObj = dvmCreateStringFromCstr(urlBuf);

bail:
    return urlObj;
}

通过以上源码分析,parent.getResource最终访问的是BootClassPath中的jar包。接下来我们分析下findResource方法。BaseDexClassLoader有实现findResource,然后它又调用了pathList中的findResource方法。DexPathList的代码如下:

libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

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

libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

private static final String DEX_SUFFIX = ".dex";
private static final String JAR_SUFFIX = ".jar";
private static final String ZIP_SUFFIX = ".zip";
private static final String APK_SUFFIX = ".apk";


public DexPathList(ClassLoader definingContext, String dexPath,
        String libraryPath, File optimizedDirectory) {
    ...
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                       suppressedExceptions);
    ...
}

private static ArrayList<File> splitDexPath(String path) {
    //分割path中以:分隔的路径
    return splitPaths(path, null, false);
}

private static ArrayList<File> splitPaths(String path1, String path2,
            boolean wantDirectories) {
    ArrayList<File> result = new ArrayList<File>();

    splitAndAdd(path1, wantDirectories, result);
    splitAndAdd(path2, wantDirectories, result);
    return result;
}

private static void splitAndAdd(String searchPath, boolean directoriesOnly,
        ArrayList<File> resultList) {
    if (searchPath == null) {
        return;
    }
    //分离出以:分隔的字符串
    for (String path : searchPath.split(":")) {
        try {
            StructStat sb = Libcore.os.stat(path);
            //directoriesOnly为false或者path是路径都会加入到resultList中
            if (!directoriesOnly || S_ISDIR(sb.st_mode)) {
                resultList.add(new File(path));
            }
        } catch (ErrnoException ignored) {
        }
    }
}

//将files中dex、apk、jar、zip文件或文件夹加入Element中。
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) {
                suppressedExceptions.add(suppressed);
            }
        } else if (file.isDirectory()) {
            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()]);
}

public URL findResource(String name) {
    for (Element element : dexElements) {
        URL url = element.findResource(name);
        if (url != null) {
            return url;
        }
    }

    return null;
}

/*package*/
static class Element {
    private final File file;
    private final boolean isDirectory;
    private final File zip;
    private final DexFile dexFile;

    private ZipFile zipFile;
    private boolean initialized;

    public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
        this.file = file;
        this.isDirectory = isDirectory;
        this.zip = zip;
        this.dexFile = dexFile;
    }

    public synchronized void maybeInit() {
        if (initialized) {
            return;
        }

        initialized = true;

        if (isDirectory || zip == null) {
            return;
        }

        try {
            zipFile = new ZipFile(zip);
        } catch (IOException ioe) {
            System.logE("Unable to open zip file: " + file, ioe);
            zipFile = null;
        }
    }

    public URL findResource(String name) {
        maybeInit();

        if (isDirectory) {
            //如果是文件夹就直接判断对应的文件夹下是否存在name文件
            File resourceFile = new File(file, name);
            if (resourceFile.exists()) {
                try {
                    return resourceFile.toURI().toURL();
                } catch (MalformedURLException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }

        //如果是dex、apk、jar、zip文件,就判断它里面是否有name文件
        if (zipFile == null || zipFile.getEntry(name) == null) {
            return null;
        }

        try {
            return new URL("jar:" + file.toURL() + "!/" + name);
        } catch (MalformedURLException ex) {
            throw new RuntimeException(ex);
        }
    }
}

《Android ClassLoader之getSystemResourceAsStream源码分析》

通过以上的代码分析,findResource最终查找到的是classPath中的路径是否有name文件。所以,ClassLoader.getSystemResourceAsStream最终调用的是BootClassLoader的findResource和BaseDexClassLoader的findResource方法。如果我们在BootClassLoader中没有找到name文件,就会去BaseDexClassLoader中查找classPath中的name文件。也就是我们new PathClassLoader(classPath, BootClassLoader.getInstance())的两个参数。先去BootClassLoader.getInstance()查找BootClassPath中的jar包,如果没有就去classPath的路径查找。好,基本上ClassLoader.getSystemResourceAsStream的调用关系基本也分析完了,由于牵涉到的源码比较多,可能比较容易看晕。慢慢理解吧,最好是结合源码查看。

总结

通过对ClassLoader.getSystemResourceAsStream的源码分析,进一步加深了对ClassLoader的了解。还是那句话

Read The Fucking Source Code

下面附上分析getSystemResourceAsStream的时序图:
《Android ClassLoader之getSystemResourceAsStream源码分析》

    原文作者:Android源码分析
    原文地址: https://blog.csdn.net/QQxiaoqiang1573/article/details/78854027
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞