Android熱更新

一、什么是熱更新

簡單來講熱更新就是線上版本出了大問題,不用重新發(fā)版本,只需要打個補丁包,客戶端進行下載整頓即可修復那個問題。

二、熱修復與插件化

1.插件化的內容是原來的app中沒有的東西,而熱更新是將原有的東西做了改動
2.插件化在代碼中有固定的入口,而熱更新則可能改變任何一個位置的代碼

三、熱更新原理

1.虛擬機在加載class時會通過雙親委托機制去加載一個類,先從自己的緩存中看看能不能找到該class,不能找到則先讓父類去加載,如果父類找不到再讓子類去加載。

2.通過查看ClassLoader源碼,findClass方法是由每個子類自己實現(xiàn)的,比如 BootClassLoader或者BaseDexClassLoader。而PathClassLoader和DexClassLoader是繼承自BaseDexClassLoader的,它的findClass也是在BaseDexClassLoader里面實現(xiàn)的。在安卓中,我們平時在使用時遇到的那些類,基本都是由PathClassLoader來進行加載的。
3.BaseDexClassLoader的findClass里面使用了另一個對象DexPathList去查找對應的class,這是安卓里面特有的實現(xiàn)。在DexPathList對象里面有一個屬性dexElements,dexElements是用于存放加載好了的dex數(shù)組的,查找class是從這個數(shù)組里面從前往后找的
4.dexElements里面存放的是Element對象,findClass最終會交給Element去實現(xiàn),Element又會交給Element里面的一個屬性DexFile去實現(xiàn)。最終是交給native方法來實現(xiàn)的
5.回到上面的DexPathList對象從dexElements數(shù)組里面查找class,從數(shù)組的前面往后面找,找到了就返回結果,不再繼續(xù)查找
6.我們修復好了bug的時候,把那些有改動的java源碼編譯成class,再打包成dex,然后通過反射技術放到dexElements數(shù)組的最前面,這樣系統(tǒng)在通過PathClassLoader找class時,首先找到的是我們放在最前面的修復好了的class,然后就不會再往后面找了,相當于實現(xiàn)了熱修復。這樣有bug的class就不會被用到了。
7.其中上面一步的反射流程:獲取到PathClassLoader,然后反射獲取父類中的DexPathList對象,然后再反射到DexPathList對象中的dexElements數(shù)組。然后將補丁dex轉為Element對象,插入到dexElements數(shù)組的最前面(先復制出來,再合并,再通過反射放回去)

一句話小結:將修復后的類通過反射技術放到dexElements的最前面,從而讓系統(tǒng)在加載類的時候優(yōu)先加載到已修復的class,從而達到熱修復

四、熱更新代碼實現(xiàn)

下面是核心代碼

fun loadPatch() {
    val appContext = App.getAppContext()
    //HotFixUtil::class.java.classLoader 即 PathClassLoader,默認的類加載器
    //1. PathClassLoader->BaseDexClassLoader 拿到當前程序的dexElements數(shù)組
    val oldPathList = FieldUtil.getFieldValue(BaseDexClassLoader::class.java, HotFixUtil::class.java.classLoader, "pathList")
    val DexPathListClazz = Class.forName("dalvik.system.DexPathList")
    val oldDexElements = FieldUtil.getFieldValue(DexPathListClazz, oldPathList, "dexElements")

    //2. 補丁包  構建一個DexClassLoader 用來加載補丁包里面的class
    //我這里是模擬從網上下載的過程,我直接將補丁放assets目錄下了
    val inputStream = appContext.assets.open("hotfix.dex")
    val file = File("${appContext.cacheDir}/hotfix.dex")
    val outputStream = FileOutputStream(file)
    inputStream.copyTo(outputStream)
    val dexClassLoader = DexClassLoader(file.path, appContext.cacheDir.path, null, HotFixUtil::class.java.classLoader)
    val newPathList = FieldUtil.getFieldValue(BaseDexClassLoader::class.java, dexClassLoader, "pathList")
    val newDexElements = FieldUtil.getFieldValue(DexPathListClazz, newPathList, "dexElements")

    //3. 合并2個數(shù)組
    val combineArray = InsertDexUtils.combineArray(newDexElements, oldDexElements)

    //4. 將合并好的數(shù)組放進當前程序的BaseDexClassLoader的dexElements數(shù)組中,這樣在找class時就先找到補丁里面的class,熱修復成功
    FieldUtil.setField(DexPathListClazz, oldPathList!!, "dexElements", combineArray)
}

五、熱更番外篇

除了我們常見讓系統(tǒng)優(yōu)先加載修改后class文件,美團熱更框架robust,另辟蹊徑,使用字節(jié)碼插磚方法,

  1. 給每個class插入一個public static的ChangeQuickRedirect對象,
  2. 當該方法的changeQuickRedirect不為空時,直接將參數(shù)直接傳入PatchProxy的accessDispatchVoid/accessDispatch方法并返回,這樣做跳過了原方法后面的代碼,執(zhí)行修改后的方法,達到熱更作用
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容