Android解析ClassLoader(二)Android中的ClassLoader

前言

在上一篇文章我們學(xué)習(xí)了Java的ClassLoader,很多同學(xué)會把Java和Android的ClassLoader搞混狂丝,甚至?xí)J為Android中的ClassLoader和Java中的ClassLoader是一樣的具被,這顯然是不對的豁翎。這一篇文章我們就來學(xué)習(xí)Android中的ClassLoader,來看看它和Java中的ClassLoader有何不同。

1.ClassLoader的類型

我們知道Java中的ClassLoader可以加載jar文件和Class文件(本質(zhì)是加載Class文件)秉颗,這一點在Android中并不適用稽穆,因為無論是DVM還是ART它們加載的不再是Class文件,而是dex文件米丘,這就需要重新設(shè)計ClassLoader相關(guān)類剑令,我們先來學(xué)習(xí)ClassLoader的類型。
Android中的ClassLoader類型和Java中的ClassLoader類型類似拄查,也分為兩種類型吁津,分別是系統(tǒng)ClassLoader和自定義ClassLoader。其中系統(tǒng)ClassLoader包括三種分別是BootClassLoader堕扶、PathClassLoader和DexClassLoader碍脏。

1.1 BootClassLoader

Android系統(tǒng)啟動時會使用BootClassLoader來預(yù)加載常用類梭依,與Java中的BootClassLoader不同,它并不是由C/C++代碼實現(xiàn)典尾,而是由Java實現(xiàn)的役拴,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的內(nèi)部類急黎,并繼承自ClassLoader扎狱。BootClassLoader是一個單例類,需要注意的是BootClassLoader的訪問修飾符是默認的勃教,只有在同一個包中才可以訪問淤击,因此我們在應(yīng)用程序中是無法直接調(diào)用的。

1.2 DexClassLoader

DexClassLoader可以加載dex文件以及包含dex的壓縮文件(apk和jar文件)故源,不管是加載哪種文件污抬,最終都是要加載dex文件,為了方便理解和敘述绳军,將dex文件以及包含dex的壓縮文件統(tǒng)稱為dex相關(guān)文件印机。
來查看DexClassLoader的代碼,如下所示门驾。
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的構(gòu)造方法有四個參數(shù):

  • dexPath:dex相關(guān)文件路徑集合射赛,多個路徑用文件分隔符分隔,默認文件分隔符為‘:’
  • optimizedDirectory:解壓的dex文件存儲路徑奶是,這個路徑必須是一個內(nèi)部存儲路徑楣责,一般情況下使用當(dāng)前應(yīng)用程序的私有路徑:/data/data/<Package Name>/...
  • librarySearchPath:包含 C/C++ 庫的路徑集合聂沙,多個路徑用文件分隔符分隔分割秆麸,可以為null。
  • parent:父加載器及汉。

DexClassLoader 繼承自BaseDexClassLoader 沮趣,方法實現(xiàn)都在BaseDexClassLoader中。

1.3 PathClassLoader

Android系統(tǒng)使用PathClassLoader來加載系統(tǒng)類和應(yīng)用程序的類坷随,來查看它的代碼:
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房铭,實現(xiàn)也都在BaseDexClassLoader中。

PathClassLoader的構(gòu)造方法中沒有參數(shù)optimizedDirectory温眉,這是因為PathClassLoader已經(jīng)默認了參數(shù)optimizedDirectory的值為:/data/dalvik-cache育叁,很顯然PathClassLoader無法定義解壓的dex文件存儲路徑,因此PathClassLoader通常用來加載已經(jīng)安裝的apk的dex文件(安裝的apk的dex文件會存儲在/data/dalvik-cache中)芍殖。

2.ClassLoader的繼承關(guān)系

運行一個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打印出來龟梦,接著打印出當(dāng)前類的類加載器的父加載器,直到?jīng)]有父加載器終止循環(huán)窃躲。打印結(jié)果如下所示计贰。

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就是示例應(yīng)用安裝在手機上的位置洒琢。關(guān)于DexPathList后續(xù)文章會進行介紹秧秉。

和Java中的ClassLoader一樣,雖然系統(tǒng)所提供的類加載器有3種類型衰抑,但是系統(tǒng)提供的ClassLoader相關(guān)類卻不只3個象迎。ClassLoader的繼承關(guān)系如下圖所示。



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

  • ClassLoader是一個抽象類,其中定義了ClassLoader的主要功能谭网。BootClassLoader是它的內(nèi)部類汪厨。
  • SecureClassLoader類和JDK8中的SecureClassLoader類的代碼是一樣的,它繼承了抽象類ClassLoader愉择。SecureClassLoader并不是ClassLoader的實現(xiàn)類劫乱,而是拓展了ClassLoader類加入了權(quán)限方面的功能,加強了ClassLoader的安全性薄辅。
  • URLClassLoader類和JDK8中的URLClassLoader類的代碼是一樣的要拂,它繼承自SecureClassLoader,用來通過URl路徑從jar文件和文件夾中加載類和資源站楚。
  • BaseDexClassLoader繼承自ClassLoader脱惰,是抽象類ClassLoader的具體實現(xiàn)類,PathClassLoader和DexClassLoader都繼承它窿春。

3.BootClassLoader的創(chuàng)建

BootClassLoader是在何時被創(chuàng)建的呢拉一?這得先從Zygote進程開始說起,不了解Zygote進程的可以查看Android系統(tǒng)啟動流程(二)解析Zygote進程這篇文章旧乞。
ZygoteInit的main方法如下所示蔚润。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

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

main方法是ZygoteInit入口方法,其中調(diào)用了ZygoteInit的preload方法尺栖,preload方法中又調(diào)用了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進程初始化時預(yù)加載常用類。注釋1處將/system/etc/preloaded-classes文件封裝成FileInputStream除盏,preloaded-classes文件中存有預(yù)加載類的目錄叉橱,這個文件在系統(tǒng)源碼中的路徑為frameworks/base/preloaded-classes,這里列舉一些preloaded-classes文件中的預(yù)加載類名稱者蠕,如下所示窃祝。

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文件中的預(yù)加載類的名稱有很多都是我們非常熟知的。預(yù)加載屬于拿空間換時間的策略踱侣,Zygote環(huán)境配置的越健全越通用粪小,應(yīng)用程序進程需要單獨做的事情也就越少,預(yù)加載除了預(yù)加載類抡句,還有預(yù)加載資源和預(yù)加載共享庫探膊,因為不是本文重點,這里就不在延伸講下去了玉转。
回到preloadClasses方法的注釋2處突想,將FileInputStream封裝為BufferedReader,并注釋3處遍歷BufferedReader究抓,讀出所有預(yù)加載類的名稱猾担,每讀出一個預(yù)加載類的名稱就調(diào)用注釋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處創(chuàng)建了BootClassLoader绑嘹,并將BootClassLoader實例傳入到了注釋2處的classForName方法中,classForName方法是Native方法橘茉,它的實現(xiàn)由c/c++代碼來完成工腋,如下所示。

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

4.PathClassLoader的創(chuàng)建

PathClassLoader的創(chuàng)建也得從Zygote進程開始說起畅卓,Zygote進程啟動SyetemServer進程時會調(diào)用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自身創(chuàng)建子進程(SystemServer進程)趁冈。注釋2處如果forkSystemServer方法返回的pid等于0,說明當(dāng)前代碼是在新創(chuàng)建的SystemServer進程中執(zhí)行的拜马,接著就會執(zhí)行注釋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處調(diào)用了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方法中又會調(diào)用PathClassLoaderFactory的createClassLoader方法俩莽,看來PathClassLoader是用工廠來進行創(chuàng)建的旺坠。
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方法中會創(chuàng)建PathClassLoader。

結(jié)語

在這篇文章中我們學(xué)習(xí)了Android的ClassLoader的類型扮超、ClassLoader的繼承關(guān)系以及BootClassLoader和PathClassLoader是何時創(chuàng)建的取刃。BootClassLoader是在Zygote進程的入口方法中創(chuàng)建的蹋肮,PathClassLoader則是在Zygote進程創(chuàng)建SystemServer進程時創(chuàng)建的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末璧疗,一起剝皮案震驚了整個濱河市括尸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌病毡,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屁柏,死亡現(xiàn)場離奇詭異啦膜,居然都是意外死亡,警方通過查閱死者的電腦和手機淌喻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門僧家,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裸删,你說我怎么就攤上這事八拱。” “怎么了涯塔?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵肌稻,是天一觀的道長。 經(jīng)常有香客問我匕荸,道長爹谭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任榛搔,我火速辦了婚禮诺凡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘践惑。我一直安慰自己腹泌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布尔觉。 她就那樣靜靜地躺著凉袱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪穷娱。 梳的紋絲不亂的頭發(fā)上绑蔫,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音泵额,去河邊找鬼配深。 笑死,一個胖子當(dāng)著我的面吹牛嫁盲,可吹牛的內(nèi)容都是我干的篓叶。 我是一名探鬼主播烈掠,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缸托!你這毒婦竟也來了左敌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤俐镐,失蹤者是張志新(化名)和其女友劉穎矫限,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體佩抹,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡叼风,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了棍苹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片无宿。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖枢里,靈堂內(nèi)的尸體忽然破棺而出孽鸡,到底是詐尸還是另有隱情,我是刑警寧澤栏豺,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布彬碱,位于F島的核電站,受9級特大地震影響冰悠,放射性物質(zhì)發(fā)生泄漏堡妒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一溉卓、第九天 我趴在偏房一處隱蔽的房頂上張望皮迟。 院中可真熱鬧,春花似錦桑寨、人聲如沸伏尼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爆阶。三九已至,卻和暖如春沙咏,著一層夾襖步出監(jiān)牢的瞬間辨图,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工肢藐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留故河,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓吆豹,卻偏偏與公主長得像鱼的,于是被迫代替她去往敵國和親理盆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容