很早之前就想深入的研究和學習一下熱修復(fù)鞋邑,由于時間的原因一直拖著洼滚,現(xiàn)在才執(zhí)筆弄起來瞒渠。
Android而更新系列:
Android熱更新一:JAVA的類加載機制
Android熱更新二:理解Java反射
Android熱更新三:Android類加載機制
Android熱更新四:熱修復(fù)機制
Android熱更新五:四大熱修復(fù)方案分析
Android熱更新六:Qzone熱更新原理
Android熱更新七:Tinker熱更新原理
Android熱更新八:AndFix熱更新原理
Android熱更新九:Robust熱更新原理
Android熱更新十:自己寫一個Android熱修復(fù)
熱修復(fù)機制
之前已經(jīng)了解了Android類加載機制,知道在DexPathList里有個dexElements的數(shù)組
源碼中官方注釋
/**
* 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;
熱修復(fù)就是利用dexElements的順序來做文章疑俭,當一個補丁的patch.dex放到了dexElements的第一位粮呢,那么當加載一個bug類時,發(fā)現(xiàn)在patch.dex中钞艇,則直接加載這個類啄寡,原來的bug類可能就被覆蓋了
看下PathClassLoader代碼
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
DexClassLoader代碼
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
兩個ClassLoader就兩三行代碼,只是調(diào)用了父類的構(gòu)造函數(shù).
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
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;
}
在BaseDexClassLoader 構(gòu)造函數(shù)中創(chuàng)建一個DexPathList類的實例,這個DexPathList的構(gòu)造函數(shù)會創(chuàng)建一個dexElements 數(shù)組
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) {
...
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
//創(chuàng)建一個數(shù)組
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
...
}
然后BaseDexClassLoader 重寫了findClass方法,調(diào)用了pathList.findClass,跳到DexPathList類中.
/* package */final class DexPathList {
...
public Class findClass(String name, List<Throwable> suppressed) {
//遍歷該數(shù)組
for (Element element : dexElements) {
//初始化DexFile
DexFile dex = element.dexFile;
if (dex != null) {
//調(diào)用DexFile類的loadClassBinaryName方法返回Class實例
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
...
}
會遍歷這個數(shù)組,然后初始化DexFile哩照,如果DexFile不為空那么調(diào)用DexFile類的loadClassBinaryName方法返回Class實例.
歸納上面的話就是:ClassLoader會遍歷這個數(shù)組,然后加載這個數(shù)組中的dex文件.
而ClassLoader在加載到正確的類之后,就不會再去加載有Bug的那個類了,我們把這個正確的類放在Dex文件中,讓這個Dex文件排在dexElements數(shù)組前面即可.
CLASS_ISPREVERIFIED問題
根據(jù)QQ空間談到的在虛擬機啟動的時候挺物,在verify選項被打開的時候,如果static方法飘弧、private方法识藤、構(gòu)造函數(shù)等,其中的直接引用(第一層關(guān)系)到的類都在同一個dex文件中次伶,那么該類就會被打上CLASS_ISPREVERIFIED標志痴昧,且一旦類被打上CLASS_ISPREVERIFIED標志其他dex就不能再去替換這個類。所以一定要想辦法去阻止類被打上CLASS_ISPREVERIFIED標志冠王。
為了阻止類被打上CLASS_ISPREVERIFIED標志赶撰,QQ空間開發(fā)團隊提出了一個方法是先將一個預(yù)備好的hack.dex加入到dexElements的第一項,讓后面的dex的所有類都引用hack.dex其中的一個類,這樣原來的class1.dex豪娜、class2.dex餐胀、class3.dex中的所有類都引用了hack.dex的類,所以其中的都不會打上CLASS_ISPREVERIFIED標志瘤载。
Qzon團隊的 安卓App熱補丁動態(tài)修復(fù)技術(shù)介紹
(這個一定要看!!! 他是熱修復(fù)元老級文章,也是本文重點抄襲對象??????)