Android ClassLoader源碼解析

提起熱修復以及插件化,相信大家肯定不陌生惜索,而無論是熱修復還是插件化派昧,其理論依據(jù)就是Android 類加載機制葱绒。今天我們從源碼的角度一起學習下。

簡單來講斗锭,Android中的ClassLoader主要分為BootClassLoader、PathClassLoader和DexClassLoader這三種類型失球。BootClassLoader:主要負責加載Android FrameWork層中的字節(jié)碼文件岖是; PathClassLoader:負責加載已經(jīng)安裝到系統(tǒng)APK文件中的字節(jié)碼文件;DexClassLoader:負責加載指定目錄中的字節(jié)碼文件实苞;我們先來看下其源碼實現(xiàn):

#BootClassLoader 
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;
    }

    public BootClassLoader() {
        super(null);
    }
    ...
}

由上述代碼可以看出豺撑,BootClassLoader 繼承自ClassLoader抽象類,實現(xiàn)方式為單例模式黔牵,需要注意的是BootClassLoader的訪問修飾符是默認的,只有在同一個包中才可以訪問,所以我們在應用程序中是無法直接調(diào)用到的西土。

#PathClassLoader 
public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
    }

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

PathClassLoader繼承自BaseDexClassLoader ,由上述代碼灯抛,很顯然,PathClassLoader中的方法實現(xiàn)都在其父類BaseDexClassLoader 中音瓷,在這里我們分析下PathClassLoader構(gòu)造方法中各個參數(shù)的含義:

dexPath:dex文件以及包含dex的apk文件或jar文件的路徑集合对嚼,多個路徑用文件分隔符分隔,默認文件分隔符為‘:’绳慎。
librarySearchPath:所使用到的C/C++庫存放的路徑
parent:該ClassLoader所對應的父ClassLoader

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

同樣纵竖,DexClassLoader 也是繼承自BaseDexClassLoader ,相比較PathClassLoader而言杏愤,DexClassLoader的構(gòu)造方法中多了一個參數(shù)optimizedDirectory靡砌,我們看下這個參數(shù)的含義:

optimizedDirectory:Android系統(tǒng)將dex文件進行優(yōu)化后所生成的ODEX文件的存放路徑,該路徑必須是一個內(nèi)部存儲路徑珊楼。PathClassLoader中使用默認路徑“/data/dalvik-cache”通殃,而DexClassLoader則需要我們指定ODEX優(yōu)化文件的存放路徑。

和Java中的ClassLoader類似亥曹,Android中的ClassLoader同樣遵循雙親委托機制邓了。上述三種ClassLoader中,PathClassLoader的parent為BootClassLoader媳瞪,DexClassLoader的parent同樣為BootClassLoader骗炉,下面我們來驗證下:

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ClassLoader classLoader = getClassLoader();
        if (classLoader != null){
            Log.e("MainActivity", classLoader.toString());
            while (classLoader.getParent() != null){
                classLoader = classLoader.getParent();
                Log.e("MainActivity", classLoader.toString());
            }
        }
        
        Log.e("MainActivity", "------------------");
        
        TextView mText = findViewById(R.id.tv_text);
        Log.e("MainActivity-TextView", mText.getClass().getClassLoader().toString());
    }
}

輸出日志為:

11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.administrator.mdtest-2/base.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_dependencies_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_0_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_1_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_2_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_3_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_4_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_5_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_6_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_7_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_8_apk.apk", zip file "/data/app/com.example.administrator.mdtest-2/split_lib_slice_9_apk.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: java.lang.BootClassLoader@245c18f6
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity: ------------------
11-24 22:34:44.205 20274-20274/com.example.administrator.mdtest E/MainActivity-TextView: java.lang.BootClassLoader@245c18f6

由上述輸出日志,我們不僅可以驗證蛇受,PathClassLoader的parent為BootClassLoader句葵,同時還驗證了我們文章開始所說的應用程序的ClassLoader為PathClassLoader,F(xiàn)rameWork層的ClassLoader為BootClassLoader兢仰。

照例我們打開源碼乍丈,看下BootClassLoader是在哪里作為parent參與構(gòu)建PathClassLoader對象的:

#ClassLoader
private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");

        // TODO Make this a java.net.URLClassLoader once we have those?
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
    }

沒錯,正是在ClassLoader類中的createSystemClassLoader方法中把将。

好了轻专,我們既然知道Android的ClassLoader遵循雙親委托機制,那么肯定要看下ClassLoader類中的loadClass方法了:

#ClassLoader
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

關(guān)于雙親委托機制察蹲,相信大家都了解请垛,在這里我就不詳細介紹了,需要注意的是洽议,在Java中宗收,類加載是通過defineClass方法,而在Android中亚兄,類加載則是通過findClass方法混稽,我們跟進去findClass方法看下類加載的過程(由于BaseDexClassLoader對findClass方法進行了重寫,所以我們需要跟進到BaseDexClassLoader類中,而Android Studio中無法查看到BaseDexClassLoader的具體源碼匈勋,所以筆者在這里通過源碼在線查看網(wǎng)站:https://www.androidos.net.cn/sourcecode):

    #BaseDexClassLoader
    @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;
    }

可以看到礼旅,在BaseDexClassLoader的findClass方法中直接調(diào)用到pathList的findClass方法進行類加載操作,pathList是個什么東東呢颓影?我們看下它的定義:

    private final DexPathList pathList;

    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());
        }
    }

由上述代碼可以看到各淀,pathList被定義為final類型,其對象是在BaseDexClassLoader的構(gòu)造方法中創(chuàng)建的诡挂,也就是說在PathClassLoader對象創(chuàng)建的時候就創(chuàng)建了DexPathList對象碎浇,并將相應參數(shù)傳入。我們跟進去看下DexPathList的構(gòu)造方法:

 //定義所要加載文件后綴
 private static final String DEX_SUFFIX = ".dex";
 //構(gòu)造方法中傳入的ClassLoader
 private final ClassLoader definingContext;
 //Element為 DexPathList 中的內(nèi)部類璃俗,其主要的成員變量為 dexFile
 //DexFile:dex文件在安卓虛擬機中的具體實現(xiàn)
 private Element[] dexElements;

 public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {

        異常判斷操作...
        //接收classloader對象
        this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader  
        //重點奴璃,通過 makeDexElements 方法初始化 dexElements數(shù)組
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);

        ...
    }

我們跟進去看下makeDexElements方法的實現(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;
      /*
       * 遍歷所有的dex文件
       */
      for (File file : files) {
          if (file.isDirectory()) {    //判斷file是否為文件夾
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {          //判斷file是否為文件
              //獲取文件名稱
              String name = file.getName();
              //判斷文件名稱是否以“.dex”結(jié)尾
              if (name.endsWith(DEX_SUFFIX)) {
                  // Raw dex file (not inside a zip/jar).
                  try {
                      //將dex文件轉(zhuǎn)換為DexFile對象
                      DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          //創(chuàng)建Element對象,將DexFile對象作為參數(shù)傳入城豁,
                          //并將該Element對象添加到elements數(shù)組中
                          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;
    }

由上述代碼苟穆,我們可以知道m(xù)akeDexElements方法的主要作用為:遍歷指定路徑下的所有文件,將其中的.dex文件轉(zhuǎn)換成DexFile對象唱星,最終存儲到elements數(shù)組中雳旅。

由上述分析,我們知道類加載操作最終是由pathList的findClass方法來實現(xiàn)的间聊,我們繼續(xù)跟進去pathList的findClass方法看下:

 #DexPathList
 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;
    }

可以看到攒盈, DexPathList 的 findClass方法中簡單粗暴,對dexElements數(shù)組進行遍歷哎榴,調(diào)用element的findClass方法來尋找當前需要的class字節(jié)碼型豁,簡單來講就是Android在進行類加載的時候,會遍歷我們的每一個dex文件尚蝌,來尋找所需的Class迎变。
我們接著跟進去element的findClass方法去看下:

#Element
public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
        }

可以看到,最終是調(diào)用到 DexFile 的loadClassBinaryName方法飘言,我們接著跟:

 #DexFile
 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;
    }

  ...

  private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
                                                  DexFile dexFile)

可以看到最終是通過DexFile類中的defineClassNative方法來完成所需Class的查找衣形。

好了,Android ClassLoader源碼解析到這里就結(jié)束了姿鸿,歡迎大家一起交流谆吴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市般妙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌相速,老刑警劉巖碟渺,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡苫拍,警方通過查閱死者的電腦和手機芜繁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绒极,“玉大人骏令,你說我怎么就攤上這事÷⑻幔” “怎么了榔袋?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铡俐。 經(jīng)常有香客問我凰兑,道長,這世上最難降的妖魔是什么审丘? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任吏够,我火速辦了婚禮,結(jié)果婚禮上滩报,老公的妹妹穿的比我還像新娘锅知。我一直安慰自己,他們只是感情好脓钾,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布售睹。 她就那樣靜靜地躺著,像睡著了一般惭笑。 火紅的嫁衣襯著肌膚如雪侣姆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天沉噩,我揣著相機與錄音捺宗,去河邊找鬼。 笑死川蒙,一個胖子當著我的面吹牛蚜厉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畜眨,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼昼牛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了康聂?” 一聲冷哼從身側(cè)響起贰健,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恬汁,沒想到半個月后伶椿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年脊另,在試婚紗的時候發(fā)現(xiàn)自己被綠了导狡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡偎痛,死狀恐怖旱捧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情踩麦,我是刑警寧澤枚赡,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站靖榕,受9級特大地震影響标锄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茁计,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一料皇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧星压,春花似錦践剂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竣贪,卻和暖如春军洼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背演怎。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工匕争, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爷耀。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓甘桑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親歹叮。 傳聞我的和親對象是個殘疾皇子跑杭,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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