Android解析ClassLoader(二)Android中的ClassLoader

劉望舒發表於2019-03-03

相關文章
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: java.lang.BootClassLoader@e175998

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

和Java中的ClassLoader一樣,雖然系統所提供的類載入器有3種型別,但是系統提供的ClassLoader相關類卻不只3個。ClassLoader的繼承關係如下圖所示。


可以看到上面一共有7個ClassLoader相關類,其中有一些和Java中的ClassLoader相關類十分類似,下面簡單對它們進行介紹:

  • ClassLoader是一個抽象類,其中定義了ClassLoader的主要功能。BootClassLoader是它的內部類。
  • SecureClassLoader類和JDK8中的SecureClassLoader類的程式碼是一樣的,它繼承了抽象類ClassLoader。SecureClassLoader並不是ClassLoader的實現類,而是擴充了ClassLoader類加入了許可權方面的功能,加強了ClassLoader的安全性。
  • URLClassLoader類和JDK8中的URLClassLoader類的程式碼是一樣的,它繼承自SecureClassLoader,用來通過URl路徑從jar檔案和資料夾中載入類和資源。
  • 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檔案載入機制

相關文章