熱更新原理總結

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);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章