【轉(zhuǎn)】Android熱補(bǔ)丁動態(tài)修復(fù)技術(shù)(一):從Dex分包原理到熱補(bǔ)丁

轉(zhuǎn)自

一群发、參考

博文:安卓App熱補(bǔ)丁動態(tài)修復(fù)技術(shù)介紹——by QQ空間終端開發(fā)團(tuán)隊
博文:Android dex分包方案——by 貓的午后
開源項目:https://github.com/jasonross/Nuwa
開源項目:https://github.com/dodola/HotFix
感謝以上幾位大神分享的技術(shù)知識梭伐!

關(guān)于熱補(bǔ)丁技術(shù),以上文章已經(jīng)做了很詳細(xì)的描述。但是細(xì)節(jié)上的東西都一帶而過窖杀,這里會做出更為詳細(xì)的說明角骤,更適合初學(xué)者學(xué)習(xí)這門技術(shù)。

二养葵、Dex分包方案的由來

2.1 Dalvik限制

眾所周知征堪,當(dāng)apk解壓后里面是只有一個classes.dex文件的,而這個dex文件里面就包含了我們項目的所有.class文件关拒。

但是當(dāng)一個app功能越來越復(fù)雜佃蚜,可能會出現(xiàn)兩個問題:

編譯失敗,因為一個dvm中存儲方法id用的是short類型着绊,導(dǎo)致dex中方法不能超過65536個
你的apk在android 2.3之前的機(jī)器無法安裝谐算,因為dex文件過大(用來執(zhí)行dexopt的內(nèi)存只分配了5M)

2.2 解決方案

針對上述兩個問題,有人研究出了dex分包方案归露。
原理就是將編譯好的class文件拆分打包成兩個dex洲脂,繞過dex方法數(shù)量的限制以及安裝時的檢查,在運(yùn)行時再動態(tài)加載第二個dex文件中剧包。

除了第一個dex文件(即正常apk包唯一包含的Dex文件)恐锦,其它dex文件都以資源的方式放在安裝包中往果,并在Application的onCreate回調(diào)中被注入到系統(tǒng)的ClassLoader。因此一铅,對于那些在注入之前已經(jīng)引用到的類(以及它們所在的jar),必須放入第一個Dex文件中陕贮。

三、Dex分包的原理——ClassLoader

接下來我們就來看看馅闽,如何將第二個dex文件注入到系統(tǒng)中飘蚯。

3.1 ClassLoader體系

我們都知道,java執(zhí)行程序的時候是需要先將字節(jié)碼加載到j(luò)vm之后才會被執(zhí)行的福也,而這個過程就是使用到了ClassLoader類加載器局骤。Android也是如此

以下是DVM的ClassLoader體系

查看官方文檔可以知道以下兩點:

1.Android系統(tǒng)是通過PathClassLoader加載系統(tǒng)類和已安裝的應(yīng)用的。

Android uses this class for its system class loader and for its application class loader(s).

2.而DexClassPath則可以從一個jar包或者未安裝的apk中加載dex

A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

從這里就可以看出暴凑,動態(tài)加載dex的時候我們應(yīng)該使用DexClassLoader

3.2 ClassLoader源碼分析

源碼可以到這個網(wǎng)站查閱:http://androidxref.com/

DexClassLoader和PathClassLoader都只重寫了BaseDexClassLoader的構(gòu)造而已峦甩,而具體的加載邏輯則在BaseDexClassLoader中。

這部分源碼都很簡單现喳,請務(wù)必看懂

BaseDexClassLoader部分源碼:

public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;

/**
 * Constructs an instance.
 *
 * @param dexPath the list of jar/apk files containing classes and
 * resources, delimited by {@code File.pathSeparator}, which
 * defaults to {@code ":"} on Android
 * @param optimizedDirectory directory where optimized dex files
 * should be written; may be {@code null}
 * @param libraryPath the list of directories containing native
 * libraries, delimited by {@code File.pathSeparator}; may be
 * {@code null}
 * @param parent the parent class loader
 */
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String libraryPath, ClassLoader parent) {
    super(parent);
    this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

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

從源碼得知凯傲,當(dāng)我們需要加載一個class時,實際是從pathList中去找的嗦篱,而pathList則是DexPathList的一個實體冰单。

DexPathList部分源碼:

/package/ final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String JAR_SUFFIX = ".jar";
private static final String ZIP_SUFFIX = ".zip";
private static final String APK_SUFFIX = ".apk";

/** class definition context */
private final ClassLoader definingContext;

/**
 * List of dex/resource (class path) elements.
 * Should be called pathElements, but the Facebook app uses reflection
 * to modify 'dexElements' (http://b/7726934).
 */
private final Element[] dexElements;

/**
 * Finds the named class in one of the dex files pointed at by
 * this instance. This will find the one in the earliest listed
 * path element. If the class is found but has not yet been
 * defined, then this method will define it in the defining
 * context that this instance was constructed with.
 *
 * @param name of class to find
 * @param suppressed exceptions encountered whilst finding the class
 * @return the named class or {@code null} if the class is not
 * found in any of the dex files
 */
public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

從這段源碼可以看出,dexElements是用來保存dex的數(shù)組灸促,而每個dex文件其實就是DexFile對象诫欠。遍歷dexElements,然后通過DexFile去加載class文件浴栽,加載成功就返回荒叼,否則返回null

通常情況下,dexElements數(shù)組中只會有一個元素典鸡,就是apk安裝包中的classes.dex
而我們則可以通過反射被廓,強(qiáng)行的將一個外部的dex文件添加到此dexElements中,這就是dex的分包原理了萝玷。
這也是熱補(bǔ)丁修復(fù)技術(shù)的原理嫁乘。

四、熱補(bǔ)丁修復(fù)技術(shù)的原理

上面的源碼球碉,我們注意到一點蜓斧,如果兩個dex中存在相同的class文件會怎樣?
先從第一個dex中找汁尺,找到了直接返回法精,遍歷結(jié)束多律。而第二個dex中的class永遠(yuǎn)不會被加載進(jìn)來痴突。
簡而言之搂蜓,兩個dex中存在相同class的情況下,dex1的class會覆蓋dex2的class辽装。
盜一下QQ空間的圖帮碰,如圖:classes1.dex中的Qzone.class并不會被加載

而熱補(bǔ)丁技術(shù)則利用了這一特性,當(dāng)一個app出現(xiàn)bug的時候拾积,我們就可以將出現(xiàn)那個bug的類修復(fù)后殉挽,重新編譯打包成dex,插入到dexElements的前面拓巧,那么出現(xiàn)bug的類就會被覆蓋斯碌,app正常運(yùn)行,這就是熱修復(fù)的原理了肛度。

五傻唾、本章結(jié)束

這章為大家介紹了熱補(bǔ)丁技術(shù)的原理,但是大家可能并不會實際操作承耿。

  1. 怎么通過反射將dex插入到elements
  2. 怎么講修復(fù)后的類打包成dex
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冠骄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子加袋,更是在濱河造成了極大的恐慌凛辣,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件职烧,死亡現(xiàn)場離奇詭異扁誓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)阳堕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門跋理,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恬总,你說我怎么就攤上這事前普。” “怎么了壹堰?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵拭卿,是天一觀的道長。 經(jīng)常有香客問我贱纠,道長峻厚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任谆焊,我火速辦了婚禮惠桃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己辜王,他們只是感情好劈狐,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著呐馆,像睡著了一般肥缔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上汹来,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天续膳,我揣著相機(jī)與錄音,去河邊找鬼收班。 笑死坟岔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的摔桦。 我是一名探鬼主播炮车,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酣溃!你這毒婦竟也來了瘦穆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赊豌,失蹤者是張志新(化名)和其女友劉穎扛或,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碘饼,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡熙兔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了艾恼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片住涉。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钠绍,靈堂內(nèi)的尸體忽然破棺而出舆声,到底是詐尸還是另有隱情,我是刑警寧澤柳爽,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布媳握,位于F島的核電站,受9級特大地震影響磷脯,放射性物質(zhì)發(fā)生泄漏蛾找。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一赵誓、第九天 我趴在偏房一處隱蔽的房頂上張望打毛。 院中可真熱鬧柿赊,春花似錦、人聲如沸幻枉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽展辞。三九已至,卻和暖如春万牺,著一層夾襖步出監(jiān)牢的瞬間罗珍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工脚粟, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留覆旱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓核无,卻偏偏與公主長得像扣唱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子团南,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355