插件化知識(shí)梳理(8) - 類的動(dòng)態(tài)加載源碼分析


相關(guān)閱讀

插件化知識(shí)梳理(1) - Small 框架之如何引入應(yīng)用插件
插件化知識(shí)梳理(2) - Small 框架之如何引入公共庫插件
插件化知識(shí)梳理(3) - Small 框架之宿主分身
插件化知識(shí)梳理(4) - Small 框架之如何實(shí)現(xiàn)插件更新
插件化知識(shí)梳理(5) - Small 框架之如何不將插件打包到宿主中
插件化知識(shí)梳理(6) - Small 源碼分析之 Hook 原理
插件化知識(shí)梳理(7) - 類的動(dòng)態(tài)加載入門
插件化知識(shí)梳理(8) - 類的動(dòng)態(tài)加載源碼分析
插件化知識(shí)梳理(9) - 資源的動(dòng)態(tài)加載示例及源碼分析
插件化知識(shí)梳理(10) - Service 插件化實(shí)現(xiàn)及原理


一、前言

插件化知識(shí)梳理(7) - 類的動(dòng)態(tài)加載入門 中,我們通過一個(gè)例子來演示了如何通過PathClassLoaderDexClassLoader實(shí)現(xiàn)類的動(dòng)態(tài)加載达布。今天這篇文章,我們一起來對(duì)這個(gè)類加載內(nèi)部的實(shí)現(xiàn)源碼進(jìn)行一次簡單的走讀铐维。源碼的地址為 地址 版姑,友情提示谴供,需要翻墻能扒。

二移层、源碼解析

整個(gè)加載過程設(shè)計(jì)到的類,如下圖所示:


2.1 BaseDexClassLoader

從上面的圖中赫粥,我們可以看到DexClassLoaderPathClassLoader,都是BaseDexClassLoader的子類予借,而當(dāng)我們調(diào)用以上兩個(gè)類的構(gòu)造方法時(shí)越平,其實(shí)都是調(diào)用了super()方法,也就是BaseDexClassLoader的構(gòu)造方法灵迫,它支持傳入一下四個(gè)參數(shù):

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
        if (reporter != null) {
            reporter.report(this.pathList.getDexPaths());
        }
    }
  • dexPath:包含了類和資源的jar/apk文件秦叛,也就是上一篇例子當(dāng)中的plugin_dex.jar,如果有多個(gè)文件瀑粥,那么用File.pathSeparator進(jìn)行分割挣跋,如果我們傳入的是jar/apk文件,那么它會(huì)先將里面的.dex文件解壓到內(nèi)存當(dāng)中狞换,而如果是.dex文件避咆,那么將不會(huì)有這一過程舟肉。
  • optimizedDirectory:這個(gè)參數(shù)目前已經(jīng)被廢棄了,沒有什么作用查库。
  • librarySearchPathNative庫的路徑路媚,同樣可以用File.pathSeparator進(jìn)行分割。
  • parent:父加載器的實(shí)例樊销。

DexClassLoaderPathClassLoader的區(qū)別就在于后者不支持傳入optimizedDirectory這個(gè)參數(shù)整慎,現(xiàn)在看來,對(duì)于最新的源碼围苫,這個(gè)參數(shù)已經(jīng)被廢棄了裤园,那么這兩個(gè)類其實(shí)是一樣的。但是具體的實(shí)現(xiàn)剂府,還是要看手機(jī)的安卓版本拧揽。

2.2 DexPathList

在前面的例子當(dāng)中,獲得PathClassLoader/DexClassLoader實(shí)例之后周循,調(diào)用了loadClass方法强法,它其實(shí)調(diào)用的是基類ClassLoader中的方法:

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
            //先查看該類是否已經(jīng)被加載過
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //優(yōu)先調(diào)用父加載器進(jìn)行加載.
                        c = parent.loadClass(name, false);
                    } else {
                        //2.如果沒有父加載器,那么使用 bootstrap 進(jìn)行加載湾笛。
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                     
                }
                if (c == null) {
                    //調(diào)用 findClass 方法饮怯。
                    long t1 = System.nanoTime();
                    c = findClass(name);
                }
            }
            return c;
    }

對(duì)于BaseDexClassLoader,最終會(huì)走到他們重寫的findClass方法嚎研,而該方法又會(huì)去通過pathList去尋找蓖墅,如果找不到,那么就會(huì)拋出異常临扮,

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

并且其它的公有方法论矾,都是通過pathList去尋找的,因此這個(gè)pathList是如何構(gòu)成的就是我們分析源碼的關(guān)鍵杆勇。

    public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();

        //dex/resource (class path) elements
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
        //application native library directories
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);

        //system native library directories
        this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        //native library path elements
        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }

在上面的構(gòu)造方法中贪壳,最關(guān)鍵的就是通過makeDexElementsmakePathElements來構(gòu)建dexElementsnativeLibraryPathElements,它們兩個(gè)分別為ElementNativeLibraryElement類型的數(shù)組蚜退,在 插件化知識(shí)梳理(7) - 類的動(dòng)態(tài)加載入門 中闰靴,這兩個(gè)變量的值為:

2.3 makeDexElements

下面,我們來看一下makeDexElements的內(nèi)部實(shí)現(xiàn)邏輯:

    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      /*
       * Open all files and load the (direct or contained) dex files up front.
       */
      for (File file : files) {
          if (file.isDirectory()) {
              // We support directories for looking up resources. Looking up resources in
              // directories is useful for running libcore tests.
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();
              if (name.endsWith(DEX_SUFFIX)) {
                  // Raw dex file (not inside a zip/jar).
                  try {
                      DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else {
                  DexFile dex = null;
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  } catch (IOException suppressed) {
                      /*
                       * IOException might get thrown "legitimately" by the DexFile constructor if
                       * the zip file turns out to be resource-only (that is, no classes.dex file
                       * in it).
                       * Let dex == null and hang on to the exception to add to the tea-leaves for
                       * when findClass returns null.
                       */
                      suppressedExceptions.add(suppressed);
                  }
                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      elements[elementsPos++] = new Element(dex, file);
                  }
              }
          } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

這里面钻注,最終會(huì)創(chuàng)建一個(gè)和files相等大小的elements數(shù)組蚂且,其最終目的是為每個(gè)Element中的dexFile賦值,而dexFile則是通過loadDexFile方法創(chuàng)建的幅恋。

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

這里面杏死,會(huì)根據(jù)optimizedDirectory的區(qū)別,來調(diào)用DexFile不同的函數(shù),我們先看靜態(tài)方法淑翼,可以看到腐巢,它里面也是調(diào)用了new DexFile來返回一個(gè)DexFile對(duì)象:

    static DexFile loadDex(String sourcePathName, String outputPathName,
        int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        /*
         * TODO: we may want to cache previously-opened DexFile objects.
         * The cache would be synchronized with close().  This would help
         * us avoid mapping the same DEX more than once when an app
         * decided to open it multiple times.  In practice this may not
         * be a real issue.
         */
        return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
    }

這里面,又會(huì)調(diào)用openDex方法窒舟,得到一個(gè)mCookie變量系忙,在前面的例子中,這個(gè)mCookie是一個(gè)long型的對(duì)象惠豺,對(duì)于里面的內(nèi)部實(shí)現(xiàn)银还,可以參見這篇文章 跟蹤源碼分析 Android DexClassLoader 加載機(jī)制


以上就是整個(gè)構(gòu)建pathList的過程,下面洁墙,我們來看一下前面所說的DexFileListfindClass的過程蛹疯。

2.4 尋找 Class 的過程

DexFileList中尋找類的代碼如下:

    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

它會(huì)遍歷先前構(gòu)建的Element數(shù)組,調(diào)用每個(gè)的findClass方法热监,直到找到為止捺弦,而Element中的該方法,則會(huì)調(diào)用在2.3中創(chuàng)建的DexFileloadClassBinaryName來查找該Class對(duì)象:

    public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }

    private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                     DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }

三孝扛、小結(jié)

經(jīng)過一次簡單的源碼走讀列吼,我們可以知道,DexClassLoader/PathClassLoader的內(nèi)部苦始,為每一個(gè)傳入的jar/apk/dex文件寞钥,都創(chuàng)建了一個(gè)Element變量,它們被保存在DexFileList當(dāng)中陌选,而每一個(gè)Element變量中理郑,又包含了一個(gè)關(guān)鍵的DexFile類,之后我們通過DexClassLoader/PathClassLoader尋找類或者資源時(shí)咨油,其實(shí)最終都是調(diào)用了DexFile中的Native方法您炉,如果有興趣的同學(xué)可以去研究這些方法的內(nèi)部實(shí)現(xiàn)。

最后役电,簡單地提一下赚爵,在Small的源碼當(dāng)中,并沒有直接使用DexClassLoader/PathClassLoader法瑟,它首先是直接調(diào)用了DexFile的靜態(tài)方法來為每一個(gè)插件創(chuàng)建一個(gè)DexFile


之后囱晴,再通過反射,將這些DexFile加入到宿主的ClassLoader當(dāng)中瓢谢,而不是像我們之前那樣,為每一個(gè)插件都創(chuàng)建一個(gè)ClassLoader驮瞧。

該方法中氓扛,會(huì)像DexFileList中所做的那樣,通過makeDexElement方法,為每一個(gè)DexFile創(chuàng)建一個(gè)Element對(duì)象:

最后采郎,再將這個(gè)對(duì)象加入到pathList變量中:


更多文章千所,歡迎訪問我的 Android 知識(shí)梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蒜埋,隨后出現(xiàn)的幾起案子淫痰,更是在濱河造成了極大的恐慌,老刑警劉巖整份,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件待错,死亡現(xiàn)場離奇詭異,居然都是意外死亡烈评,警方通過查閱死者的電腦和手機(jī)火俄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讲冠,“玉大人瓜客,你說我怎么就攤上這事「涂” “怎么了谱仪?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長否彩。 經(jīng)常有香客問我疯攒,道長,這世上最難降的妖魔是什么胳搞? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任卸例,我火速辦了婚禮,結(jié)果婚禮上肌毅,老公的妹妹穿的比我還像新娘筷转。我一直安慰自己,他們只是感情好悬而,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布呜舒。 她就那樣靜靜地躺著,像睡著了一般笨奠。 火紅的嫁衣襯著肌膚如雪袭蝗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天般婆,我揣著相機(jī)與錄音到腥,去河邊找鬼。 笑死蔚袍,一個(gè)胖子當(dāng)著我的面吹牛乡范,可吹牛的內(nèi)容都是我干的配名。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晋辆,長吁一口氣:“原來是場噩夢啊……” “哼渠脉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瓶佳,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤芋膘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后霸饲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體为朋,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年贴彼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了潜腻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡器仗,死狀恐怖融涣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情精钮,我是刑警寧澤威鹿,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站轨香,受9級(jí)特大地震影響忽你,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜臂容,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一科雳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧脓杉,春花似錦糟秘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蕉堰,卻和暖如春凌净,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屋讶。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工冰寻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人皿渗。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓性雄,卻偏偏與公主長得像没卸,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秒旋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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