基础概念
-
Android中的ClassLoader类型可分为系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括3种分别是:
-
BootClassLoader
,Android系统启动时会使用BootClassLoader
来预加载常用类,与Java中的Bootstrap ClassLoader
不同的是,它并不是由C/C++代码实现,而是由Java实现的。BootClassLoader
是ClassLoader
的一个内部类。-
PathClassLoader
,只能加载系统中已经安装过的apk。 -
DexClassLoader
,是一个可以从包含classes.dex
实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。可以加载jar/apk/dex,可以从SD卡中加载未安装的apk。 -
PathClassLoader
和DexClasLoader
都是继承自BaseDexClassLoader
,它们的类加载逻辑全部写在BaseDexClassLoader
中。
-
-
源码分析
DexClassLoader分析
DexClassLoader.java
位置:~/android6.0.1_r66/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
继承BaseDexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
参数:
- dexpath:jar或apk文件目录
- optimizedDirectory:优化dex缓存目录
- libraryPath:包含native lib的目录路径
- parent:父类加载器
BaseDexClassLoader.java
位置:~/android6.0.1_r66/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
继承ClassLoader
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
...
}
首先调用父类的构造函数super(parent)
:ClassLoader
然后调用并初始化了DexPathList类型的对象pathList。
ClassLoader.java
位置:~/android6.0.1_r66/libcore/libart/src/main/java/java/lang/ClassLoader.java
public abstract class ClassLoader {
...
protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
if (parentLoader == null && !nullAllowed) {
throw new NullPointerException("parentLoader == null && !nullAllowed");
}
parent = parentLoader;
}
...
}
该构造函数把传进来的父类加载器赋给了私有变量parent。
DexPathList.java
位置:~/android6.0.1_r66/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String zipSeparator = "!/";
...
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexPath == null) {
throw new NullPointerException("dexPath == null");
}
if (optimizedDirectory != null) {
if (!optimizedDirectory.exists()) {
throw new IllegalArgumentException(
"optimizedDirectory doesn't exist: "
+ optimizedDirectory);
}
if (!(optimizedDirectory.canRead()
&& optimizedDirectory.canWrite())) {
throw new IllegalArgumentException(
"optimizedDirectory not readable/writable: "
+ optimizedDirectory);
}
}
this.definingContext = definingContext;
ArrayList suppressedExceptions = new ArrayList();
// save dexPath for BaseDexClassLoader
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
this.nativeLibraryDirectories = splitPaths(libraryPath, false);
this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,
suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}
}
首先对传入参数的验证,然后调用makeDexElements(List
方法
private static Element[] makePathElements(List files, File optimizedDirectory,
List suppressedExceptions) {
List elements = new ArrayList<>();
/*
* Open all files and load the (direct or contained) dex files
* up front.
*/
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
zip = new File(split[0]);
dir = new File(split[1]);
} else if (file.isDirectory()) {
// We support directories for looking up resources and native libraries.
// Looking up resources in directories is useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else if (file.isFile()) {
if (name.endsWith(DEX_SUFFIX)) {//原始的dex文件处理,而不是在zip或者jar中的dex文件
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else {//处理zip或者jar包中的dex文件
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 {
System.logW("ClassLoader referenced unknown path: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(dir, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
dex文件、zip或者jar包中的dex文件都会调用loadDexFile
。
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对象,调用DexFile的构造函数。
位置:~/android6.0.1_r66/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
...
public DexFile(File file) throws IOException {
this(file.getPath());
}
public DexFile(String fileName) throws IOException {//fileName就是上一个构造函数的file.getPath()
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
}
...
关键函数是openDexFile。
private static Object openDexFile(String sourceName, String outputName, int flags) throws IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null) ? null : new File(outputName).getAbsolutePath(),
flags);
}
openDexFileNative是一个native函数,需要找到具体实现:grep -rns
:-r指定要查找的是目录 -n显示行号 -s不显示错误信息
所以openDexFileNative的具体实现就是:
位置:~/android6.0.1_r66/art/runtime/native/dalvik_system_DexFile.cc
static jobject DexFile_openDexFileNative(
JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == nullptr) {
return 0;
}
NullableScopedUtfChars outputName(env, javaOutputName);
if (env->ExceptionCheck()) {
return 0;
}
ClassLinker* linker = Runtime::Current()->GetClassLinker();
std::vector> dex_files;
std::vector error_msgs;
dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
if (!dex_files.empty()) {
jlongArray array = ConvertNativeToJavaArray(env, dex_files);
if (array == nullptr) {
ScopedObjectAccess soa(env);
for (auto& dex_file : dex_files) {
if (Runtime::Current()->GetClassLinker()->IsDexFileRegistered(*dex_file)) {
dex_file.release();
}
}
}
return array;
} else {
ScopedObjectAccess soa(env);
CHECK(!error_msgs.empty());
// The most important message is at the end. So set up nesting by going forward, which will
// wrap the existing exception as a cause for the following one.
auto it = error_msgs.begin();
auto itEnd = error_msgs.end();
for ( ; it != itEnd; ++it) {
ThrowWrappedIOException("%s", it->c_str());
}
return nullptr;
}
}