FrameWork源碼解析(7)-ClassLoader及dex加載過程

主目錄見:Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)
在線源碼查看:AndroidXRef

了解這一篇的知識(shí)對(duì)后面插件化中的類加載是必不可少的,我們知道罚缕,我們的應(yīng)用中的類在編譯過程中會(huì)被編譯成dex文件并鸵,所以把我們的dex加載進(jìn)我們的程序我們就可以查找到插件中的類苇倡。我們今天就會(huì)來了解這一過程。

一.類加載器

學(xué)過java的應(yīng)該知道审姓,我們的類是通過類加載器加載到JVM的珍特,Android也不例外,Android中有兩個(gè)特別重要的ClassLoader:
1.PathClassLoader:

類描述

可以看到這邊英文描述了這個(gè)類加載器不會(huì)加載網(wǎng)絡(luò)上的類魔吐,只會(huì)加載系統(tǒng)類和應(yīng)用類的扎筒,而且在dalvik虛擬機(jī)上只能加載已經(jīng)安裝的apk的dex。當(dāng)然在android 5.0之后是否可以加載未安裝的apk的dex酬姆,這個(gè)沒做過實(shí)驗(yàn)嗜桌。但是可以知道,用這個(gè)類加載器來加載插件中的dex是不可行的辞色。我們看下完整的這個(gè)類:

public class PathClassLoader extends BaseDexClassLoader {

 public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

 public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}

我們看到構(gòu)造函數(shù)都是調(diào)用了super的構(gòu)造函數(shù)骨宠,所以我們待會(huì)在看BaseDexClassLoader時(shí)候會(huì)詳細(xì)來說明。但是我們看第二個(gè)參數(shù)為空相满,這個(gè)參數(shù)是optimizedDirectory层亿,是dex文件被加載后會(huì)被編譯器優(yōu)化,優(yōu)化之后的dex存放路徑立美,因?yàn)?code>PathClassLoader只能加載系統(tǒng)類或者應(yīng)用的類棕所,所以這個(gè)為空,其實(shí)optimizedDirectory為null時(shí)的默認(rèn)路徑就是/data/dalvik-cache 目錄悯辙。

2.DexClassLoader

/**
 * A class loader that loads classes from {@code .jar} and {@code .apk} files
 * containing a {@code classes.dex} entry. This can be used to execute code not
 * installed as part of an application.
 *
 * <p>This class loader requires an application-private, writable directory to
 * cache optimized classes. Use {@code Context.getDir(String, int)} to create
 * such a directory: <pre>   {@code
 *   File dexOutputDir = context.getDir("dex", 0);
 * }</pre>
 *
 * <p><strong>Do not cache optimized classes on external storage.</strong>
 * External storage does not provide access controls necessary to protect your
 * application from code injection attacks.
 */

上面英文注釋分別說明了三個(gè)方面的知識(shí):
1).DexClassLoader支持加載APK琳省、DEX和JAR,也可以從SD卡進(jìn)行加載躲撰。
上面說dalvik不能直接識(shí)別jar,DexClassLoader卻可以加載jar文件,這難道不矛盾嗎?其實(shí)在BaseDexClassLoader里對(duì)".jar",".zip",".apk",".dex"后綴的文件最后都會(huì)生成一個(gè)對(duì)應(yīng)的dex文件,所以最終處理的還是dex文件针贬。
2).這個(gè)類需要提供一個(gè)optimizedDirectory路徑用于存放優(yōu)化后的dex。
3).optimizedDirectory路徑不允許是外部存儲(chǔ)的路徑拢蛋,為了防止應(yīng)用被注入攻擊桦他。
我們來看下DexClassLoader完整的類:

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

我們看到第二個(gè)參數(shù)傳了不為null的目錄,跟PathDexClassLoader不同谆棱,所以這個(gè)類加載器可以從外部存儲(chǔ)里面加載apk快压,dex或者jar文件,我們目標(biāo)就是它了垃瞧。

3.BaseDexClassLoader
PathClassLoaderDexClassLoader都繼承自BaseDexClassLoader,其中的主要邏輯都是在BaseDexClassLoader完成的蔫劣。

構(gòu)造函數(shù)

可以看到構(gòu)造函數(shù)會(huì)new出一個(gè)DexPathList對(duì)象,我們等會(huì)會(huì)說个从,現(xiàn)在我們先來看看參數(shù)的意思:
1).dexPath:待加載的類的apk脉幢,jar歪沃,dex的路徑,必須是全路徑嫌松。如果要包含多個(gè)路徑,路徑之間必須使用特定的分割符分隔,特定的分割符可以使用File.pathSeparator獲得沪曙。上面"支持加載APK、DEX和JAR萎羔,也可以從SD卡進(jìn)行加載"指的就是這個(gè)路徑液走,最終做的是將dexPath路徑上的文件ODEX優(yōu)化到內(nèi)部位置optimizedDirectory,然后贾陷,再進(jìn)行加載的育灸。
2).libraryPath:目標(biāo)類中所使用的C/C++庫存放的路徑,也就是so文件的路徑昵宇。
3).ClassLoader:是指該裝載器的父裝載器,一般為當(dāng)前執(zhí)行類的裝載器磅崭,例如在Android中以context.getClassLoader()作為父裝載器。因?yàn)轭惣虞d器的雙親委托機(jī)制瓦哎,需要設(shè)置一個(gè)父裝載器砸喻。

二.類加載過程

我們知道,類的加載過程最終都會(huì)通過BaseDexClassLoader中的findClass()開始的:

findClass

可以看到我們這里的Class對(duì)象是通過pathList中的findClass()方法獲取的蒋譬。這里的pathList又是什么呢割岛?這個(gè)類在上面BaseDexClassLoader的構(gòu)造函數(shù)中初始化的。我們可以看下:

 public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }
        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }
        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }
            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }
        this.definingContext = definingContext;
      this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, 121 suppressedExceptions); 
        this.nativeLibraryDirectories = splitPaths(libraryPath, false); 
        this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); 
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); 
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, 140 suppressedExceptions); 
    }

這個(gè)類很重要犯助,我們可以看到首先是dexPath癣漆,optimizedDirectory的非空判斷。然后是dexElements的賦值剂买,這里我們說下dexElements

/**  
* List of dex/resource (class path) elements. 
* Should be called pathElements, but the Facebook app uses reflection 
* to modify 'dexElements' (http://b/7726934).  */  
private final Element[] dexElements;

這個(gè)數(shù)組就是放我們dex的數(shù)組惠爽,我們的不同的dex作為數(shù)組存放。里面注釋很有意思有句話瞬哼,F(xiàn)aceBook使用反射來修改dexElements婚肆,很明確告訴我們也可以通過修改這個(gè)數(shù)組來加載我們的dex。接著我們來看看makePathElements方法坐慰,在看這個(gè)方法之前我們看到里面有個(gè)參數(shù)是調(diào)用splitDexPath方法,這個(gè)方法是用于根據(jù)分隔符取到文件列表的:

 private static Element[] makePathElements(ArrayList<File> files,
           File optimizedDirectory) {
      for (File file : files) {
           File zip = null; 
           File dir = new File(""); 
           DexFile dex = null;
           String path = file.getPath(); 
          String name = file.getName(); 
           if (path.contains(zipSeparator)) { 
                String split[] = path.split(zipSeparator, 2);
                 zip = new File(split[0]); 
                 dir = new File(split[1]);
            } else if (file.isDirectory()) {
              // We support directories for looking up resources and native libraries.
               // Looking up resources in directories is useful for running libcore tests. 
               elements.add(new Element(file, true, null, null)); 
            } else if (file.isFile()) {     
                    if (name.endsWith(DEX_SUFFIX)) { 
                        // Raw dex file (not inside a zip/jar). 
                        try {
                                 dex = loadDexFile(file, optimizedDirectory); 
                         } catch (IOException ex) { 
                                  System.logE("Unable to load dex file: " + file, ex); 
                              } 
                    } else { 
                          zip = file;
                          try { 
                                dex = loadDexFile(file, optimizedDirectory); 
                          } catch (IOException suppressed) { 
                          ......
                          }
               }
           } else {
               System.logW("Unknown file type for: " + file);
           }
           if ((zip != null) || (dex != null)) {
              elements.add(new Element(dir, false, zip, dex)); 
           }
       }
       return elements.toArray(new Element[elements.size()]);
   }

這個(gè)方法就是遍歷之前得到的文件列表较性,然后判斷傳進(jìn)來的文件是目錄,zip文件或者dex文件结胀,如果是目錄的話赞咙,直接將文件file作為參數(shù)傳給Element然后添加進(jìn)elements中。否則其他情況都會(huì)調(diào)用loadDexFile方法進(jìn)行加載糟港,我們看下這個(gè)方法:

private static DexFile loadDexFile(File file, File optimizedDirectory) throws IOException { 
      if (optimizedDirectory == null) {
           return new DexFile(file); 
       } else { 
           String optimizedPath = optimizedPathFor(file, optimizedDirectory); 
           return DexFile.loadDex(file.getPath(), optimizedPath, 0);
       } 
 } 

我們看到這個(gè)方法很簡(jiǎn)單攀操,如果optimizedDirectory == null則直接new 一個(gè)DexFile,否則就使用DexFile#loadDex來創(chuàng)建一個(gè)DexFile實(shí)例着逐。

  private static String optimizedPathFor(File path,
           File optimizedDirectory) {
       /*
        * Get the filename component of the path, and replace the
        * suffix with ".dex" if that's not already the suffix.
        *
        * We don't want to use ".odex", because the build system uses
        * that for files that are paired with resource-only jar
        * files. If the VM can assume that there's no classes.dex in
        * the matching jar, it doesn't need to open the jar to check
        * for updated dependencies, providing a slight performance
        * boost at startup. The use of ".dex" here matches the use on
        * files in /data/dalvik-cache.
        */
       String fileName = path.getName();
       if (!fileName.endsWith(DEX_SUFFIX)) {
           int lastDot = fileName.lastIndexOf(".");
           if (lastDot < 0) {
               fileName += DEX_SUFFIX;
           } else {
               StringBuilder sb = new StringBuilder(lastDot + 4);
               sb.append(fileName, 0, lastDot);
               sb.append(DEX_SUFFIX);
               fileName = sb.toString();
           }
       }
       File result = new File(optimizedDirectory, fileName);
       return result.getPath();
   }

這個(gè)方法獲取被加載的dexpath的文件名崔赌,如果不是“.dex”結(jié)尾的就改成“.dex”結(jié)尾,然后用optimizedDirectory和新的文件名構(gòu)造一個(gè)File并返回該File的路徑耸别,所以DexFile#loadDex方法的第二個(gè)參數(shù)其實(shí)是dexpath文件對(duì)應(yīng)的優(yōu)化文件的輸出路徑健芭。

接著 DexPathList構(gòu)造函數(shù)中會(huì)獲取so文件庫的路徑,然后傳給makePathElements方法秀姐,同樣地慈迈,也是添加到DexElement中,到這里我們已經(jīng)將我們的類和so文件添加進(jìn)DexElement數(shù)組中了省有。所以我們插件化框架只要將我們的插件中的類想辦法添加進(jìn)DexElement數(shù)組中就可以了痒留。

然后我們繼續(xù)分析我們DexPathList#findClass():

public Class findClass(String name) { 
    for (Element element : dexElements) { 
        DexFile dex = element.dexFile;
        if (dex != null) { 
            Class clazz = dex.loadClassBinaryName(name, definingContext); 
          if (clazz != null) { 
              return clazz; 
          } 
        } 
    } 
    return null;
}

我們看到這里會(huì)遍歷我們的dexElements數(shù)組,然后取出數(shù)組中的DexFile對(duì)象蠢沿,調(diào)用他的DexFile#loadClassBinaryName方法:

public Class loadClassBinaryName(String name, ClassLoader loader) { 
    return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);

我們看到最終查找類會(huì)調(diào)用到native的defineClass()方法伸头。這樣,我們的加載流程就算講完了舷蟀。

總結(jié):到這里類的加載已經(jīng)講完了恤磷,這里只是說明了一下流程,希望對(duì)大家會(huì)有點(diǎn)幫助野宜,這也是插件化中很重要的一步扫步。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市匈子,隨后出現(xiàn)的幾起案子河胎,更是在濱河造成了極大的恐慌,老刑警劉巖虎敦,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件游岳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡其徙,警方通過查閱死者的電腦和手機(jī)吭历,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來擂橘,“玉大人晌区,你說我怎么就攤上這事⊥ㄕ辏” “怎么了朗若?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)昌罩。 經(jīng)常有香客問我哭懈,道長(zhǎng),這世上最難降的妖魔是什么茎用? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任遣总,我火速辦了婚禮睬罗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好瘫筐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布麻养。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天算芯,我揣著相機(jī)與錄音,去河邊找鬼凳宙。 笑死熙揍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的氏涩。 我是一名探鬼主播诈嘿,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼削葱!你這毒婦竟也來了奖亚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤析砸,失蹤者是張志新(化名)和其女友劉穎昔字,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體首繁,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡作郭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弦疮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夹攒。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胁塞,靈堂內(nèi)的尸體忽然破棺而出咏尝,到底是詐尸還是另有隱情,我是刑警寧澤啸罢,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布编检,位于F島的核電站,受9級(jí)特大地震影響扰才,放射性物質(zhì)發(fā)生泄漏允懂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一衩匣、第九天 我趴在偏房一處隱蔽的房頂上張望蕾总。 院中可真熱鬧粥航,春花似錦、人聲如沸生百。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽置侍。三九已至映之,卻和暖如春拦焚,著一層夾襖步出監(jiān)牢的瞬間蜡坊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工赎败, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秕衙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓僵刮,卻偏偏與公主長(zhǎng)得像据忘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搞糕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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