Android熱修復原理簡介

Android熱修復原理簡介

今天看到塞爾維亞總統(tǒng)在全國電視直播中說到期揪,只有中國才能救我們的時候,作為中國人的那種驕傲油然而生摇幻,很幸運能見證中國的崛起和強大横侦,這才是大國當擔的樣子。

閑話少說绰姻,今天準備寫一篇關(guān)于Android熱修復的東西

熱修復四大框架

首先我們來對看一下主流框架對于熱修復的對比圖,了解一下各大廠商用的框架對比引瀑。熱補丁方案有很多狂芋,其中比較出名的有騰訊Tinker、阿里的AndFix憨栽、美團的Robust以及QZone的超級補丁方案帜矾,下面是他們的對比

熱修復框架對比圖

騰訊Tinker:Tinker通過計算對比指定的Base Apk中的dex與修改后的Apk中的dex的區(qū)別,補丁包中的內(nèi)容即為兩者差分的描述屑柔。運行時將Base Apk中的dex與補丁包進行合成屡萤,重啟后加載全新的合成后的dex文件。

特點:重啟生效掸宛、反射死陆、類加載、DexDiff

QQ的Qzone:QQ空間基于的是dex分包方案唧瘾。把BUG方法修復以后措译,放到一個單獨的dex補丁文件,讓程序運行期間加載dex補丁饰序,執(zhí)行修復后的方法领虹。如何做到這一點?在Android中所有我們運行期間需要的類都是由ClassLoader(類加載器)進行加載求豫。因此讓ClassLoader加載全新的類替換掉出現(xiàn)Bug的類即可完成熱修復塌衰。

特點:重啟生效诉稍、反射、類加載

美團Robust:對每個函數(shù)都在編譯打包階段自動的插入了一段代碼最疆。類似于代理均唉,將方法執(zhí)行的代碼重定向到其他方法中。

特點:即時生效肚菠、注解舔箭、插樁、代理

阿里AndFix:在native動態(tài)替換java層的方法蚊逢,通過native層hook java層的代碼层扶。

特點:即時生效、不能替換類烙荷,只是通過改變Native層的指針改變所指向的方法镜会,從而完成對方法的修復

以上是各大平臺使用熱修復方案的優(yōu)缺點,有些地方可能有些難以理解终抽,這篇文章將著重介紹Qzone的原理和具體實現(xiàn)戳表,其它方案讀者可以自行研究,此處只做簡單的介紹昼伴。

QQ空間Qzone原理

在介紹Qzone的實現(xiàn)原理之前匾旭,需要向大家介紹這么幾個知識點:

  1. 類加載機制 classloader的原理

    我們知道任何一個類的class對象都會對應一個classloader,表示該類被哪個類加載器加載圃郊,Android原生api為我們提供了二種ClassLoader的抽象子類价涝,分別為BootClassLoader,BaseClassLoader

    BootClassLoader用于加載Android Framework層的class文件持舆,例于Activity.class等等

    BaseDe'xClassLoadexer下面又有兩個子類色瘩,PathClassLoader,DexClassLoder

    PathClassLoader用于加載自己寫的類逸寓,或者第三方庫里面的類居兆,包括android自己開發(fā)的第三方庫

    DexClassLoder 和PathClassLoader一樣,都是用來加載class文件

    其實兩者并沒有太大區(qū)別竹伸,只是構(gòu)造方法不同而已泥栖,谷歌的意思是系統(tǒng)的類用pathclassloader,而我們用戶自己寫的類用DexClassLoder佩伤,但其實兩者可以互相替換使用聊倔,只不過DexClassLoder比pathclassloader的構(gòu)造方法多了一個參數(shù),而這個參數(shù)只是用來保存我們的odex文件的目錄生巡,且在android更高的版本耙蔑,這個參數(shù)也被棄用,被統(tǒng)一保存到系統(tǒng)的目錄中孤荣。

    這些類加載器有一個共同的特性甸陌,在加載完一個類的class文件以后须揣,不會再去加載相同的class文件,而我們就是利用這種機制钱豁,去實現(xiàn)熱修復耻卡。

    在應用程序啟動的時候,所有類的class文件牲尺,會被添加到一個Element的數(shù)組中卵酪,classloader有序的遍歷這個數(shù)組,當遇見加載過重復的類時谤碳,就不會再去加載溃卡,所以我們只要想辦法,幫我們要修復的class文件添加到這個集合的最前面蜒简,也就完成了熱修復功能瘸羡。

  2. 雙親委托機制

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

這是classloader加載類時候的源碼,findLoadedClass相當于緩存搓茬,如果之前加載過可以直接加載出來犹赖。假設(shè)我們程序重新啟動,代碼會執(zhí)行到 c = parent.loadClass(name, false); 查看源碼可知parent為classloader內(nèi)部維護的一個成員變量classloader parent卷仑,這里優(yōu)先讓parent加載類峻村,如果parent沒有找到,自己再去找系枪,其實這里面有點類似裝飾者模式雀哨,我們思考一個問題,在這個內(nèi)部維護的parent內(nèi)部是不是也有一個相同的classloader 私爷,然后在查找這個name的時候,又會委托parent內(nèi)部維護的classloader 去做膊夹,直到找不到為止衬浑,就自己來找。我們把這種機制稱之為雙親委托機制放刨。永遠先讓父加載器加載工秩。總結(jié)入下:

某個類加載器在加載類時进统,首先將加載任務委托給父 - 類加載器助币,依次遞歸,如果父類加載器可以完成類加載任務螟碎,就成功返回眉菱;只有父類加載器無法完成此加載任務或者沒有父類加載器時,才自己去加載掉分。

那么為什么會有這個機制呢俭缓,

1克伊、避免重復加載,當父加載器已經(jīng)加載了該類的時候华坦,就沒有必要子ClassLoader再加載一次愿吹。且只有一個classLoader就能加載出來系統(tǒng)所有的class對象

2、安全性考慮惜姐,防止核心API庫被隨意篡改犁跪。 (假設(shè)我創(chuàng)建一個String類,如果沒有這種機制歹袁,回導致我們的String類把系統(tǒng)的String替換掉)

掌握以上兩點基礎(chǔ)知識坷衍,我們再來看看classloader是如何去加載一個類的宇攻。我們已經(jīng)了解了惫叛,如果我們自己寫一個類是會被PathClassLoader加載的,所以parent.loadClass(name, false)是注定找不到我們要修復的類嘉涌,然后我們看看findClass的邏輯仑最。PathClassLoader沒有實現(xiàn)這個方法帆喇,我們來看他的父類BaseDexClassLoader的findclass

private final DexPathList pathList;
@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;
}

在findclass里面警医,又是通過pathList來查找,所以我們可以繼續(xù)查看pathList.finClass做了什么

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內(nèi)部坯钦,又是通過element來findClass预皇,所以我們最終只要鎖定Element這個數(shù)組即可。系統(tǒng)會把我們所有dex文件婉刀,加載到Element數(shù)組中吟温,然后有序遍歷,而我們要想給一個類打補丁突颊,就必須要保證這個補丁類的dex文件在錯誤類dex文件之前加載鲁豪,而實現(xiàn)步驟就是在這個數(shù)組最開始的位置插入這個打了補丁的dex文件即可。(因為數(shù)組大小固定律秃,為了避免數(shù)組角標越界爬橡,我們需要替換這個數(shù)組而不是插入)

所以總結(jié)一下,想要做到熱修復棒动,需要做到如下幾步:

  1. 獲取到當前應用的PathClassloader;

  2. 反射獲取到DexPathList屬性對象pathList;

  3. 反射修改pathList的dexElements
    3.1 把補丁包patch.dex轉(zhuǎn)化為Element[] (patch)
    3.2 獲得pathList的dexElements屬性(old)
    3.3 patch+old合并糙申,并反射賦值給pathList的dexElements

問題:QQ空間兼容問題
https://mp.weixin.qq.com/s?__biz=MzI1MTA1MzM2Nw==&mid=400118620&idx=1&sn=b4fdd5055731290eef12ad0d17f39d4a

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市迁客,隨后出現(xiàn)的幾起案子郭宝,更是在濱河造成了極大的恐慌辞槐,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粘室,死亡現(xiàn)場離奇詭異榄檬,居然都是意外死亡,警方通過查閱死者的電腦和手機衔统,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門鹿榜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锦爵,你說我怎么就攤上這事舱殿。” “怎么了险掀?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵沪袭,是天一觀的道長。 經(jīng)常有香客問我樟氢,道長冈绊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任埠啃,我火速辦了婚禮死宣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碴开。我一直安慰自己毅该,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布潦牛。 她就那樣靜靜地躺著眶掌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪巴碗。 梳的紋絲不亂的頭發(fā)上畏线,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音良价,去河邊找鬼。 笑死蒿叠,一個胖子當著我的面吹牛明垢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播市咽,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼痊银,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了施绎?” 一聲冷哼從身側(cè)響起溯革,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤贞绳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后致稀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冈闭,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年抖单,在試婚紗的時候發(fā)現(xiàn)自己被綠了萎攒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡矛绘,死狀恐怖耍休,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情货矮,我是刑警寧澤羊精,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站囚玫,受9級特大地震影響喧锦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜劫灶,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一裸违、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧本昏,春花似錦供汛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筐高。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間许赃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工猫缭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留麻养,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓罩锐,卻偏偏與公主長得像奉狈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子涩惑,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

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