Android熱修復(fù)技術(shù)——QQ空間補(bǔ)丁方案解析(1)

傳統(tǒng)的app開(kāi)發(fā)模式下,線上出現(xiàn)bug忘伞,必須通過(guò)發(fā)布新版本,用戶手動(dòng)更新后才能修復(fù)線上bug沙兰。隨著app的業(yè)務(wù)越來(lái)越復(fù)雜氓奈,代碼量爆發(fā)式增長(zhǎng),出現(xiàn)bug的機(jī)率也隨之上升鼎天。如果單純靠發(fā)版修復(fù)線上bug舀奶,其較長(zhǎng)的新版覆蓋期無(wú)疑會(huì)對(duì)業(yè)務(wù)造成巨大的傷害,更不要說(shuō)大型app開(kāi)發(fā)通常涉及多個(gè)團(tuán)隊(duì)協(xié)作斋射,發(fā)版排期必須多方協(xié)調(diào)育勺。
那么是否存在一種方案可以在不發(fā)版的前提下修復(fù)線上bug?有罗岖!而且不只一種涧至,業(yè)界各家大廠都針對(duì)這一問(wèn)題拿出了自家的解決方案,較為著名的有騰訊的Tinker和阿里的Andfix以及QQ空間補(bǔ)丁桑包。網(wǎng)上對(duì)上述方案有很多介紹性文章南蓬,不過(guò)大多不全面,中間略過(guò)很多細(xì)節(jié)哑了。筆者在學(xué)習(xí)的過(guò)程中也遇到很多麻煩赘方。所以筆者將通過(guò)接下來(lái)幾篇博客對(duì)上述兩種方案進(jìn)行介紹,力求不放過(guò)每一個(gè)細(xì)節(jié)弱左。首先來(lái)看下QQ空間補(bǔ)丁方案窄陡。

1. Dex分包機(jī)制

大家都知道,我們開(kāi)發(fā)的代碼在被編譯成class文件后會(huì)被打包成一個(gè)dex文件拆火。但是dex文件有一個(gè)限制跳夭,由于方法id是一個(gè)short類(lèi)型,所以導(dǎo)致了一個(gè)dex文件最多只能存放65536個(gè)方法榜掌。隨著現(xiàn)今App的開(kāi)發(fā)日益復(fù)雜优妙,導(dǎo)致方法數(shù)早已超過(guò)了這個(gè)上限。為了解決這個(gè)問(wèn)題憎账,Google提出了multidex方案套硼,即一個(gè)apk文件可以包含多個(gè)dex文件。
不過(guò)值得注意的是胞皱,除了第一個(gè)dex文件以外邪意,其他的dex文件都是以資源的形式被加載的九妈,換句話說(shuō)就是在Application.onCreate()方法中被注入到系統(tǒng)的ClassLoader中的。這也就為熱修復(fù)提供了一種可能:將修復(fù)后的代碼達(dá)成補(bǔ)丁包雾鬼,然后發(fā)送到客戶端萌朱,客戶端在啟動(dòng)的時(shí)候到指定路徑下加載對(duì)應(yīng)dex文件即可。
根據(jù)Android虛擬機(jī)的類(lèi)加載機(jī)制策菜,同一個(gè)類(lèi)只會(huì)被加載一次晶疼,所以要讓修復(fù)后的類(lèi)替換原有的類(lèi)就必須讓補(bǔ)丁包的類(lèi)被優(yōu)先加載。接下來(lái)看下Android虛擬機(jī)的類(lèi)加載機(jī)制又憨。

2. 類(lèi)加載機(jī)制

Android的類(lèi)加載機(jī)制和jvm加載機(jī)制類(lèi)似翠霍,都是通過(guò)ClassLoader來(lái)完成,只是具體的類(lèi)不同而已:

1
1

Android系統(tǒng)通過(guò)PathClassLoader來(lái)加載系統(tǒng)類(lèi)和主dex中的類(lèi)蠢莺。而DexClassLoader則用于加載其他dex文件中的類(lèi)寒匙。上述兩個(gè)類(lèi)都是繼承自BaseDexClassLoader,具體的加載方法是findClass:

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

從代碼中可以看到加載類(lèi)的工作轉(zhuǎn)移到了pathList中躏将,pathList是一個(gè)DexPathList類(lèi)型锄弱,從變量名和類(lèi)型名就可以看出這是一個(gè)維護(hù)Dex的容器:

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

DexPathListfindClass也很簡(jiǎn)單,dexElements是維護(hù)dex文件的數(shù)組祸憋,每一個(gè)item對(duì)應(yīng)一個(gè)dex文件会宪。DexPathList遍歷dexElements,從每一個(gè)dex文件中查找目標(biāo)類(lèi)夺衍,在找到后即返回并停止遍歷狈谊。所以要想達(dá)到熱修復(fù)的目的就必須讓補(bǔ)丁dex在dexElements中的位置先于原有dex:

2
2
3
3

這就是QQ空間補(bǔ)丁方案的基本思路,接下來(lái)的博文筆者將以一個(gè)實(shí)際的例子詳述QQ空間補(bǔ)丁熱修復(fù)的過(guò)程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沟沙,一起剝皮案震驚了整個(gè)濱河市河劝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矛紫,老刑警劉巖赎瞎,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異颊咬,居然都是意外死亡务甥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)喳篇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)敞临,“玉大人,你說(shuō)我怎么就攤上這事麸澜⊥δ颍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)编矾。 經(jīng)常有香客問(wèn)我熟史,道長(zhǎng),這世上最難降的妖魔是什么窄俏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任蹂匹,我火速辦了婚禮,結(jié)果婚禮上凹蜈,老公的妹妹穿的比我還像新娘限寞。我一直安慰自己,他們只是感情好踪区,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布昆烁。 她就那樣靜靜地躺著吊骤,像睡著了一般缎岗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上白粉,一...
    開(kāi)封第一講書(shū)人閱讀 51,245評(píng)論 1 299
  • 那天传泊,我揣著相機(jī)與錄音,去河邊找鬼鸭巴。 笑死眷细,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹃祖。 我是一名探鬼主播溪椎,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼恬口!你這毒婦竟也來(lái)了校读?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤祖能,失蹤者是張志新(化名)和其女友劉穎歉秫,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體养铸,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雁芙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钞螟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兔甘。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鳞滨,靈堂內(nèi)的尸體忽然破棺而出洞焙,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布闽晦,位于F島的核電站扳碍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仙蛉。R本人自食惡果不足惜笋敞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荠瘪。 院中可真熱鬧夯巷,春花似錦、人聲如沸哀墓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)篮绰。三九已至后雷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吠各,已是汗流浹背臀突。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贾漏,地道東北人候学。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像纵散,于是被迫代替她去往敵國(guó)和親梳码。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

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