Android中的ClassLoader類型可分爲系統ClassLoader和自定義ClassLoader。其中系統ClassLoader包括3種分別是:java
BootClassLoader
,Android系統啓動時會使用BootClassLoader
來預加載經常使用類,與Java中的Bootstrap ClassLoader
不一樣的是,它並非由C/C++代碼實現,而是由Java實現的。BootClassLoader
是ClassLoader
的一個內部類。android
PathClassLoader
,只能加載系統中已經安裝過的apk。DexClassLoader
,是一個能夠從包含classes.dex
實體的.jar或.apk文件中加載classes的類加載器。能夠用於實現dex的動態加載、代碼熱更新等等。能夠加載jar/apk/dex,能夠從SD卡中加載未安裝的apk。PathClassLoader
和DexClasLoader
都是繼承自 BaseDexClassLoader
,它們的類加載邏輯所有寫在BaseDexClassLoader
中。位置:~/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); } }
參數:git
位置:~/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。緩存
位置:~/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。app
位置:~/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<IOException> suppressedExceptions = new ArrayList<IOException>(); // 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<File> 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<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader)
方法函數
private static Element[] makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions) { List<Element> 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。this
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的具體實現就是:spa
位置:~/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<std::unique_ptr<const DexFile>> dex_files; std::vector<std::string> 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; } }