Instant Run概述
Instant Run 是 Android Studio2.0 之後新增的一個運行機制,能夠減少開發人員第二次及以後的構建時間。
在沒有Instant run之前,編譯部署應用程序的流程如下圖:
傳統的編譯部署,需要重新安裝APP和重啓 APP。這樣會很耗時。
使用Instant Run會避免這一情況。
Instant Run基於更改的部分進行構建和部署。
Instant Run部署有三種方式,Instant Run會根據代碼的情況來決定採用哪種部署方式。
- hot swap——修改一個現有方法中的代碼時會採用hot swap,他不需要重啓當前Activity即可實現使更改的代碼運行
- warm swap——修改現有資源文件時採用warm swap,他需要重啓Activity。
- cold swap——當增刪改一個字段、方法或類時會採用cold swap。他需要重啓APP。
Gradle Transform
Gradle Transform是Android官方提供給開發者在項目構建階段即由class到dex轉換期間修改class文件的一套api。
Instant Run修復原理
- 當第一次構建APP時,Instant Run利用Transform API在每一個類中注入一個叫change的字段
- change實現了incrementalChange接口。
- 在每一個方法中添加一個邏輯判斷,如果change不爲空就執行change的accessdispatch方法,否則執行原方法的邏輯。
- 當我們修改完對應的代碼點擊run後,Instant Run會生成對應的patch文件。
- patch文件中補丁類的名字是修改類的名字+$override,這個類實現了IncrementalChange接口
- 同時生成一個AppPatchsLoaderImpl類,這個類維護着一個所有修改過的類的列表
- 通過AppPatchsLoaderImpl將修改過的類中的change字段賦值爲生成的xxx$override。
這樣就實現了對代碼的修改。
ClassLoader 修復代碼
類加載方案基於Dex分包方案。
65535限制
Android系統有一個65536限制,意思是應用中引用的方法數不能超過65536個。
產生一個限制的原因是Dalvik Bytecode的限制,Dalvik指令集的方法調用指令invoke-kind索引爲16bite,最多應用65536個方法。
LinearAlloc限制
Dalvik中的LinearAlloc是一個固定的緩衝區,當方法數超出了緩存區的大小時會報錯。
針對這兩個限制,產生了Dex分包方案。Dex分包方案主要做的是在打包時將應用代碼分成多個Dex。
將應用啓動時必須用到的類和這些類的引用類放到主Dex,其他代碼放到次Dex,當應用啓動時先加載主Dex,等到啓動後再動態加載次Dex。
我們都知道,Android的類加載主要有兩個:PathClassLoader和DexClassLoader,他們都是BaseDexClassLoader 的子類。
其中PathClassLoader在應用啓動時創建,用於加載apk的dex文件。
DexClassLoader可以加載SD卡上包含dex的.jar和.apk文件。
BaseDexClassLoader 有個字段private final DexPathList pathList
,BaseDexClassLoader 的findClass()、findResource()均是基於pathList實現的。
DexPathList 裏面有一個Element數組,Element內部封裝了DexFile ,DexFile用於加載dex文件,所以每個dex文件對應一個Element。
當通過BaseDexClassLoader 查找一個類時,會通過以下路線進行查找:
- 調用 BaseDexClassLader 的 findClass(String name)方法
- 接着交給 DexPathList 的findClass()方法
- 然後遍歷Element數組,調用Element的findClass()方法
- Element的findClass()方法內部調用DexFile的loadClassBinaryName方法查找類。
當查找到了就直接返回,沒有找到就去下一個Element查找。
所以,根據上述的查找流程,我們只需將我們修復後的類打包成Dex文件,存放到Element數組的最前面,這樣就會優先從修復後的dex中查找並返回
具體操作的步驟就是
- 獲得APP的PathClassLoader
- 通過dex包路徑和父加載器創建我們的DexClassLoader
- 反射得到兩個加載器的pathList
- 反射得到兩個pathList的dexElements
- 創建新的dexElements,將我們的Element放入dexElements最前面
- 反射賦值給PathClassLoader
代碼
由插件化那裏搬過來的,忽略插件字眼~
DexClassLoader dexClassLoader = new DexClassLoader(Environment.getExternalStorageDirectory().getAbsolutePath() + "/classes.dex",
getCacheDir().getAbsolutePath(), null, getClassLoader());*/
private void fix() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
//獲取PathClassLoader
PathClassLoader pathClassLoader =
(PathClassLoader) getApplicationContext().getClassLoader();
//分別獲取宿主和插件的PathList
Object suzhuPathList = getPathList(pathClassLoader);
Object chajianPathList = getPathList(dexClassLoader);
//獲得PathList的element併合並
Object newElements = mergeElements(getElements(suzhuPathList),
getElements(chajianPathList));
Log.d("Lpp", "newElements: " + Array.getLength(newElements));
//重新設置給宿主的dexElement
Field field = suzhuPathList.getClass().getDeclaredField("dexElements");
field.setAccessible(true);
field.set(suzhuPathList, newElements);
}
// 獲取DexPathList 中的dexElements
private static Object getElements(Object suzhuPathList) throws NoSuchFieldException,
IllegalAccessException {
Class cl = suzhuPathList.getClass();
Field field = cl.getDeclaredField("dexElements");
field.setAccessible(true);
return field.get(suzhuPathList);
}
private static Object mergeElements(Object elements2, Object elements1) {
Log.d("Lpp", "suzhuPathList: " + Array.getLength(elements1));
Log.d("Lpp", "chajianPathList: " + Array.getLength(elements2));
int len1 = Array.getLength(elements1);
int len2 = Array.getLength(elements2);
Object newArr = Array.newInstance(elements1.getClass().getComponentType(), len1 + len2);
for (int i = 0; i < len1; i++) {
Array.set(newArr, i, Array.get(elements1, i));
}
for (int i = len1; i < len1 + len2; i++) {
Array.set(newArr, i, Array.get(elements2, i - len1));
}
return newArr;
}
// 獲取DexPathList
private static Object getPathList(Object loader) throws ClassNotFoundException,
NoSuchFieldException, IllegalAccessException {
Class cl = Class.forName("dalvik.system.BaseDexClassLoader");
Field field = cl.getDeclaredField("pathList");
field.setAccessible(true);
return field.get(loader);
}