ClassLoader類加載與熱修復(fù)

ClassLoader類加載與熱修復(fù)

ART 和 Dalvik

DVM也是實(shí)現(xiàn)了JVM規(guī)范的一個(gè)虛擬器,默認(rèn)使用CMS垃圾回收器,但是與JVM運(yùn)行 Class 字節(jié)碼不同攻柠,DVM 執(zhí)行 Dex(Dalvik Executable Format) ——專為 Dalvik 設(shè)計(jì)的一種壓縮格式赃磨。Dex 文件是很多 .class 文件處理壓 縮后的產(chǎn)物立由,最終可以在 Android 運(yùn)行時(shí)環(huán)境執(zhí)行。

ART(Android Runtime) 是在 Android 4.4 中引入的一個(gè)開發(fā)者選項(xiàng)序厉,也是 Android 5.0 及更高版本的默認(rèn) Android 運(yùn)行時(shí)锐膜。ART 和 Dalvik 都是運(yùn)行 Dex 字節(jié)碼的兼容運(yùn)行時(shí),因此針對 Dalvik 開發(fā)的應(yīng)用也能在 ART 環(huán) 境中運(yùn)作弛房。

dexopt與dexaot

  • dexopt

在Dalvik中虛擬機(jī)在加載一個(gè)dex文件時(shí)道盏,對 dex 文件 進(jìn)行 驗(yàn)證 和 優(yōu)化的操作,其對 dex 文件的優(yōu)化結(jié)果 變成了 odex(Optimized dex) 文件文捶,這個(gè)文件和 dex 文件很像荷逞,只是使用了一些優(yōu)化操作碼。

  • dex2oat

ART 預(yù)先編譯機(jī)制粹排,在安裝時(shí)對 dex 文件執(zhí)行AOT 提前編譯操作种远,編譯為OAT(實(shí)際上是ELF文件)可執(zhí)行 文件(機(jī)器碼)。

ART 預(yù)先編譯機(jī)制

ClassLoader介紹

任何一個(gè) Java 程序都是由一個(gè)或多個(gè) class 文件組成顽耳,在程序運(yùn)行時(shí)坠敷,需要將class文件加載到 JVM 中才可以使用,負(fù)責(zé)加載這些 class 文件的就是 Java 的類加載機(jī)制射富。ClassLoader 的作用簡單來說就是加載 class 文件膝迎,提供 給程序運(yùn)行時(shí)使用。每個(gè) Class 對象的內(nèi)部都有一個(gè) classLoader 字段來標(biāo)識自己是由哪個(gè) ClassLoader 加載的胰耗。

class Class<T> { 
    ...
    private transient ClassLoader classLoader; 
    ...
}

ClassLoader是一個(gè)抽象類限次,而它的具體實(shí)現(xiàn)類主要有:

  • BootClassLoader

    用于加載Android Framework層class文件。

  • PathClassLoader

    用于Android應(yīng)用程序類加載器柴灯〉嗨。可以加載指定的dex,以及jar弛槐、zip懊亡、apk中的classes.dex

  • DexClassLoader

    用于加載指定的dex,以及jar乎串、zip店枣、apk中的classes.dex

很多博客里說PathClassLoader只能加載已安裝的apk的dex,其實(shí)這說的應(yīng)該是在dalvik虛擬機(jī)上

但現(xiàn)在一般不用關(guān)心dalvik了叹誉。

Log.e(TAG, "Activity.class 由:" + Activity.class.getClassLoader() +" 加載"); 
Log.e(TAG, "MainActivity.class 由:" + getClassLoader() +" 加載");
//輸出:
Activity.class 由:java.lang.BootClassLoader@d3052a9 加載
    
MainActivity.class 由:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.enjoyfix-1/base.apk"],nativeLibraryDirectories= [/data/app/com.enjoy.enjoyfix-1/lib/x86, /system/lib, /vendor/lib]]] 加載
image

PathClassLoaderDexClassLoader 的共同父類是 BaseDexClassLoader 鸯两。

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
        String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    } 
}
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);
    } 
}

可以看到兩者唯一的區(qū)別在于:創(chuàng)建 DexClassLoader 需要傳遞一個(gè) optimizedDirectory 參數(shù),并且會將其創(chuàng)建 為 File 對象傳給 super 长豁,而 PathClassLoader 則直接給到null钧唐。因此兩者都可以加載指定的dex,以及jar匠襟、 zip钝侠、apk中的classes.dex

PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/xx.dex", getClassLoader());
File dexOutputDir = context.getCodeCacheDir();
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/xx.dex",dexOutputDir.getAbsolutePath(), null,getClassLoader());

其實(shí), optimizedDirectory 參數(shù)就是dexopt的產(chǎn)出目錄(odex)该园。那 PathClassLoader 創(chuàng)建時(shí),這個(gè)目錄為null帅韧,就 意味著不進(jìn)行dexopt?并不是里初, optimizedDirectory 為null時(shí)的默認(rèn)路徑為:/data/dalvik-cache

在API 26源碼中忽舟,將DexClassLoader的optimizedDirectory標(biāo)記為了 deprecated 棄用双妨,實(shí)現(xiàn)也變?yōu)榱?

public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}

和PathClassLoader一摸一樣了!

雙親委托機(jī)制

可以看到創(chuàng)建 ClassLoader 需要接收一個(gè) ClassLoader parent 參數(shù)。這個(gè) parent 的目的就在于實(shí)現(xiàn)類加載的雙 親委托叮阅。即:
某個(gè)類加載器在接到加載類的請求時(shí)刁品,首先將加載任務(wù)委托給父類加載器,依次遞歸浩姥,如果父類加載器可以完成類 加載任務(wù)哑诊,就成功返回;只有父類加載器無法完成此加載任務(wù)時(shí),才自己去加載及刻。

 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    // 檢查class是否有被加載
    Class c = findLoadedClass(name); 
    if (c == null) {
    long t0 = System.nanoTime(); 
        try {
            if (parent != null) { //如果parent不為null,則調(diào)用parent的loadClass進(jìn)行加載 
                c = parent.loadClass(name, false);
            } else { //parent為null竞阐,則調(diào)用BootClassLoader進(jìn)行加載 
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
        }
        if (c == null) {
            // 如果都找不到就自己查找
            long t1 = System.nanoTime(); c = findClass(name);
        } 
    }
    return c; 
 }

因此我們自己創(chuàng)建的ClassLoader: new PathClassLoader("/sdcard/xx.dex", getClassLoader()); 并不僅僅 只能加載 xx.dex中的class缴饭。

值得注意的是: c = findBootstrapClassOrNull(name);

按照方法名理解,應(yīng)該是當(dāng)parent為null時(shí)候骆莹,也能夠加載 BootClassLoader 加載的類颗搂。

new PathClassLoader("/sdcard/xx.dex", null) ,能否加載Activity.class?

但是實(shí)際上幕垦,Android當(dāng)中的實(shí)現(xiàn)為:(Java不同)

findClass

可以看到在所有父ClassLoader無法加載Class時(shí)丢氢,則會調(diào)用自己的 findClass 方法。 findClass 在ClassLoader中的定義為:

 protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

其實(shí)任何ClassLoader子類先改,都可以重寫 loadClass 與 findClass 疚察。一般如果你不想使用雙親委托,則重寫 loadClass 修改其實(shí)現(xiàn)仇奶。而重寫 findClass 則表示在雙親委托下貌嫡,父ClassLoader都找不到Class的情況下,定義 自己如何去查找一個(gè)Class该溯。而我們的 PathClassLoader 會自己負(fù)責(zé)加載 MainActivity 這樣的程序中自己編寫的類岛抄,利用雙親委托父ClassLoader加載Framework中的 Activity 。說明 PathClassLoader 并沒有重寫 loadClass 狈茉,因此我們可以來看看PathClassLoader中的 findClass 是如何實(shí)現(xiàn)的夫椭。

 
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String
                        librarySearchPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, librarySearchPath,optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException { 
    List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 
    //查找指定的class
    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); 
        }
    } 
    return c;
}

實(shí)現(xiàn)非常簡單,從 pathList 中查找class氯庆。繼續(xù)查看 DexPathLis

public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
//.........
// splitDexPath 實(shí)現(xiàn)為返回 List<File>.add(dexPath)
// makeDexElements 會去 List<File>.add(dexPath) 中使用DexFile加載dex文件返回 Element數(shù)組 
    this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
}
public Class findClass(String name, List<Throwable> suppressed) { 
    //從element中獲得代表Dex的 DexFile
    for (Element element : dexElements) { 
        DexFile dex = element.dexFile;
        if (dex != null) {
        //查找class
        Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); 
            if (clazz != null) {
                 return clazz;
            }
        } 
    }
    if (dexElementsSuppressedExceptions != null) { 
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}
  

熱修復(fù)

PathClassLoader 中存在一個(gè)Element數(shù)組蹭秋,Element類中存在一個(gè)dexFile成員表示dex文件扰付,即:APK中有X個(gè) dex,則Element數(shù)組就有X個(gè)元素感凤。

image

在 PathClassLoader 中的Element數(shù)組為:[patch.dex , classes.dex , classes2.dex]悯周。如果存在Key.class位于 patch.dex與classes2.dex中都存在一份,當(dāng)進(jìn)行類查找時(shí)陪竿,循環(huán)獲得 dexElements 中的DexFile禽翼,查找到了 Key.class則立即返回,不會再管后續(xù)的element中的DexFile是否能加載到Key.class了族跛。

因此實(shí)際上闰挡,一種熱修復(fù)實(shí)現(xiàn)可以將出現(xiàn)Bug的class單獨(dú)的制作一份fix.dex文件(補(bǔ)丁包),然后在程序啟動時(shí)礁哄,從 服務(wù)器下載fix.dex保存到某個(gè)路徑长酗,再通過fix.dex的文件路徑,用其創(chuàng)建 Element 對象桐绒,然后將這個(gè) Element 對 象插入到我們程序的類加載器 PathClassLoader 的 pathList 中的 dexElements 數(shù)組頭部夺脾。這樣在加載出現(xiàn)Bug的 class時(shí)會優(yōu)先加載fix.dex中的修復(fù)類,從而解決Bug茉继。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咧叭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子烁竭,更是在濱河造成了極大的恐慌菲茬,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件派撕,死亡現(xiàn)場離奇詭異婉弹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)终吼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門镀赌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人际跪,你說我怎么就攤上這事佩脊。” “怎么了垫卤?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵威彰,是天一觀的道長。 經(jīng)常有香客問我穴肘,道長歇盼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任评抚,我火速辦了婚禮豹缀,結(jié)果婚禮上伯复,老公的妹妹穿的比我還像新娘。我一直安慰自己邢笙,他們只是感情好啸如,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著氮惯,像睡著了一般叮雳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妇汗,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天帘不,我揣著相機(jī)與錄音,去河邊找鬼杨箭。 笑死寞焙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的互婿。 我是一名探鬼主播捣郊,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慈参!你這毒婦竟也來了呛牲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤懂牧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后尊勿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僧凤,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年元扔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躯保。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡澎语,死狀恐怖途事,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情擅羞,我是刑警寧澤尸变,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站减俏,受9級特大地震影響召烂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜娃承,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一奏夫、第九天 我趴在偏房一處隱蔽的房頂上張望怕篷。 院中可真熱鬧,春花似錦酗昼、人聲如沸廊谓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒸痹。三九已至,卻和暖如春碟婆,著一層夾襖步出監(jiān)牢的瞬間电抚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工竖共, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝙叛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓公给,卻偏偏與公主長得像借帘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子淌铐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

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