溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android Muitldex熱更新修復(fù)方案原理

發(fā)布時(shí)間:2020-07-18 09:32:44 來源:網(wǎng)絡(luò) 閱讀:225 作者:Android丶VG 欄目:移動(dòng)開發(fā)
前言

做程序開發(fā),基礎(chǔ)很重要。同樣是擰螺絲人家擰出來的可以經(jīng)久不壞,你擰出來的遇到點(diǎn)風(fēng)浪就開始顫抖,可見基本功的重要性。再復(fù)雜的技術(shù),也是由一個(gè)一個(gè)簡(jiǎn)單的邏輯構(gòu)成。先了解核心基礎(chǔ),才能更好理解前沿高新技術(shù)。

正文大綱
  1. 先看效果{github Demo地址}:(https://github.com/18598925736/HotUpdateDemo)
  2. Demo使用方法
  3. Demo源碼概覽
  4. 熱修復(fù)核心技術(shù)
    • 基礎(chǔ)知識(shí)預(yù)備
    • hook思路
  5. TIPS

熱更新技術(shù),不是新話題。目前最熱門的熱更新由兩種,一種是騰訊tinker為代表的 需重啟app的熱更新,一種是美團(tuán)app為代表的instant Run,無需重啟app. 今天先探究 前者的核心原理。

先看效果[github Demo地址] :(https://github.com/18598925736/HotUpdateDemo)
假如說這是我們的app界面,這個(gè)界面有個(gè)bug,我們直接用一個(gè) TextView來表示
Android Muitldex熱更新修復(fù)方案原理
然而,我們的開發(fā)人員發(fā)現(xiàn)了這個(gè)bug,但是產(chǎn)品已經(jīng)上線。這時(shí)候,由于引起bug的 代碼,只有一行,

public  class  MainActivity extends AppCompatActivity {

     @Override
     protected void onCreate(Bundle savedInstanceStata) {
           super.onCreate(savedINstanceState);
           srtContentView(R.layout.activity_main);

           TextView textView = findViewById(R.id.tv);
           Bug bug = new Bug():
           String s = bug.getstr():
           textView.setText(s):
     }
}

Android Muitldex熱更新修復(fù)方案原理
Android Muitldex熱更新修復(fù)方案原理
這個(gè)時(shí)候,機(jī)智的程序員用最快的方式修復(fù)了這個(gè)bug,也只是改了一行代碼:
Android Muitldex熱更新修復(fù)方案原理

那么,產(chǎn)品已經(jīng)在線上,怎么辦?我們通過后臺(tái),向app推送了一個(gè) fix.dex文件, 等這個(gè)文件下載完成,app提示用戶,發(fā)現(xiàn)新的更新,需要重啟app. 待用戶重啟,代碼修復(fù) 即會(huì)生效。無需發(fā)布新版本!
Android Muitldex熱更新修復(fù)方案原理

Demo使用方法

下載Demo代碼之后,會(huì)在assets下看到一個(gè)fix.dex文件
Android Muitldex熱更新修復(fù)方案原理
按照正常的邏輯,我們做bug修復(fù)一定是把fix.dex放到服務(wù)器上, app去服務(wù)器下載它,然后存放在app私有目錄,重啟app之后,fix.dex生效, 當(dāng)加載到這個(gè)類的時(shí)候,就會(huì)去讀fix.dex中當(dāng)時(shí)打包的已修復(fù)bug的類. 但是,我這里為了演示方便,直接放在assets,然后使用 項(xiàng)目中的 AssetsFileUtil類 用io流將它讀寫到 app私有目錄下.

演示方法:

  1. 刪掉 fix.dex ,運(yùn)行app,你看到 手機(jī)屏幕中心 出現(xiàn):"臥槽,有bug!"
  2. 還原 fix.dex ,運(yùn)行app,你看到 手機(jī)屏幕中心 出現(xiàn):"嘿嘿,bug已修復(fù)"

起作用的是誰?就是這個(gè)fix.dex文件.

Demo源碼概覽

Android Muitldex熱更新修復(fù)方案原理
如上圖所示: 核心類其實(shí)就只有一個(gè): ClassLoaderHookHelper ,它 就是 讓 fix.dex這個(gè)補(bǔ)丁發(fā)揮作用的 " 幕后大佬". 這個(gè)核心類:有3個(gè)方法,分別是在不同的系統(tǒng)版本上,來對(duì)源碼程序邏輯進(jìn)行 hook,提高h(yuǎn)ook的兼容性.
Android Muitldex熱更新修復(fù)方案原理
下面是完整 ClassLoaderHookHelper代碼 以及 使用它的 MyApp完整代碼 :

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class ClassLoaderHookHelper {

       //23和19的差別,就是 makeXXXElements 方法名和參數(shù)要求不同
      //后者是 makeDexElements(ArrayList<File> files, File optimizedDirectory,ArrayList<IOException> suppressedExceptions)
     //前者是 makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions)
     public static void hookV23(ClassLoader classLoader,File outDexFilePath,File optimizedDirectory)throws IllegalAccessException, InvocationTargetException {
     Field pathList =ReflectionUtil.getField(classLoader,"pathList");//1、獲DexPathList pathList 屬性
     object dexpathListobj =pathList.get(classLoader);//2、獲DexPathList pathList對(duì)象
     Field dexElementsField =ReflectionUtil.getField(dexPathListObj, "dexElements");//3、獲得DexPathList的dexElements屬性

     Object[] oldElements =(Object[]) dexElementsField.get(dexPathListObj);//4、獲得pathList對(duì)象中 dexElements 的屬性值
     ...

   }
}
Multidex熱修復(fù)核心技術(shù)

其實(shí) 熱修復(fù)的核心技術(shù),就一句話, HookClassLoader ,但是要深入了解它,需要相當(dāng)多的基礎(chǔ)知識(shí),下面列舉出必須要知道的一些東西。

基礎(chǔ)知識(shí)預(yù)備
1.Dex文件是什么?

我們寫安卓,目前還是用 java比較多,就算是用 kotlin,它最終也是要轉(zhuǎn)換成 java來運(yùn)行。 java文件,被編譯成 class之后,多個(gè) class文件,會(huì)被打包成 classes.dex,被放到 apk中,安卓設(shè)備拿到 apk,去安裝解析( 預(yù)編譯balabala...),當(dāng)我們運(yùn)行 app時(shí), app的程序邏輯全都是在classes.dex中。所以, dex文件是什么?一句話, dex文件是 android app的源代碼的最終打包

2.Dex文件如何生成?

androidStudio 打包 apk的時(shí)候會(huì)生成 Dex,其實(shí)它使用的是 SDK的 dx命令,我們可以用 dx命令自己去打包想要打包的 class. 命令格式為:dx --dex --output=output.dex xxxx.class 將上面的output 和 xxxx換成你想要的文件名即可。

注:dx.bat在 安卓 SDK的目錄下:比如我d的`C:\XXXXX\AndroidStudioAbout\sdk1\build-tools\28.0.3\dx.bat

3.ClassLoader是什么?

ClassLoader來自 jdk,翻譯為 :類加載器,用于將 class文件中的類,加載到內(nèi)存中,生成 class對(duì)象。只有存在了 Class對(duì)象,我們才可以創(chuàng)建我們想要的對(duì)象。 android SDK繼承了JDKclassLoader,創(chuàng)造出了新的 ClassLoader子類。下圖表示了 android9.0-28 所有的ClassLoader直接或者間接子類.
Android Muitldex熱更新修復(fù)方案原理
比較多的是 BaseDexClassLoader, DexClassLoader , PathClassLoader, 其他這些,應(yīng)該是谷歌大佬 創(chuàng)造出來新的 類加載器子類吧,還沒研究過。

注: 關(guān)于 DexClassLoaderPathClassLoader ,網(wǎng)上資料有個(gè)誤區(qū),應(yīng)該不少人都認(rèn)為, PathClassLoader 用于加載 app內(nèi)部的 dex文件, DexClassLoader用于加載外部的 dex文件,但是其實(shí)只要看一眼 這兩個(gè)類的關(guān)系,就會(huì)發(fā)現(xiàn),它們都是繼承自 BaseDexClassLoader,他們的構(gòu)造函數(shù)內(nèi)部都會(huì)去執(zhí)行父類的構(gòu)造函數(shù)。他們只有一個(gè)差別,那就是 PathClssLoader不用傳 optimizedDirectory這個(gè)參數(shù),但是 DexClassLoader必須傳。這個(gè)參數(shù)的作用是,傳入一個(gè) dex優(yōu)化之后的存放目錄。而事實(shí)上,雖然 PathClassLoader不要求傳這個(gè) optimizedDirectory,但是它實(shí)際上是給了一個(gè)默認(rèn)值。emmmm............所以不要再認(rèn)為 PathClassLoader不能加載外部的 dex了,它只是沒有讓你傳 optimizedDirectory而已。

另外: BootClassLoader用于加載 AndroidFramework層class文件( SDK中沒有這個(gè)BootClassLoader,也是很奇怪) PathClassLoader 是用于Android應(yīng)用程序類的加載器,可以加載指定的 dex,以及 jar、 zip、 apk中的 classes.dex。 DexClassLoader 可以加載指定的 dex,以及 jar、 zip、 apk中的 classes.dex。

4.ClassLoader的雙親委托機(jī)制是什么?

android里面 ClassLoader的作用,是將 dex文件中的類,加載到內(nèi)存中,生成 Class對(duì)象,供我們使用 (舉個(gè)例子:我寫了一個(gè) A類,app運(yùn)行起來之后,當(dāng)我需要new 一個(gè) A, ClassLoader首先會(huì)幫我查找 A的 Class對(duì)象是否存在,如果存在,就直接給我 Class對(duì)象,讓我拿去 new A,如果不存在,就會(huì)出創(chuàng)建這個(gè) A的 Class對(duì)象。) 這個(gè)查找的過程,就遵循 雙親委托機(jī)制。一句話解釋 雙親委托機(jī)制:某個(gè) 類加載器在加載某個(gè) 類的時(shí)候,首先會(huì)將 這件事委托給 parent類加載器,依次遞歸,如果 parent類加載器可以完成加載,就會(huì)直接返回 Class對(duì)象。如果 parent找不到或者沒有父了,就會(huì) 自己加載。

下圖是 安卓源碼 ClassLoader.java:
Android Muitldex熱更新修復(fù)方案原理
紅字注解,很容易讀懂 ClassLoader去 load一個(gè) class的過程.

hook思路

OK,現(xiàn)在可以來解讀我是如何去hook ClassLoader的了. 解讀之前,先弄清楚,我為何 要 hookClassLoader,為什么 hook了它之后,我的 fix.dex就能發(fā)揮作用?先解決這個(gè)疑問,既然是 hook,那么自然要讀懂源碼,因?yàn)?hook就是在理解源碼思維的前提下,更改源碼邏輯。 一張圖解決你的疑問:
Android Muitldex熱更新修復(fù)方案原理

按照上面圖,去追蹤源碼,會(huì)發(fā)現(xiàn), ClassLoader最終會(huì)從 DexFile對(duì)象中去獲得一個(gè) Class對(duì)象。并且在 DexPathList類中 findClass的時(shí)候,存在一個(gè) Element數(shù)組的遍歷。這就意味著,如果存在多個(gè) dex文件,多個(gè) dex文件中都存在同樣一個(gè) class,那么它會(huì)從第一個(gè)開始找,如果找到了,就會(huì)立即返回。如果沒找到,就往下一個(gè)dex去找。

也就是說,如果我們可以在 這個(gè)數(shù)組中插入我們自己的修復(fù)bug的 fix.dex,那我們就可以讓我們 已經(jīng)修復(fù)bug的補(bǔ)丁類發(fā)揮作用,讓類加載器優(yōu)先讀取我們的 補(bǔ)丁類.

OK,理解了源碼的邏輯,那我們可以動(dòng)手了。來解析SDK 23的 hookClassLoader過程吧!

確定思路,我們要改變app啟動(dòng)之后,自帶的ClassLoader對(duì)象(具體實(shí)現(xiàn)類是PathClassLoader )中 DexPathList 中 Element[] element 的實(shí)際值。

那么,步驟:

1.取得PathClassLoaderpathList的屬性
2.取得PathClassLoaderpathList的屬性真實(shí)值(得到一個(gè)DexPathList對(duì)象)
3.獲得DexPathList中的dexElements 屬性
4.獲得DexPathList對(duì)象中dexElements 屬性的真實(shí)值(它是一個(gè)Element數(shù)組) 做完這4個(gè)步驟,我們得到下面的代碼
Android Muitldex熱更新修復(fù)方案原理
5.用外部傳入的Dex文件路徑,構(gòu)建一個(gè)我們自己的Element數(shù)組
Android Muitldex熱更新修復(fù)方案原理
6.將從外部傳入的ClassLoader中得到的Element數(shù)組和 我們自己的Element數(shù)組合并起來, 注意,我們自己的數(shù)組元素要放前面!
Android Muitldex熱更新修復(fù)方案原理
7.將剛才合并的新Element數(shù)組,設(shè)置到 外部傳入的ClassLoader里面去。
Android Muitldex熱更新修復(fù)方案原理

OK,收官!

TIPS

上面的內(nèi)容,讀起來可能會(huì)有一些疑問,我預(yù)估到了一些,將答案寫在下面

1. 當(dāng)我們需要反射獲得一個(gè)類的某個(gè)方法或者成員變量時(shí),我們只想拿getDeclareXX,因?yàn)槲覀冎幌肽帽绢愔械某蓡T,但是僅僅getDeclareXX不能跨越繼承關(guān)系 拿到 父類中的非私有成員,所以我寫了ReflectionUtil.java,支持跨越繼承關(guān)系 拿到父類的非私有成員。
2. 這種熱修復(fù),是不是下載的包會(huì)很大,和原先的apk差不多大?答案是,NO,我們只需要將我們修復(fù)bug之后的補(bǔ)丁dex下載到設(shè)備,讓app重啟,去讀取這個(gè)dex即可。補(bǔ)丁包很小,甚至只有1K.
3. 這種修復(fù)方式必須重啟么? 是的,必須重啟,當(dāng)然,存在不需要重啟就可以修復(fù)bug的方法,那種方法叫做instant run方案,本文不涉及。而,當(dāng)前這種方案叫做:MultipleDex 即,多dex方案。
*4.** 為什么要對(duì)SDK 23 ,19,14 寫不同的hook代碼?因?yàn)?code>SDK版本的變遷,導(dǎo)致 一些類的關(guān)系,變量名,方法名,方法參數(shù)(個(gè)數(shù)和類型)都會(huì)發(fā)生變化,所以,要針對(duì)各個(gè)變遷的版本進(jìn)行兼容。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI