當前位置:首頁 » 安卓系統 » androiddex載入

androiddex載入

發布時間: 2023-03-13 17:44:41

① Android-類載入

雙親委託機制

類在進行類載入的時候,把載入任務託管給父類載入器,如能載入成功,則返回,否則依次向子類載入器遞歸嘗試類載入。

意義:

①避免類的重復載入,父類載入已載入該類時,子ClassLoader就沒有必要載入一次了。

②安全性,防止核心API被隨意篡改。

ClassLoader

ClassLoader本身是一個抽象方法。它的主要實現類有BootClassLoader、PathClassLoader、DexClassLoader.

BootClassLoader:用於載入Android Framwork層(SDK)的class文件

PathClassLoader:用於Android應用程序載入器,可以載入指定的dex和jar、zip、apk中的classes.dex(系統使用)

DexClassLoader:用於載入指定的dex和jar、zip、apk中的classes.dex。(供開發者使用)

拓展:

在API26之前。

optimizedDirectory 參數就是dexopt的產出目錄(odex)。那 PathClassLoader 創建時,這個目錄為null,就

意味著不進行dexopt?並不是, optimizedDirectory 為null時的默認路徑為:/data/dalvik-cache。

在API26之後DexClassLoader也取消了optimizedDirectory

熱修復相關

LoadClass:

findClass:PathClassLoader和DexClassLoader的父類BaseDexClassLoader中實現findClass。

BaseDexClassLoader中

PathClassLoader載入過後,pathlist 中存在一個Element數組,Element類中存在一個dexFile成員表示dex文件,即:APK中有X個dex,則Element數組就有X個元素。

總結:

可能看到這里我們比較亂了,理一下。一個類的載入經歷了哪些。我們以PathClassLoader為例。

①載入一個類的時候,首先通過Class緩存尋找是否已經載入過該類。參考抽象類的loadClass方法。

②若在緩存中未找到該類,則交由父載入器載入該類。參考抽象類的loadClass方法。

③調用父載入器PathClassLoader的父類BaseDexClassLoader實現的findClass方法載入該類。

④PathClassLoader在初始化的時候調用父構造方法實例化DexPathList屬性,DexPathList屬性初始化時構造方法內通過makePathElements(或makeDexElements 不同API可能不同)載入APK內的dex文件生成Element數組。

⑤BaseDexClassLoader實現的findClass方法中順序循環已存在的Element數組,通過Element中的DexFile載入類。。

⑥未找到,拋出類未找到異常。

熱修復(multide 形式(thinker、qfix))

熱修復的原理。我們只需在應用啟動的時候,一般是在application方法中(因為class載入首先從緩存中載入),在應用啟動後,經過PathClassLoader載入過後所有的類都在 pathList的Element 數組,把生成的Elment數組插入到PathList的Element數組的最前方。在載入類的時候就只會載入到我們需要更新的類了,因為是順序尋找,找到就返回。(先從我們補丁的dex文件生成的element尋找,找不到再從APK的dex生成的element種尋找)。

熱修復基本思路總結:

①獲取到當前引用的PathClassLoader

②反射獲取其中DexPathList屬性:DexPathList pathList.

③獲取到補丁包path.dex文件的Element[]數組 pElements。參考PathClassLoader怎麼把dex文件轉換為Element數組的。於是我們反射執行DexPathList 中的makePathElements方法(視API而定)傳入dex路徑得到補丁包的element數組。

④獲取pathList的dexElements數組。

⑤把補丁包的pElements數組合並到pathList的dexElements數組的前方,即newElements=pElements+dexElements

⑥反射賦值把newElements替換掉pathList的dexElements

熱修復沒這么簡單,還需考慮混淆,API版本不同導致的使用makePathElements方法或makeDexElements方法等因素。

熱修復(InstantRun 形式(Robust))待了解。

② android插件化(四)Hook載入插件APK(ClassLoader方式)

前面插件化一和二說了下插樁式載入未安裝的APK,主要是重寫了getResource和getClassloader兩個方法來實現的。以及每個組件要實現一個介面,通過介面注入上下文來達到它的生命周期。

那麼插樁式和hook式的實現方式有什麼不同呢?

插樁式是怎麼載入到插件中的class文件呢,是通過將將APK轉化成插件的Classloader,然後想要載入插件的class文件,我們的去拿這個插件的classloader去loadClass。所以是有一個中間者的。

hook式呢是將插件apk融入到了我們的宿主apk,那直接在裡面就可以直接loadClass了,在不用這個插件的ClassLoader了,這樣的話對於插件和宿主就沒什麼區別了,不像插樁式有一個中間者。

那麼要實現hook式 就要知道android中一個class文件式怎樣被載入到內存中去的。其實就是通過PathClassLoader來載入的。

那麼我們先看下ClassLoader

任何一個java程序都是由一個或者多個class組成的,在程序運行時,需要將class文件載入到JVM中才可以使用,負責載入這些class文件的就是java的類載入機制。CLassLoader的作用就是載入class文件提供給程序運行時使用,每個Class對象內部都有一個ClassLoader來標示自己是有那個classLoade載入的。

Android app的所有的java文件都是通過PathClassLoader來載入的,那麼它的父類是BaseDexClassLoader,還有一個兄弟類是DexClassLoader,那麼他們有什麼區別呢。

從上面可以看出這兩個類的構造函數不同。(在26的源碼中DexClassLoader中的optimizedDirectory也廢棄了)

PathClassLoader:用於Android應用程序類載入器。可以載入指定的dex,以及jar、zip、apk中的classes.dex

DexClassLoader:載入指定的dex以及jar、zip、apk中的classes.dex。

可以看到創建ClassLoader的時候需要接收一個CLassLoader parent的參數,這個parent的目的就在於實現類載入的委託。

某個類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,一次遞歸,如果父載入器可以完成載入任務,那麼就返回,只有當父載入器無法完成載入任務時,才自己去載入。

因此我們自己創建的ClassLoader:newPathClassLoader("/sdcard/xx.dex",getClassLoader()),並不僅僅只能載入我們的xx.dex中的class。

需要注意的是,findBootstrapClassOrNull 這個方法,當parent為null的時候,去這個BootCLassLoader進行載入,

但是在Android當中的實現:

所以new PathClassLoader("/sdcard/xx.dex",null),是不能載入Activity.class的。

上面分析了載入了一個class,是利用了雙親委託機制,那麼要是都找不到那就開始調用自己的findCLass方法

在ClassLoader類中findClass:

任何ClassLoader的子類,都可以重寫loadClass和findClass。如果你不想使用雙親委託,就重寫loadClas修改實現,重寫findClass則表示在雙親委託機制下,父ClassLoader都找不到class的情況下,定義自己去查找一個class。

而我們的PathClassLoader會自己負責載入Activity這樣的類,利用雙親委託父類去載入activity,而我們的PathClassLoader沒有重寫findClass,是在它的父類裡面。因此我們可以看看父類的findClass是如何實現的。

可以看到載入PathClassLoader載入class,轉化為從DexPathList中載入class了,那麼我們看看DexPathList中的findClass

那麼從上面分析得到

到這里我們想要載入一個插件的apk ,其實最終載入的是一個dex文件(先說class文件,載入資源後面說),有沒有辦法吧這個dex文件給轉化成一個 Element 對象,給放到 Elemeng數組 當中,這樣直接就可以載入我們插件中的類了。

1、首先我們肯定是要得到插件APK的的中DexPathList對象中的dexElement數組

2、插件的dexElements數組我們拿到了,那麼是不是要開始拿我們系統裡面的 ,我們反射獲取,和上面的一樣。

3、上面我們獲取到了系統和我們插件的dexElement數組,然後我們將這個數組合並到一個新的數組裡面去,並且給注入到系統裡面

至此,載入插件的一個流程基本就完成了。但是上面只是處理了class文件,沒有處理資源。資源的話我們也是採用hook的方式去實現

在宿主的Application中hook這個方法,然後去重寫getAsserts和getResources兩個方法:

然後在插件的BaseActivity中繼續重寫getAssets和getResources兩個方法

這樣就可以完成hook式載入一個未安裝的APK了。至此基本就完成了插樁式和Hook式插件化的基本實現。(後面幾篇是優化)。

③ android dex何時載入

1 問題
在Android系統中,一個App的所有代碼都在一個Dex文件裡面。Dex是一個類似Jar的存
儲了多有Java編譯位元組碼的歸檔文件。因為Android系統使用Dalvik虛擬機,所以需要把
使用Java Compiler編譯之後的class文件轉換成Dalvik能夠執行的class文件。這里需要強
調的是,Dex和Jar一樣是一個歸檔文件,裡面仍然是Java代碼對應的位元組碼文件。
當Android系統啟動一個應用的時候,有一步是對Dex進行優化,這個過程有一個專門的
工具來處理,叫DexOpt。DexOpt的執行過程是在第一次載入Dex文件的時候執行的。這
個過程會生成一個ODEX文件,即Optimised Dex。執行ODex的效率會比直接執行Dex文
件的效率要高很多。但是在早期的Android系統中,DexOpt有一個問題,也就是這篇文
章想要說明並解決的問題。DexOpt會把每一個類的方法id檢索起來,存在一個鏈表結構
裡面。但是這個鏈表的長度是用一個short類型來保存的,導致了方法id的數目不能夠超過
65536個。當一個項目足夠大的時候,顯然這個方法數的上限是不夠的。盡管在新版本的
Android系統中,DexOpt修復了這個問題,但是我們仍然需要對老系統做兼容。
2 思路
一種有效的解決思路是把Dex文件分割成多個較小的Dex。這就如同很多項目會把自己分
割成多個Jar文件一樣,不同的功能在不同的Jar文件裡面,通過一些配置和額外的操作,
可以讓虛擬機有選擇性的載入Jar文件。但是在Android系統中,一個應用是只允許有一
個Dex文件的。也就是說在編譯期的時候,所有的Jar文件最終會被合並成一個Dex文件。
我們沒有辦法在Apk文件裡面打包兩個Dex,讓DexOpt分別對兩個Dex文件做處理,而
Android系統也不會同時為一個Apk載入兩個Dex。
1
2.1 動態載入
如果我們把Dex分成多個文件,然後在程序運行的時候,再把多的那幾個動態的載入進來
是否可行呢?也就是說我們能否在運行時階段把代碼加入虛擬機中。對於虛擬機來說,其
實所有的代碼都是在運行時被載入進來的。而不同於C語言還存在著靜態鏈接。虛擬機在
所有Java代碼執行之前被啟動,然後開始把位元組碼載入到環境中執行,我們可以理解成所
有的代碼都是動態載入到虛擬機里的。
而說到載入,不得不說的是ClassLoader。它的工作就是載入.class文件。在Android的
Dalvik環境中,對應的是DexClassLoader,它們的功能是完全一樣的。ClassLoader的
一大特點就是它是一個樹狀結構。每個ClassLoader都有一個父親ClassLoader。也就是
說,ClassLoader不是把所有的Class放到一個巨大的數組或別的什麼數據結構中來處理。
ClassLoader在載入一個Jar中的類的時候,需要制定另一個ClassLoader作為父親節點,
當我們需要通過ClassLoader得到一個類類型的時候,ClassLoader會把請求優先交給父
親ClassLoader來處理,而父親ClassLoader又會交給它的父親,一直到根ClassLoader。
如果根ClassLoader有這個類,而返回這個類的類類型,否則把這個請求交給這個請求的
來源子ClassLoader。這是一種向上傳遞,向下分發的機制。這種情況下,對於調用著來
說,子ClassLoader永遠都是包含最多Class的ClassLoader。有一點我們需要注意,父親
ClassLoader只會向請求來源分發自己的處理結果。所以如果來源是自己,那麼如果沒有
請求類它就會返回空,而不是遍歷所有子ClassLoader去請求是否有被請求的類。
在Android系統中,對於一個應用來說,其實有兩個ClassLoader,一個是SystemClassLoader,這個ClassLoader裡面除了Java標準的類庫之外,還有一個android.jar,所有
Android Framework層的類都在這里。而另外一個重要的ClassLoader就是基於Android
Context的ClassLoader。所有屬於當前應用的類都是用這個ClassLoader來載入的,我們
可以在Android源碼中看到,所有的Activity,Service,View都是使用這個ClassLoader
來反射並創建的。我們暫時把它叫做ContextClassLoader。
3 載入外部Dex
3.1 構建一個Dex文件
這一步並不復雜,首先我們把所需要的.class文件或者是Jar文件和一些源碼一起編譯生
成一個Jar文件。然後使用Android SDK提供的dx工具把Jar文件轉成Dex文件。我們可以
提前對它進行ODex操作,讓它在被DexClassLoader載入的時候,跳過DexOpt的部分工
作,從而加快載入的過程。
2
3.2 DexClassLoader
現在的工作就是在運行時載入這個Dex文件了。我們可以在Application啟動的onCreate
方法裡面載入Dex,但是如果你的Dex太大,那麼它會讓你的App啟動變慢。我們也可以
使用線程去載入,但我們必須保證載入完成之後再進行某個外部類的請求。當然也可以真
正等到需要某個外部類的時候再進行Dex載入。這根本上取決於Dex文件本身的大小,太
大了可以預載入,而比較小可以等到實際需要的時候再載入。我們暫且把這個載入了外部
Dex的ClassLoader成為ExternalClassLoader
上面我們提到了樹形結構和系統中的多個ClassLoader,當我們載入外部Dex的時候,我
們是否需要指定一個父ClassLoader呢?我們當然需要一個父ClassLoader,否則我們ExternalClassLoader連一些基本的Java類都沒有,它根本不可能成功的載入一個Dex。進一
步的,我們要選擇哪一個ClassLoader來作為我們的父親呢?是SystemClassLoader還是
ExternalClassLoader?這是根據情況來定的,如果外部的Dex文件里沒有任何和Android
相關的代碼,那麼SystemClassLoader是我們的首選,否則我們就應該用ContextClassLoader。如果是後者的情況,我們的樹可以被看成一個鏈表。
3.3 外部的View, Acitivity等
我們知道,我們編寫的四大組建都不是由我們自己來創建的,是由系統來給我們構造並
管理其生命周期的。那麼這個過程是什麼樣的呢?拿Activity來舉例,我們需要通過調用
當前Activity/Context的startActivity,傳入一個Intent來調用啟動一個新的Activity。系
統有一個ActivityManager來處理這里的邏輯。這里的邏輯相當的復雜,但簡單來說,
ActivityManager會收到並處理這個Intent,從而決定是是啟動一個新的,還是把舊的放
到前台。它會先查找這個Activity在哪個應用裡面,這是通過掃描每個應用的AndroidManifest來確定。這些信息是在PackageManager裡面被檢索的。總之如果這個Activity
不再任何的manifest裡面,它就不可能被啟動。所以僅有一個Activity類是不夠的,我們
需要在manifest裡面聲明它。
上面是Activity的情況,Service之類的也是同理。那麼View怎麼辦?盡管我們可以直接創
建View,但是大部分的View都不是我們創建的,而是通過XML布局文件Inflate出來的。
也就是說,我們在XML定義了一些外部Dex裡面的View,那麼顯然這個XML是不能被成
功的Inflate的。因為除非系統會使用我們的ExternalClassLoader,否則它肯定是找不到
我們的類的:ContextClassLoader裡面並沒有外部Dex中的類。
也就是說問題的根本在於,對於那些Android系統為我們創建的對象,它是不能包含在外
部Dex裡面的。而Android系統中大部分的組建類的生命周期都交給了系統來管理。我們
3
不可能自己來創建這些類對象。那麼另一種思路:我們是不是可以通過使用我們的ExternalClassLoader來代替ContextClassLoader呢?盡管系統的ContextClassLoader是私有
的,但是我們可以通過反射強制的把它替換成我們的ExternalClassLoader。而對於那些
外部的組建(Activity等),盡管我們沒有它們的類,但是並不影響我們在AndroidManifest裡面聲明這個Activity。因為Android系統只是把它作為一個檢索,並不會真正檢查它
裡面的組建是不是真的在虛擬機環境中已經被載入了,只有真正使用Intent啟動某個組建
的時候才會去檢查。而只要我們保證這個時候我們已經載入了外部的ClassLoader,那麼
這個組建就可以被正常的啟動。
還有一點,除了我們要為外部可能有的組建在AndroidManifest裡面做聲明一外,那些外
部組建可能用到的許可權我們也需要一一聲明,例如如果外部Activity使用了相機功能,那
么如果我們的Manifest裡面沒有聲明使用相機功能的許可權的話,即便這個Activity能成功
為載入出來,仍然是不能使用的。
4 核心代碼段
載入外部Dex
mClassLoader = new DexClassLoader ( f . getAbsolutePath ( ) ,
mContext . getCacheDir ( ) . getAbsolutePath ( ) ,
null , mContext . getClassLoader ( ) ) ;
讓系統使用ExternalClassLoader
t r y {
F ie ld mMainThread = ge t F ie ld ( A c t i v i t y . class , 」mMainThread」) ;
Object mainThread = mMainThread . get ( a c t i v i t y ) ;
Class t hreadClass = mainThread . get Class ( ) ;
F ie ld mPackages = ge t F ie ld ( threadClass , 」mPackages」) ;
WeakReference<?> r e f ;
Map< St ring , ?> map =(Map< St ring , ? >) mPackages . get (mainThread ) ;
r e f = (WeakReference<? >) map . get (mContext . getPackageName ( ) ) ;
Object apk = r e f . get ( ) ;
Class apkClass = apk . get Class ( ) ;
F ie ld mClassLoader = ge t F ie ld ( apkClass , 」mClassLoader」) ;
mClassLoader . set (apk , classLoader ) ;
} catch ( I llegalArgument Except ion e) {
i f (DEBUG) {
e . print St ackTrace ( ) ;
}
} catch ( I llega lAc c essEx c ept ion e) {
i f (DEBUG) {
4
e . print St ackTrace ( ) ;
}
}
5

④ Android動態載入dex技術初步了解

此處需要注意DexClassLoader的四個參數:
參數1 dexPath:待載入的dex文件路徑,如果是外存路徑,一定要加上讀外存文件的許可權(<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> ),否則會報與上面一樣的錯誤,這點參考文章2中說這個許可權可有可無是錯誤的。(更正下:Android4.4 KitKat及以後的版本需要此許可權,之前的版本不需要許可權)

⑤ 【Android】Android中的類載入

前文: 【Java】ClassLoader與雙親委派機制

Android中的類載入器有三種, DexClassLoader 、 PathClassLoader 、 BootClassLoader 。
其中 BootClassLoader 是系統啟動時預載入常用類的,一般使用不到。 DexClassLoader 、 PathClassLoader 都是繼承自 BaseDexClassLoader 。
但 DexClassLoader 和 PathClassLoader 並沒有重寫 BaseDexClassLoader 中的任何方法,所以源碼只需要看 BaseDexClassLoader 即可。

由於Android SDK並沒有包含 BaseDexClassLoader ,所以需要到源碼查詢網站查詢源碼,如下:

復制這個java文件到對應源碼文件夾下就可以在Android Studio中查看了。

通過調試可以看到,Android中普通類的載入器其實是 PathClassLoader 。追蹤 PathClassLoader.findClass 方法,即可獲取Android的類載入過程:

PathClassLoader.findClass -- 繼承自 --> BaseDexClassLoader.findClass()
-> BaseDexClassLoader.pathList.findClass()
-> DexPathList.dexElements.foreach { element.findClass() }
-> Element.findClass()
-> Element.dexFile.loadClassBinaryName()
-> DexFile.defineClass()

即類載入過程通過 BaseDexClassLoader.findClass 、 DexPathList.findClass 、 Element.findClass 、 DexFile.loadClassBinaryName ,最終會落到 DexFile.defineClass 方法中,然後就交給native層了。

其中需要注意的是,在 BaseDexClassLoader.findClass 的開頭有這么一段:

這段是在Android 10新加入的,據稱是為了實現 shared library 功能的,在之前的版本中沒有這一段。

在上一節中知道了,類載入的流程如下:
BaseDexClassLoader.findClass() ->
BaseDexClassLoader.pathList.findClass() ->
DexPathList.dexElements.foreach { element.findClass() } ->
Element.findClass() -> ...

看 DexPathList.findClass 方法:

可以發現, DexPathList 載入類的方法是遍歷 dexElements 數組依次載入,知道獲取到值為止。所以可以通過修改這個數組,把新的dex文件放在數組的前面,使其載入修改後的類,從而實現熱修復。

根據以上原理,寫下這個工具類,有效性待驗證:

⑥ Android類載入機制

Android手寫熱修復(一)--ClassLoader

我們平時編寫的 .java 文件不是可執行文件,需要先編譯成 .class 文件才可以被虛擬機執行。所謂類載入是指通過 類載入器 把class文件載入到虛擬機的內存空間,具體來說是方法區。類通常是按需載入,即第一次使用該類時才載入。

首先,Java與Android都是把類載入到虛擬機內存中,然後由虛擬機轉換成設備識別的機器碼。但是由於二者使用的虛擬機不同,所以在類載入方面也是有所區別的。Java的虛擬機是JVM,Android的虛擬機是dalvik/art(5.0以後虛擬機是art,是對dalvik的一種升級)。 Java虛擬機運行的是class文件,而Android 虛擬機運行的是dex文件。 dex其實是class文件的集合,是對class文件優化的產物,是為了避免出現重復的class。

從上面的講解中,我們已經知道我們平時寫的類是被 類載入器 載入盡虛擬機內存才能運行。下面就通過Framework源碼來為大家講解Android中最主要的5個類載入器。

在Activity做個簡單驗證:

結果:

可以看出系統類由BootClassLoader載入,apk中的類由PathClassLoader載入,PathClassLoader的父類載入器是BootClassLoader。如果暫時不能理解父類載入器是什麼,沒關系,後面講雙親委託機制的時候會理解的。

下面的源碼解析基於 Android SDK API28 ,這幾個類載入器(除了ClassLoader)沒辦法直接在AS上查看源碼,AS搜索到的是反編譯的class的內容,是不可信的,為大家推薦一個在線工具查看, 在線查看Android Framework源碼 。

用來載入本地文件系統上的文件或目錄,通常是用來載入apk中我們自己寫的類,而像 Activity.class 這種系統的類不是由它載入。注意:這里,並不像很多網上文章說的那樣只能載入apk,本地的其他目錄的文件也是可以的,這一點我會在後面驗證說明。

也是被用來載入 jar 、apk、dex,通常用來載入未安裝到應用中的文件。注意,它需要一個應用私有的可寫的目錄來存放優化後的dex文件。千萬不要選擇外部存儲路徑,因為這樣可能會導致你的應用遭到注入攻擊。

關於dex文件優化,可能很多人還是不理解,水平有限,我簡單解釋一下,

構造器參數解釋:

關於optimizedDirectory:
1、這是dex優化後的路徑,它必須是一個應用私有的可寫的目錄否則會存在注入攻擊的風險;
2、這個參數在API 26(8.0)之前是有值的,之後的話,這個參數已經沒有影響了,因為在調用父構造器的時候這個參數始終為null,也就是說Android 8.0 以後DexClassLoader和PathClassLoader基本一樣的來;
3、在載入app的時候,apk內部的dex已經執行過優化了,優化之後放在系統目錄/data/dalvik-cache下。

這個構造器的關鍵是初始化了一個DexPathList對象,這個是後面載入class的關鍵類。

這個構造方法等關鍵是通過 makeDexElements() 方法來獲取Element數組,這個Element數組非常關鍵,後面查找class就會用到它,也是熱修復的關鍵點之一。

splitDexPath(dexPath) 方法是把dexPath目錄下的所有文件轉換成一個File集合,如果是多個文件的話,會用 : 作為分隔符。

makeDexElements()

小結一下,這個方法就是把指定目錄下的文件apk/jar/zip/dex按不同的方式封裝成Element對象,然後按順序添加到Element[]數組中。

DexPathList#loadDexFile()

可以看到 DexFile 最終是調用了openDexFile、native方法openDexFileNative去打開Dex文件的,如果outputName為空,則自動生成一個緩存目錄,具體來說是 /data/dalvik-cache/[email protected] 。openDexFileNative這個native方法就不具體分析了,主要是對dex文件進行了優化操作,將優化後得odex文件通過mmap映射到內存中。感興趣的同學可以參考:
《DexClassLoader和PathClassLoader載入Dex流程》

現在在回頭看看DexClassLoader與PathClassLoader的區別。DexClassLoader可以指定odex的路徑,而PathClassLoader則採用系統默認的緩存路徑,在8.0以後沒有區別。

ClassLoader是一個抽象類,有3個構造方法,最終調用的還是第一個構造方法,主要功能是保存實現類傳入的parent參數,也就是父類載入器。ClassLoader的實現類主要有2個,一個是前面講過的BaseDexClassLoader,另一個是BootClassLoader。

BootClassLoader是ClassLoader的內部類,而且繼承了ClassLoader。

這是載入一個類的入口,流程如下:
1、 先檢查這個類是否已經被載入,有的話直接返回Class對象;
2、如果沒有載入過,通過父類載入器去載入,可以看出parent是通過遞歸的方式去載入class的;
3、如果所有的父類載入器都沒有載入過,就由當前的類載入器去載入。

通常我們自己寫的類是通過當前類載入器調用 findClass 方法去載入的,但是在 ClassLoader 中這是個空方法,具體的實現在它的子類 BaseDexClassLoader 中。

BaseDexClassLoader # findClass

可以看到是通過pathList去查找class的,這個對象其實之前講過,它是在BaseDexClassLoader 的構造方法中初始化的,它實際上是一個 DexPathList 對象。

DexPathList # findClass()

對Element數組遍歷,再通過Element對象的 findClass 方法去查找class,有的話就直接返回這個class,找不到則返回null。 這里可以看出獲取Class是通過DexFile來實現的,而各種類載入器操作的是Dex。Android虛擬機載入的dex文件,而不是class文件。

1、載入一個類是通過雙親委託機制來實現的。
2、如果是第一次載入class,那是通過 BaseDexClassLoader 中的findClass方法實現的;接著進入 DexPathList 中的findClass方法,內部通過遍歷Element數組,從Element對象中去查找類;Element實際上是對Dex文件的包裝,最終還是從dexfile去查找的class。
3、一般app運行主要用到2個類載入器,一個是PathClassLoader:主要用於載入自己寫的類;另一個是BootClassLoader:用於載入Framework中的類;
4、熱修復和插件化一般是利用DexClassLoader來實現。
5、PathClassLoader和DexClassLoader其實都可以載入apk/jar/dex,區別是 DexClassLoader 可以指定 optimizedDirectory ,也就是 dex2oat 的產物 .odex 存放的位置,而 PathClassLoader 只能使用系統默認位置。但是在8.0 以後二者是沒有區別的,只能使用系統默認的位置了。

這張圖來源於:
Android虛擬機框架:類載入機制

在類載入流程分析中,我們已經知道,查找class是通過DexPathList來完成的,實際上DexPathList最終還是遍歷其Element數組,獲取DexFile對象來載入Class文件。 由於數組是有序的,如果2個dex文件中存在相同類名的class,那麼類載入器就只會載入數組前面的dex中的class。如果apk中出現了有bug的class,那隻要把修復的class打包成dex文件並且放在 DexPathList 中Element數組`的前面,就可以實現bug修復了 。下一篇為大家帶來的手寫熱修復。

Android類載入機制的細枝末節
從JVM到Dalivk再到ART(class,dex,odex,vdex,ELF)
類載入機制系列2——深入理解Android中的類載入器
Android 熱修復核心原理,ClassLoader類載入

熱點內容
app什麼情況下找不到伺服器 發布:2025-05-12 15:46:25 瀏覽:714
php跳過if 發布:2025-05-12 15:34:29 瀏覽:467
不定時演算法 發布:2025-05-12 15:30:16 瀏覽:131
c語言延時1ms程序 發布:2025-05-12 15:01:30 瀏覽:166
動物園靈長類動物配置什麼植物 發布:2025-05-12 14:49:59 瀏覽:736
wifi密碼設置什麼好 發布:2025-05-12 14:49:17 瀏覽:148
三位數乘兩位數速演算法 發布:2025-05-12 13:05:48 瀏覽:399
暴風影音緩存在哪裡 發布:2025-05-12 12:42:03 瀏覽:544
access資料庫exe 發布:2025-05-12 12:39:04 瀏覽:632
五開的配置是什麼 發布:2025-05-12 12:36:37 瀏覽:365