熱修復(fù)、熱補(bǔ)丁與插件化

標(biāo)簽(空格分隔): Android


基礎(chǔ)知識(shí)補(bǔ)充:###

為什么需要分包:
Android2.3及以前版本用來(lái)執(zhí)行dexopt(用于優(yōu)化dex文件)的內(nèi)存只分配了5M,
一個(gè)dex文件最多只支持65536個(gè)方法

其實(shí)android中的分包,除了用dex分包還可以用插件化,即將一些獨(dú)立的功能做成一個(gè)單獨(dú)的apk,當(dāng)打開的時(shí)候使用DexClassLoader動(dòng)態(tài)加載,然后使用反射機(jī)制來(lái)調(diào)用插件中的類和方法。這固然是一種解決問(wèn)題的方案:但這種方案存在著以下兩個(gè)問(wèn)題:

  1. 插件化只適合一些比較獨(dú)立的模塊;
  2. 必須通過(guò)反射機(jī)制去調(diào)用插件的類和方法,因此,必須搭配一套插件框架來(lái)配合使用;

但是使用dex分包方案仍然有幾個(gè)注意點(diǎn):

  1. 由于第二個(gè)dex包是在Application的onCreate中動(dòng)態(tài)注入的,如果dex包過(guò)大,會(huì)使app的啟動(dòng)速度變慢,因此,在dex分包過(guò)程中一定要注意,第二個(gè)dex包不宜過(guò)大。
  2. 由于上述第一點(diǎn)的限制,假如我們的app越來(lái)越臃腫和龐大,往往會(huì)采取dex分包方案和插件化方案配合使用,將一些非核心獨(dú)立功能做成插件加載,核心功能再分包加載。(采用dex分包+插件化)

熱修復(fù)、熱補(bǔ)丁##

參考文章:安卓App熱補(bǔ)丁動(dòng)態(tài)修復(fù)技術(shù)介紹——QQ空間終端開發(fā)團(tuán)隊(duì)鴻洋文章

應(yīng)用場(chǎng)景:
當(dāng)一個(gè)App發(fā)布之后,突然發(fā)現(xiàn)了一個(gè)嚴(yán)重bug需要進(jìn)行緊急修復(fù),這時(shí)候公司各方就會(huì)忙得焦頭爛額:重新打包App、測(cè)試、向各個(gè)應(yīng)用市場(chǎng)和渠道換包、提示用戶升級(jí)、用戶下載、覆蓋安裝。有時(shí)候僅僅是為了修改了一行代碼,也要付出巨大的成本進(jìn)行換包和重新發(fā)布。
這時(shí)候就提出一個(gè)問(wèn)題:有沒(méi)有辦法以補(bǔ)丁的方式動(dòng)態(tài)修復(fù)緊急Bug,不再需要重新發(fā)布App,不再需要用戶重新下載,覆蓋安裝?

熱修復(fù)是基于android dex分包方案的,android dex分包原理
簡(jiǎn)單來(lái)說(shuō),其原理是將編譯好的class文件拆分打包成兩個(gè)dex,繞過(guò)dex方法數(shù)量的限制以及安裝時(shí)的檢查,在運(yùn)行時(shí)再動(dòng)態(tài)加載第二個(gè)dex文件中。除了第一個(gè)dex文件(即正常apk包唯一包含的Dex文件),其它dex文件都以資源的方式放在安裝包中,并在Application的onCreate回調(diào)中被注入到系統(tǒng)的ClassLoader。因此,對(duì)于那些在注入之前已經(jīng)引用到的類(以及它們所在的jar),必須放入第一個(gè)Dex文件中。


這里要區(qū)別PathClassLoader與DexClassLoader###

PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader
1、Android使用PathClassLoader作為其類加載器,只能去加載已經(jīng)安裝到Android系統(tǒng)中的apk文件;
2、DexClassLoader可以從.jar和.apk類型的文件內(nèi)部加載classes.dex文件就好了。如果大家對(duì)于插件化有所了解,肯定對(duì)這個(gè)類不陌生,插件化一般就是提供一個(gè)apk(插件)文件,然后在程序中l(wèi)oad該apk,那么如何加載apk中的類呢?其實(shí)就是通過(guò)這個(gè)DexClassLoader。熱修復(fù)也用到這個(gè)類!

#BaseDexClassLoader源碼
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    Class clazz = pathList.findClass(name);

    if (clazz == null) {
        throw new ClassNotFoundException(name);
    }

    return clazz;
}

#DexPathList
public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext);
            if (clazz != null) {
                return clazz;
            }
        }
    }

    return null;
}

#DexFile
public Class loadClassBinaryName(String name, ClassLoader loader) {
    return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);
//
可以看出呢,BaseDexClassLoader中有個(gè)pathList對(duì)象,pathList中包含一個(gè)DexFile的集合dexElements,而對(duì)于類加載呢,就是遍歷這個(gè)集合,通過(guò)DexFile去尋找。
ok,通俗點(diǎn)說(shuō):
一個(gè)ClassLoader可以包含多個(gè)dex文件,每個(gè)dex文件是一個(gè)Element,多個(gè)dex文件排列成一個(gè)有序的數(shù)組dexElements,當(dāng)找類的時(shí)候,會(huì)按順序遍歷dex文件,然后從當(dāng)前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個(gè)dex文件繼續(xù)查找。(來(lái)自:安卓App熱補(bǔ)丁動(dòng)態(tài)修復(fù)技術(shù)介紹)

總結(jié)##

其實(shí)就是兩件事:1、動(dòng)態(tài)改變BaseDexClassLoader對(duì)象間接引用的dexElements;2、在app打包的時(shí)候,阻止相關(guān)類去打上CLASS_ISPREVERIFIED標(biāo)志。


理論基礎(chǔ):把多個(gè)dex文件塞入到app的classloader之中,但是android dex拆包方案中的類是沒(méi)有重復(fù)的,如果classes.dex和classes1.dex中有重復(fù)的類,當(dāng)用到這個(gè)重復(fù)的類的時(shí)候,系統(tǒng)會(huì)選擇哪個(gè)類進(jìn)行加載呢?
   一個(gè)ClassLoader可以包含多個(gè)dex文件,每個(gè)dex文件是一個(gè)Element,多個(gè)dex文件排列成一個(gè)有序的數(shù)組dexElements,當(dāng)找類的時(shí)候,會(huì)按順序遍歷dex文件,然后從當(dāng)前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個(gè)dex文件繼續(xù)查找。
理論上,如果在不同的dex中有相同的類存在,那么會(huì)優(yōu)先選擇排在前面的dex文件的類

在此理論基礎(chǔ)上,我們構(gòu)想了熱補(bǔ)丁的方案,,我們可以在這個(gè)dexElements中去做一些事情,把有問(wèn)題的類打包到一個(gè)dex(patch.dex)中去,然后把這個(gè)dex插入到Elements的最前面比如,在這個(gè)數(shù)組的第一個(gè)元素放置我們的patch.jar,里面包含修復(fù)過(guò)的類,這樣的話,當(dāng)遍歷findClass的時(shí)候,我們修復(fù)的類就會(huì)被查找到,從而替代有bug的類。


插件化##

Android插件化基礎(chǔ):使用了自定義的ClassLoader來(lái)實(shí)現(xiàn)的加載,功能比較簡(jiǎn)單,只支持class的加載與另外也可以通過(guò)使用系統(tǒng)的DexClassLoader來(lái)直接加載Jar,Apk

使用PathClassLoader加載已安裝的apk插件、DexClassLoader加載未安裝的apk插件

通過(guò)插件化的操作,就可以做一個(gè)簡(jiǎn)單的代碼邏輯的插件化了。將你的邏輯處理代碼打包成Jar或者APK,放到服務(wù)器上。
客戶端開啟后檢查是否有新的版本,如果有就后臺(tái)下載,覆蓋源文件(這樣邏輯上應(yīng)該先把舊的邏輯加載到內(nèi)存中,這里可以有很多不同的策略)。
下次打開的時(shí)候,直接加載新下載的Jar或者APK就動(dòng)態(tài)更新了內(nèi)部邏輯
【與原先的下載新的APK跟新版本不同,因?yàn)椴寮腁PK只是新增的功能類,所以體積小】

插件化框架:

此處輸入圖片的描述
此處輸入圖片的描述

插件化發(fā)展歷史:博客一博客二
說(shuō)到未來(lái),也不得不提去年出來(lái)的ReactNative,RN比插件化更輕量級(jí),越來(lái)越多人選擇了RN,或許會(huì)代替插件化,雖然還有很多缺點(diǎn),比如說(shuō)沒(méi)網(wǎng)的時(shí)候


熱修復(fù)與插件化的對(duì)比##

共同原理:
都使用ClassLoader來(lái)實(shí)現(xiàn)的加載的新的功能類,都可以使用PathClassLoader與DexClassLoader
不同的是:
  熱修復(fù)因?yàn)槭菫榱诵迯?fù)Bug的,所以要將新的同名類替代同名的Bug類,要搶先加載新的類而不是Bug類,所以多做兩件事:在原先的app打包的時(shí)候,阻止相關(guān)類去打上CLASS_ISPREVERIFIED標(biāo)志,還有在熱修復(fù)時(shí)動(dòng)態(tài)改變BaseDexClassLoader對(duì)象間接引用的dexElements,這樣才能搶先代替Bug類,完成系統(tǒng)不加載舊的Bug類
  而插件化只是增肌新的功能類或者是資源文件,所以不涉及搶先加載舊的類這樣的使命,就避過(guò)了阻止相關(guān)類去打上CLASS_ISPREVERIFIED標(biāo)志和還有在熱修復(fù)時(shí)動(dòng)態(tài)改變BaseDexClassLoader對(duì)象間接引用的dexElements
  所以插件化比熱修復(fù)簡(jiǎn)單,熱修復(fù)是在插件化的基礎(chǔ)上在進(jìn)行替舊的Bug類

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容