前言
無論是 Java 還是 Android,學習它們的類加載機制都非常重要的。本文統一記錄兩個平臺下 ClassLoader 的實現。
一、Java 中的 ClassLoader
1.1 Bootstrap ClassLoader 引導類加載器
- 作用:
加載 Java 系統類,如java.lang.*、java.uti.*
等。JVM 的啟動也是由它創建的初始類來完成的。 - 特點:
C/C++ 實現;
不繼承于java.lang.ClassLoader
。
1.2 Extensions ClassLoader 擴展類加載器
- 作用:
加載 Java 擴展類,比如 swing 系列(圖形化)、內置的 js 引擎、xml 解析器等等。
1.3 AppClassLoader 應用程序加載器
- 作用:
加載當前應用程序 Classpath 目錄下的所有 jar 和 Class 文件。可指定目錄。
1.4 CustomClassLoader 自定義加載器
- 作用:
自己實現的加載器。 - 特點:
繼承于 java.lang.ClassLoader。
Extensions ClassLoader 和 App ClassLoader 也繼承了java.lang.ClassLoader 類。
Java 類加載器繼承關系
- ClassLoader:是抽象類,定義類加載器主要功能;
- SecureClassLoader:繼承了 ClassLoader,加入權限相關功能,加強安全性;
- URLClassLoader:通過 Uri 路徑從 jar 文件和文件夾中加載類和資源;
- ExtClassLoader 和 AppClassLoader 都繼承自 URLClassLoader,都是 Launcher 的內部類。
Launcher 是 Java 虛擬機的入口應用,ExtClassLoader 和 AppClassLoader 都是在 Launcher 中進行初始化的。
雙親委托(Parents Delegation Model)
- 定義:
所謂的雙親委托,也就是這些 ClassLoader 在加載類或接口時,首先判斷該 Class 是否已經加載。如果沒有加載則去委托給父類去加載,這樣依次查找。
到最后會找到最頂層的 Bootstrap ClassLoader,如果找到或成功加載該 Class 則返回。這樣就完成了一個所謂的 "Parents Delegation" 的過程。
如果最頂層沒有返回,則只能自己加載了。
package java.lang ClassLoader # loadClass 方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
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
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
作用:
至于為什么采用這種模式,是有原因的。Java 中判斷兩個類是否相等,也包括匹配兩者的類加載器。如果兩個類的加載器不同,就算他們名字一樣、路徑一樣,依舊會被認為不是同一個類。
這里的相等包含:equals() 方法、isAssignableFrom() 方法、isInstantce() 方法。優點:
避免重復加載;
更加安全,不容易被隨意篡改。
1.5 自定義 ClassLoader
- 重寫
findClass()
方法,調用loadClass()
時如果父加載器不為空或不能加載,會調用該方法進行加載。 - 加載 Class 文件,這里是使用文件流加載。
public class MyClassLoader extends ClassLoader {
// 1. 重寫 findClass 方法
@Override
protected Class<?> findClass(String name) {
Class clazz = null;
byte[] classData = loadData();
if (null != classData) {
clazz = defineClass(name, classData, 0, classData.length);
}
return clazz;
}
// 2.從磁盤加載文件
private byte[] loadData() {
File file = new File("D:\\Test.class");
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != in) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (null != out) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
- 進行調試,最后打印出來的類名為 TestClass。
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader();
try {
// 指定類名加載,也可以擴展一下遍歷加載文件夾下所有文件
Class c = myClassLoader.loadClass("com.sky.test.Test");
System.out.print(c.getSimpleName());//TestClass
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
二、Android 中的 ClassLoader
Android 中的 ClassLoader 以及繼承結構:
ClassLoader:頂級父類。
-
BootClassLoader:Android 系統啟動時預加載常用類。
-
BaseDexClassLoader
-
PathClassLoader:加載指定目錄下的類;
-
DexClassLoader:加載指定目錄下的類;
-
InMemoryDexClassLoader:加載內存中的類;
-
-
SecureClassLoader:擴展權限功能,增加安全性。
- URLClassLoader:加載 URL 路徑的類和資源。
2.1 BootClassLoader
java.lang.ClassLoader 內部類 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;
}
public BootClassLoader() {
super(null);
}
}
- 作用:
Android 系統啟動時,預加載常用類。 - 特點:
java 實現;
ClassLoader 內部類,同包訪問;
功能大多使用 VMClassLoader(虛擬機 ClassLoader) 調用 native 方法實現。很好理解,因為 VM 是 C/C++ 實現的,所以會調用 native 方法去加載類或資源。
2.2 PathClassLoader
dalvik.system.PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
- 作用:
加載 dex 文件,各種形式的(dex/apk/jar) 。 - 構造器:
dexPath:文件路徑;
librarySearchPath:包含 C、C++庫的路徑集合;
parent:父加載器。
2.3 DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
- 作用:
加載 dex 文件,各種形式的(dex/apk/jar )。 - 構造器
dexPath :dex 文件路徑集合,多個文件用分隔符分割,默認分隔符 “:”;
optimizedDirectory:已解壓 dex 文件路徑,該路徑必須為內部存儲路徑。一般為:/data/data/<Package Name>/...
,作為緩存路徑使用。
librarySearchPath:庫的路徑集合,可以為 null;
parent:父加載器。
PathClassLoader 和 DexClassLoader 異同
DexClassLoader 和 PathDexClassLoader 都能加載各處的 dex 文件。
API26 之前版本傳遞 optimizedDirectory 參數有不同區別:
- DexClassLoader 傳參 optimizedDirectory 可以自定義 dex 優化后存放的路徑。
- PathDexClassLoader 傳 null,緩存到默認 data/dalvik-cache/ 路徑中。
API26 之后統一了緩存 dex 文件的路徑,optimizedDirectory 參數已棄用。統一存放到了 dex 文件同級目錄下的 oat/< isa > 文件作為緩存文件的存儲目錄。
2.4 BootClassLoader 創建過程
Zygote 創建時,調用 ZygoteInit 方法,這個過程中創建了 BootClassLoader :
- ZygoteInit 調用 preload() 方法進行預加載;
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String argv[]) {
...
// 預加載方法
preload(bootTimingsTraceLog);
...
}
- preload() 方法調用 preloadClasses(); 用來加載 Class
static void preload(TimingsTraceLog bootTimingsTraceLog) {
...
preloadClasses();
...
}
- preloadClasses() 方法以流的方式,從系統指定目錄預加載 Class。
private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();
InputStream is;
try {
// 從目錄創建文件輸入流
is = new FileInputStream(PRELOADED_CLASSES);
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
return;
}
try {
// 使用 BufferedReader 讀取流,帶緩存 讀取快
BufferedReader br
= new BufferedReader(new InputStreamReader(is), 256);
int count = 0;
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || line.equals("")) {
continue;
}
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
try {
if (false) {
Log.v(TAG, "Preloading " + line + "...");
}
// 讀取到的數據轉換為 Class
Class.forName(line, true, null);
count++;
} catch (ClassNotFoundException e) {
Log.w(TAG, "Class not found for preloading: " + line);
}
...
} catch (IOException e) {
Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
} finally {
...
}
}
- PRELOADED_CLASSES 路徑下的文件,描述了要預加載的類。這里 Zygote 加載了一些通用類,應用程序進程在創建時就無需再次加載了。
frameworks/base/config/preloaded-classes
...
android.app.Dialog
android.app.Dialog$ListenersHandler
android.app.DialogFragment
...
android.app.Fragment
android.app.Fragment$1
android.app.Fragment$AnimationInfo
android.app.Fragment$OnStartEnterTransitionListener
...
android.app.Activity
android.app.Activity$HostCallbacks
android.app.ActivityManager
android.app.ActivityManager$1
android.app.ActivityManager$AppTask
android.app.ActivityManager$MemoryInfo
- forName 方法根據類名加載 Class。并且如果 loader 為null,就創建了 BootClassLoader。
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
// 創建 BootClassLoader
loader = BootClassLoader.getInstance();
}
Class<?> result;
try {
// 調用 native 方法加載 Class
result = classForName(name, initialize, loader);
} catch (ClassNotFoundException e) {
Throwable cause = e.getCause();
if (cause instanceof LinkageError) {
throw (LinkageError) cause;
}
throw e;
}
return result;
}
調用 native 方法加載 Class。
@FastNative
static native Class<?> classForName(String className, boolean shouldInitialize,
ClassLoader classLoader) throws ClassNotFoundException;
2.5 PathClassLoader 創建過程
- ZygoteInit 中 main 方法會根據參數進行系統初始化
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String argv[]) {
... // preLoad 在之前,也就是 BootClassLoader 先創建預加載
boolean startSystemServer = false;
...
for (int i = 1; i < argv.length; i++) {
...
if ("start-system-server".equals(argv[i])) {
startSystemServer = true;
}
...
}
if (startSystemServer) {
// 看這里
Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
if (r != null) {
r.run();
return;
}
}
}
- forkSystemServer() 方法 fork 系統進程:
private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {
...
int pid;
/* Request to fork the system server process */
// 2.1 創建系統進程,返回 pid
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.runtimeFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
...
if (pid == 0) {
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
zygoteServer.closeServerSocket();
// 2.2 處理系統進程
return handleSystemServerProcess(parsedArgs);
}
return null;
}
- 2.1 forkSystemServer() 調用 native 方法 fork 系統進程。
public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
...
int pid = nativeForkSystemServer(
uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities);
...
return pid;
}
native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
- 2.2 pid == 0,調用 handleSystemServerProcess() 方法處理系統進程邏輯。
private static Runnable handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs) {
...
ClassLoader cl = null;
if (systemServerClasspath != null) {
cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
Thread.currentThread().setContextClassLoader(cl);
}
return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}
- createPathClassLoader() 使用 ClassLoaderFactory 創建 PathClassLoader。
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
String libraryPath = System.getProperty("java.library.path");
return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,
ClassLoader.getSystemClassLoader(), targetSdkVersion, true /* isNamespaceShared */,
null /* classLoaderName */);
frameworks/base/core/java/com/android/internal/os/ClassLoaderFactory.java
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName) {
// 進行判斷,如果 classloaderName 為 null 或 PathClassLoader.class.getName() 則創建 PathClassLoader
if (isPathClassLoaderName(classloaderName)) {
return new PathClassLoader(dexPath, librarySearchPath, parent);
} else if (isDelegateLastClassLoaderName(classloaderName)) {
return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
}
throw new AssertionError("Invalid classLoaderName: " + classloaderName);
到這里就完成了 PathClassLoader 的創建過程。
總結
Java 中的 ClassLoader:
名稱 | 作用 |
---|---|
ClassLoader | ClassLoader 的抽象 |
SecureClassLoader | 加入權限相關功能,加強安全性 |
URLClassLoader | 通過 Uri 讀取文件 |
Bootstrap ClassLoader | 加載 Java 系統類 |
Extensions ClassLoader | 加載 Java 擴展類 |
AppClassLoader | 加載當前應用程序文件 |
ExtClassLoader | 加載 ext 目錄下文件 |
自定義 ClassLoader | 加載用戶指定 Class 文件 |
Android 中的 ClassLoader:
名稱 | 作用 |
---|---|
ClassLoader | ClassLoader 的抽象 |
BootClassLoader | Android 系統啟動時預加載常用類 |
PathClassLoader | 加載指定目錄下的類 |
DexClassLoader | 加載指定目錄下的類 |
InMemoryDexClassLoader | 加載內存中的類 |
SecureClassLoader | 擴展權限功能,增加安全性 |
URLClassLoader | 加載 URL 路徑的類和資源 |