简介
本文是由上篇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);
}
}
ClassLoader.getSystemResourceAsStream的具体实现,通过上面的源码,我们知道,它就是调用SystemClassLoader.loader.getResouceAsStream方法。而SystemClassLoader.loader就是new了一个
PathClassLoader类,这里需要注意其传入的参数classPath和BootClassLoader.getInstance(),PathClassLoader的继承关系,如下:
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);
}
}
}
通过以上的代码分析,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的时序图: