Android解析ClassLoader(二)Android中的ClassLoader

NO IMAGE

相關文章
Java虛擬機器系列
Android系統啟動系列
Android解析ClassLoader系列

前言

在上一篇文章我們學習了Java的ClassLoader,很多同學會把Java和Android的ClassLoader搞混,甚至會認為Android中的ClassLoader和Java中的ClassLoader是一樣的,這顯然是不對的。這一篇文章我們就來學習Android中的ClassLoader,來看看它和Java中的ClassLoader有何不同。

1.ClassLoader的型別

我們知道Java中的ClassLoader可以載入jar檔案和Class檔案(本質是載入Class檔案),這一點在Android中並不適用,因為無論是DVM還是ART它們載入的不再是Class檔案,而是dex檔案,這就需要重新設計ClassLoader相關類,我們先來學習ClassLoader的型別。
Android中的ClassLoader型別和Java中的ClassLoader型別類似,也分為兩種型別,分別是系統ClassLoader和自定義ClassLoader。其中系統ClassLoader包括三種分別是BootClassLoader、PathClassLoader和DexClassLoader。

1.1 BootClassLoader

Android系統啟動時會使用BootClassLoader來預載入常用類,與Java中的BootClassLoader不同,它並不是由C/C 程式碼實現,而是由Java實現的,BootClassLoade的程式碼如下所示。
libcore/ojluni/src/main/java/java/lang/ClassLoader.java

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是ClassLoader的內部類,並繼承自ClassLoader。BootClassLoader是一個單例類,需要注意的是BootClassLoader的訪問修飾符是預設的,只有在同一個包中才可以訪問,因此我們在應用程式中是無法直接呼叫的。

1.2 PathClassLoader

Android系統使用PathClassLoader來載入系統類和應用程式的類,如果是載入非系統應用程式類,則會載入data/app/目錄下的dex檔案以及包含dex的apk檔案或jar檔案,不管是載入那種檔案,最終都是要載入dex檔案,在這裡為了方便理解,我們將dex檔案以及包含dex的apk檔案或jar檔案統稱為dex相關檔案。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 librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}

PathClassLoader繼承自BaseDexClassLoader,很明顯PathClassLoader的方法實現都在BaseDexClassLoader中。從PathClassLoader的構造方法也可以看出它遵循了雙親委託模式,不瞭解雙親委託模式請檢視 Android解析ClassLoader(一)Java中的ClassLoader 這篇文章。
PathClassLoader的構造方法有三個引數:

  • dexPath:dex檔案以及包含dex的apk檔案或jar檔案的路徑集合,多個路徑用檔案分隔符分隔,預設檔案分隔符為‘:’。
  • librarySearchPath:包含 C/C 庫的路徑集合,多個路徑用檔案分隔符分隔分割,可以為null。
  • parent:ClassLoader的parent。

1.3 DexClassLoader

DexClassLoader可以載入dex檔案以及包含dex的apk檔案或jar檔案,也支援從SD卡進行載入,這也就意味著DexClassLoader可以在應用未安裝的情況下載入dex相關檔案。因此,它是熱修復和外掛化技術的基礎。來檢視它的程式碼,如下所示。
libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}

DexClassLoader構造方法的引數要比PathClassLoader多一個optimizedDirectory引數,引數optimizedDirectory代表什麼呢?我們知道應用程式第一次被載入的時候,為了提高以後的啟動速度和執行效率,Android系統會對dex相關檔案做一定程度的優化,並生成一個ODEX檔案,此後再執行這個應用程式的時候,只要載入優化過的ODEX檔案就行了,省去了每次都要優化的時間,而引數optimizedDirectory就是代表儲存ODEX檔案的路徑,這個路徑必須是一個內部儲存路徑。
PathClassLoader沒有引數optimizedDirectory,這是因為PathClassLoader已經預設了引數optimizedDirectory的路徑為:/data/dalvik-cache。DexClassLoader 也繼承自BaseDexClassLoader ,方法實現也都在BaseDexClassLoader中。

2.ClassLoader的繼承關係

執行一個Android程式需要用到幾種型別的類載入器呢?如下所示。

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader loader = MainActivity.class.getClassLoader();
while (loader != null) {
Log.d("liuwangshu",loader.toString());//1
loader = loader.getParent();
}
}
}

首先我們得到MainActivity的類載入器,並在註釋1處通過Log列印出來,接著列印出當前類的類載入器的父載入器,直到沒有父載入器終止迴圈。列印結果如下所示。

10-07 07:23:02.835 8272-8272/? D/liuwangshu: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/com.example.liuwangshu.moonclassloader-2/base.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_dependencies_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_0_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_1_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_2_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_3_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_4_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_5_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_6_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_7_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_8_apk.apk”, zip file “/data/app/com.example.liuwangshu.moonclassloader-2/split_lib_slice_9_apk.apk”],nativeLibraryDirectories=[/data/app/com.example.liuwangshu.moonclassloader-2/lib/x86, /vendor/lib, /system/lib]]]
10-07 07:23:02.835 8272-8272/? D/liuwangshu: [email protected]

可以看到有兩種類載入器,一種是PathClassLoader,另一種則是BootClassLoader。DexPathList中包含了很多apk的路徑,其中/data/app/com.example.liuwangshu.moonclassloader-2/base.apk就是示例應用安裝在手機上的位置。關於DexPathList後續文章會進行介紹。

和Java中的ClassLoader一樣,雖然系統所提供的類載入器有3種型別,但是系統提供的ClassLoader相關類卻不只3個。ClassLoader的繼承關係如下圖所示。
Android ClassLoader繼承關係(6).png
可以看到上面一共有8個ClassLoader相關類,其中有一些和Java中的ClassLoader相關類十分類似,下面簡單對它們進行介紹:

  • ClassLoader是一個抽象類,其中定義了ClassLoader的主要功能。BootClassLoader是它的內部類。
  • SecureClassLoader類和JDK8中的SecureClassLoader類的程式碼是一樣的,它繼承了抽象類ClassLoader。SecureClassLoader並不是ClassLoader的實現類,而是拓展了ClassLoader類加入了許可權方面的功能,加強了ClassLoader的安全性。
  • URLClassLoader類和JDK8中的URLClassLoader類的程式碼是一樣的,它繼承自SecureClassLoader,用來通過URl路徑從jar檔案和資料夾中載入類和資源。
  • InMemoryDexClassLoader是Android8.0新增的類載入器,繼承自BaseDexClassLoader,用於載入記憶體中的dex檔案。
  • BaseDexClassLoader繼承自ClassLoader,是抽象類ClassLoader的具體實現類,PathClassLoader和DexClassLoader都繼承它。

3.BootClassLoader的建立

BootClassLoader是在何時被建立的呢?這得先從Zygote程序開始說起,不瞭解Zygote程序的可以檢視Android系統啟動流程(二)解析Zygote程序啟動過程這篇文章。
ZygoteInit的main方法如下所示。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

 public static void main(String argv[]) {
...
try {
...
preload(bootTimingsTraceLog);
... 
}
}

main方法是ZygoteInit入口方法,其中呼叫了ZygoteInit的preload方法,preload方法中又呼叫了ZygoteInit的preloadClasses方法,如下所示。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

 private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();
InputStream is;
try {
is = new FileInputStream(PRELOADED_CLASSES);//1
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find "   PRELOADED_CLASSES   ".");
return;
}
...
try {
BufferedReader br
= new BufferedReader(new InputStreamReader(is), 256);//2
int count = 0;
String line;
while ((line = br.readLine()) != null) {//3
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.forName(line, true, null);//4
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 {
...
}
}

preloadClasses方法用於Zygote程序初始化時預載入常用類。註釋1處將/system/etc/preloaded-classes檔案封裝成FileInputStream,preloaded-classes檔案中存有預載入類的目錄,這個檔案在系統原始碼中的路徑為frameworks/base/preloaded-classes,這裡列舉一些preloaded-classes檔案中的預載入類名稱,如下所示。

android.app.ApplicationLoaders
android.app.ApplicationPackageManager
android.app.ApplicationPackageManager$OnPermissionsChangeListenerDelegate
android.app.ApplicationPackageManager$ResourceName
android.app.ContentProviderHolder
android.app.ContentProviderHolder$1
android.app.ContextImpl
android.app.ContextImpl$ApplicationContentResolver
android.app.DexLoadReporter
android.app.Dialog
android.app.Dialog$ListenersHandler
android.app.DownloadManager
android.app.Fragment

可以看到preloaded-classes檔案中的預載入類的名稱有很多都是我們非常熟知的。預載入屬於拿空間換時間的策略,Zygote環境配置的越健全越通用,應用程式程序需要單獨做的事情也就越少,預載入除了預載入類,還有預載入資源和預載入共享庫,因為不是本文重點,這裡就不在延伸講下去了。
回到preloadClasses方法的註釋2處,將FileInputStream封裝為BufferedReader,並註釋3處遍歷BufferedReader,讀出所有預載入類的名稱,每讀出一個預載入類的名稱就呼叫註釋4處的程式碼載入該類,Class的forName方法如下所示。
libcore/ojluni/src/main/java/java/lang/Class.java

    @CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
loader = BootClassLoader.getInstance();//1
}
Class<?> result;
try {
result = classForName(name, initialize, loader);//2
} catch (ClassNotFoundException e) {
Throwable cause = e.getCause();
if (cause instanceof LinkageError) {
throw (LinkageError) cause;
}
throw e;
}
return result;
}

註釋1處建立了BootClassLoader,並將BootClassLoader例項傳入到了註釋2處的classForName方法中,classForName方法是Native方法,它的實現由c/c 程式碼來完成,如下所示。

    @FastNative
static native Class<?> classForName(String className, boolean shouldInitialize,
ClassLoader classLoader) throws ClassNotFoundException;

4.PathClassLoader的建立

PathClassLoader的建立也得從Zygote程序開始說起,Zygote程序啟動SyetemServer程序時會呼叫ZygoteInit的startSystemServer方法,如下所示。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

private static boolean startSystemServer(String abiList, String socketName)
throws MethodAndArgsCaller, RuntimeException {
...
int pid;
try {
parsedArgs = new ZygoteConnection.Arguments(args);//2
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
/*1*/
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
if (pid == 0) {//2
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
handleSystemServerProcess(parsedArgs);//3
}
return true;
}

註釋1處,Zygote程序通過forkSystemServer方法fork自身建立子程序(SystemServer程序)。註釋2處如果forkSystemServer方法返回的pid等於0,說明當前程式碼是在新建立的SystemServer程序中執行的,接著就會執行註釋3處的handleSystemServerProcess方法:
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

 private static void handleSystemServerProcess(
ZygoteConnection.Arguments parsedArgs)
throws Zygote.MethodAndArgsCaller {
...
if (parsedArgs.invokeWith != null) {
...
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);//1
Thread.currentThread().setContextClassLoader(cl);
}
ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}
}

註釋1處呼叫了createPathClassLoader方法,如下所示。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

  static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {
String libraryPath = System.getProperty("java.library.path");
return PathClassLoaderFactory.createClassLoader(classPath,
libraryPath,
libraryPath,
ClassLoader.getSystemClassLoader(),
targetSdkVersion,
true /* isNamespaceShared */);
}

createPathClassLoader方法中又會呼叫PathClassLoaderFactory的createClassLoader方法,看來PathClassLoader是用工廠來進行建立的。
frameworks/base/core/java/com/android/internal/os/PathClassLoaderFactory.java

  public static PathClassLoader createClassLoader(String dexPath,
String librarySearchPath,
String libraryPermittedPath,
ClassLoader parent,
int targetSdkVersion,
boolean isNamespaceShared) {
PathClassLoader pathClassloader = new PathClassLoader(dexPath, librarySearchPath, parent);
...
return pathClassloader;
}

在PathClassLoaderFactory的createClassLoader方法中會建立PathClassLoader。

結語

在這篇文章中我們學習了Android的ClassLoader的型別、ClassLoader的繼承關係以及BootClassLoader和PathClassLoader是何時建立的。BootClassLoader是在Zygote程序的入口方法中建立的,PathClassLoader則是在Zygote程序建立SystemServer程序時建立的。本系列後續文章會接著介紹Android中的ClassLoader的其他知識點,敬請期待。

參考資料
Android動態載入之ClassLoader詳解
熱修復入門:Android 中的 ClassLoader
淺析dex檔案載入機制