一群发、參考
博文:安卓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ù)的原理,但是大家可能并不會實際操作承耿。
- 怎么通過反射將dex插入到elements
- 怎么講修復(fù)后的類打包成dex