一、Java中的ClassLoader
1、繼承關係
- ClassLoader是一個抽象類,其中定義了ClassLoader的主要功能。
- SecureClassLoader繼承了抽象類ClassLoader,但SecureClassLoader並不是ClassLoader的實現類,而是擴充了ClassLoader類加入了許可權方面的功能,加強了ClassLoader的安全性。
- URLClassLoader繼承自SecureClassLoader,用來通過URl路徑從jar檔案和資料夾中載入類和資源。
- ExtClassLoader和AppClassLoader都繼承自URLClassLoader,它們都是Launcher 的內部類,Launcher 是Java虛擬機器的入口應用,ExtClassLoader和AppClassLoader都是在Launcher中進行初始化的。
二、Android中的ClassLoader
1、繼承關係
- ClassLoader是一個抽象類,其中定義了ClassLoader的主要功能。BootClassLoader是它的內部類。
- SecureClassLoader繼承了抽象類ClassLoader。SecureClassLoader並不是ClassLoader的實現類,而是擴充了ClassLoader類加入了許可權方面的功能,加強了ClassLoader的安全性。
- URLClassLoader類繼承自SecureClassLoader,用來通過URl路徑從jar檔案和資料夾中載入類和資源。
- InMemoryDexClassLoader是Android8.0新增的類載入器,繼承自BaseDexClassLoader,用於載入記憶體中的dex檔案。
- BaseDexClassLoader繼承自ClassLoader,是抽象類ClassLoader的具體實現類,PathClassLoader和DexClassLoader都繼承它。
2、作用和分類
(1)作用:
Android虛擬機器執行的是Dex位元組碼(將Class檔案合併優化生成的產物),ClassLoader用來載入dex檔案。
(2)分類
分為系統類載入器和自定義類載入器。主要的系統類載入器:
- BootClassLoader
- PathClassLoader
- DexClassLoader
三、Android中ClassLoader原始碼分析
相關原始碼:
/dalvik/system
1、ClassLoader
(1)建構函式:
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
複製程式碼
ClassLoader需要傳入一個父ClassLoader,如果父ClassLoader不傳的時候,通過 getSystemClassLoader方法,建立了一個PathClassLoader
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}
static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
複製程式碼
(2)loadClass方法:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
//(1)先判斷這個類是否被載入過
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
//(2)如果沒被載入,先讓父載入器進行載入
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//(3)如果父載入器載入失敗,則自身進行載入
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
複製程式碼
雙親委託機制:
(1)除了頂層的類載入器外,其他的類載入器都有自己的父類載入器,在載入類時首先判斷這個類是否被載入過,如果已經載入則直接返回。
(2)如果未被載入過,則先嚐試讓父載入器進行載入,最終所有載入請求都會傳遞給頂層的載入器中。
(3)當父載入器發現未找到所需的類而無法完成載入請求時,子載入器的findClass方法中進行載入。
2、BaseDexClassLoader
(1)構造:
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, librarySearchPath, parent, null, false);
}
public BaseDexClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
boolean isTrusted) {
super(parent);
this.sharedLibraryLoaders = sharedLibraryLoaders == null
? null
: Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
if (reporter != null) {
reportClassLoaderChain();
}
}
複製程式碼
- dexPath:指目標類所在的APK或jar檔案的路徑,如果要包含多個路徑,路徑之間必須使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)獲得。
- optimizedDirectory:解壓的dex檔案儲存路徑,這個路徑必須是一個內部儲存路徑,一般情況下使用當前應用程式的私有路徑:/data/data//...。
- librarySearchPath:指目標類中所使用的C/C++庫存放的路徑
- parent:父載入器。
(2)DexPathList
DexPathList中維護著一個Element陣列,這個陣列中Element元素就是Dex檔案
#DexPathList
/*package*/ static class Element {
@UnsupportedAppUsage
private final File path;
/** Whether {@code path.isDirectory()}, or {@code null} if {@code path == null}. */
private final Boolean pathIsDirectory;
@UnsupportedAppUsage
private final DexFile dexFile;
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;
@UnsupportedAppUsage
public Element(DexFile dexFile, File dexZipPath) {
if (dexFile == null && dexZipPath == null) {
throw new NullPointerException("Either dexFile or path must be non-null");
}
this.dexFile = dexFile;
this.path = dexZipPath;
// Do any I/O in the constructor so we don't have to do it elsewhere, eg. toString().
this.pathIsDirectory = (path == null) ? null : path.isDirectory();
}
public Element(DexFile dexFile) {
this(dexFile, null);
}
public Element(File path) {
this(null, path);
}
......
}
複製程式碼
3、BootClassLoader
Android平臺上所有ClassLoader的最終parent。Android系統啟動時會使用BootClassLoader來預載入常用類。
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;
}
...
}
複製程式碼
BootClassLoader的訪問修飾符是預設的,只有在同一個包中才可以訪問
4、PathClassLoader
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
@libcore.api.CorePlatformApi
public PathClassLoader(
String dexPath, String librarySearchPath, ClassLoader parent,
ClassLoader[] sharedLibraryLoaders) {
super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);
}
複製程式碼
- 使用PathClassLoader來載入系統類和應用程式的類,通常用來載入已安裝的apk的dex檔案
- 建構函式沒有optimizedDirectory,無法定義dex檔案路徑,該引數預設值為/data/dalvik-cache
5、DexClassLoader
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
複製程式碼
DexClassLoader可以載入dex檔案以及包含dex的壓縮檔案(apk和jar檔案),在其父類BaseDexClassLoader裡對".jar",".zip",".apk",".dex"字尾的檔案最後都會生成一個對應的dex檔案。
四、Android載入Class的過程
1、BaseDexClassLoader的findClass
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// First, check whether the class is present in our shared libraries.
if (sharedLibraryLoaders != null) {
for (ClassLoader loader : sharedLibraryLoaders) {
try {
return loader.loadClass(name);
} catch (ClassNotFoundException ignored) {
}
}
}
// Check whether the class in question is present in the dexPath that
// this classloader operates on.
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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;
}
複製程式碼
內部呼叫了DexPathList的findClass方法
#DexPathList
private Element[] dexElements;
......
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
複製程式碼
DexPathList內部包含了一個DexFile的陣列dexElements。類載入的過程,就是遍歷這個陣列呼叫了DexFile的loadClassBinaryName方法,最終呼叫native方法defineClassNative。