Android Muitldex熱更新修復方案原理

前言

作程序開發,基礎很重要。一樣是擰螺絲人家擰出來的能夠經久不壞,你擰出來的遇到點風浪就開始顫抖,可見基本功的重要性。再複雜的技術,也是由一個一個簡單的邏輯構成。先了解核心基礎,才能更好理解前沿高新技術。

(更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。)
能夠點擊關於我聯繫我獲取完整PDF和麪試準備路線
(VX:mm14525201314)java

正文大綱

  1. 先看效果{github Demo地址}:(https://github.com/1859892573...
  2. Demo使用方法
  3. Demo源碼概覽
  4. 熱修復核心技術
  • 基礎知識預備
  • hook思路
  1. TIPS

熱更新技術,不是新話題。目前最熱門的熱更新由兩種,一種是騰訊tinker爲表明的 需重啓app的熱更新,一種是美團app爲表明的instant Run,無需重啓app. 今天先探究 前者的核心原理。android

先看效果[github Demo地址] :(https://github.com/1859892573...
假如說這是咱們的app界面,這個界面有個bug,咱們直接用一個 TextView來表示

然而,咱們的開發人員發現了這個bug,可是產品已經上線。這時候,因爲引發bug的 代碼,只有一行,git

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):
     }
}



這個時候,機智的程序員用最快的方式修復了這個bug,也只是改了一行代碼:
程序員

那麼,產品已經在線上,怎麼辦?咱們經過後臺,向app推送了一個 fix.dex文件, 等這個文件下載完成,app提示用戶,發現新的更新,須要重啓app. 待用戶重啓,代碼修復 即會生效。無需發佈新版本!
github

Demo使用方法面試

下載Demo代碼以後,會在assets下看到一個fix.dex文件

按照正常的邏輯,咱們作bug修復必定是把fix.dex放到服務器上, app去服務器下載它,而後存放在app私有目錄,重啓app以後,fix.dex生效, 當加載到這個類的時候,就會去讀fix.dex中當時打包的已修復bug的類. 可是,我這裏爲了演示方便,直接放在assets,而後使用 項目中的 AssetsFileUtil類 用io流將它讀寫到 app私有目錄下.數組

演示方法:服務器

  1. 刪掉 fix.dex ,運行app,你看到 手機屏幕中心 出現:"臥槽,有bug!"
  2. 還原 fix.dex ,運行app,你看到 手機屏幕中心 出現:"嘿嘿,bug已修復"

起做用的是誰?就是這個fix.dex文件.app

Demo源碼概覽


如上圖所示: 核心類其實就只有一個: ClassLoaderHookHelper ,它 就是 讓 fix.dex這個補丁發揮做用的 " 幕後大佬". 這個核心類:有3個方法,分別是在不一樣的系統版本上,來對源碼程序邏輯進行 hook,提升hook的兼容性.

下面是完整 ClassLoaderHookHelper代碼 以及 使用它的 MyApp完整代碼 :ide

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 方法名和參數要求不一樣
      //後者是 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");//一、獲DexPathList pathList 屬性
     object dexpathListobj =pathList.get(classLoader);//二、獲DexPathList pathList對象
     Field dexElementsField =ReflectionUtil.getField(dexPathListObj, "dexElements");//三、得到DexPathList的dexElements屬性
     
     Object[] oldElements =(Object[]) dexElementsField.get(dexPathListObj);//四、得到pathList對象中 dexElements 的屬性值
     ...

   }
}

Multidex熱修復核心技術

其實 熱修復的核心技術,就一句話, HookClassLoader ,可是要深刻了解它,須要至關多的基礎知識,下面列舉出必需要知道的一些東西。

基礎知識預備

1.Dex文件是什麼?

咱們寫安卓,目前仍是用 java比較多,就算是用 kotlin,它最終也是要轉換成 java來運行。 java文件,被編譯成 class以後,多個 class文件,會被打包成 classes.dex,被放到 apk中,安卓設備拿到 apk,去安裝解析( 預編譯balabala...),當咱們運行 app時, app的程序邏輯全都是在classes.dex中。因此, dex文件是什麼?一句話, dex文件是 android app的源代碼的最終打包

2.Dex文件如何生成?

androidStudio 打包 apk的時候會生成 Dex,其實它使用的是 SDK的 dx命令,咱們能夠用 dx命令本身去打包想要打包的 class. 命令格式爲:dx --dex --output=output.dex xxxx.class 將上面的output 和 xxxx換成你想要的文件名便可。

注:dx.bat在 安卓 SDK的目錄下:好比我d的`C:XXXXXAndroidStudioAboutsdk1build-tools28.0.3dx.bat

3.ClassLoader是什麼?

ClassLoader來自 jdk,翻譯爲 :類加載器,用於將 class文件中的類,加載到內存中,生成 class對象。只有存在了 Class對象,咱們才能夠建立咱們想要的對象。 android SDK繼承了JDKclassLoader,創造出了新的 ClassLoader子類。下圖表示了 android9.0-28 全部的ClassLoader直接或者間接子類.

比較多的是 BaseDexClassLoaderDexClassLoader , PathClassLoader, 其餘這些,應該是谷歌大佬 創造出來新的 類加載器子類吧,還沒研究過。

注: 關於 DexClassLoaderPathClassLoader ,網上資料有個誤區,應該很多人都認爲, PathClassLoader 用於加載 app內部的 dex文件, DexClassLoader用於加載外部的 dex文件,可是其實只要看一眼 這兩個類的關係,就會發現,它們都是繼承自 BaseDexClassLoader,他們的構造函數內部都會去執行父類的構造函數。他們只有一個差異,那就是 PathClssLoader不用傳 optimizedDirectory這個參數,可是 DexClassLoader必須傳。這個參數的做用是,傳入一個 dex優化以後的存放目錄。而事實上,雖然 PathClassLoader不要求傳這個 optimizedDirectory,可是它其實是給了一個默認值。emmmm............因此不要再認爲 PathClassLoader不能加載外部的 dex了,它只是沒有讓你傳 optimizedDirectory而已。

另外: BootClassLoader 用於加載 AndroidFramework層class文件( SDK中沒有這個BootClassLoader,也是很奇怪) PathClassLoader 是用於Android應用程序類的加載器,能夠加載指定的 dex,以及 jar、 zip、 apk中的 classes.dexDexClassLoader 能夠加載指定的 dex,以及 jar、 zip、 apk中的 classes.dex

4.ClassLoader的雙親委託機制是什麼?

android裏面 ClassLoader的做用,是將 dex文件中的類,加載到內存中,生成 Class對象,供咱們使用 (舉個例子:我寫了一個 A類,app運行起來以後,當我須要new 一個 A, ClassLoader首先會幫我查找 A的 Class對象是否存在,若是存在,就直接給我 Class對象,讓我拿去 new A,若是不存在,就會出建立這個 A的 Class對象。) 這個查找的過程,就遵循 雙親委託機制。一句話解釋 雙親委託機制:某個 類加載器在加載某個 類的時候,首先會將 這件事委託給 parent類加載器,依次遞歸,若是 parent類加載器能夠完成加載,就會直接返回 Class對象。若是 parent找不到或者沒有父了,就會 本身加載。

下圖是 安卓源碼 ClassLoader.java:

紅字註解,很容易讀懂 ClassLoader去 load一個 class的過程.

hook思路

OK,如今能夠來解讀我是如何去hook ClassLoader的了. 解讀以前,先弄清楚,我爲什麼 要 hookClassLoader,爲何 hook了它以後,個人 fix.dex就能發揮做用?先解決這個疑問,既然是 hook,那麼天然要讀懂源碼,由於 hook就是在理解源碼思惟的前提下,更改源碼邏輯。 一張圖解決你的疑問:

按照上面圖,去追蹤源碼,會發現, ClassLoader最終會從 DexFile對象中去得到一個 Class對象。而且在 DexPathList類中 findClass的時候,存在一個 Element數組的遍歷。這就意味着,若是存在多個 dex文件,多個 dex文件中都存在一樣一個 class,那麼它會從第一個開始找,若是找到了,就會當即返回。若是沒找到,就往下一個 dex去找。

也就是說,若是咱們能夠在 這個數組中插入咱們本身的修復bug的 fix.dex,那咱們就可讓咱們 已經修復bug的補丁類發揮做用,讓類加載器優先讀取咱們的 補丁類.

OK,理解了源碼的邏輯,那咱們能夠動手了。來解析SDK 23的 hookClassLoader過程吧!

肯定思路,咱們要改變app啓動以後,自帶的ClassLoader對象(具體實現類是PathClassLoader )中 DexPathList 中 Element[] element 的實際值。

那麼,步驟:

1.取得 PathClassLoaderpathList的屬性
2.取得 PathClassLoaderpathList的屬性真實值(獲得一個 DexPathList對象)
3.得到 DexPathList中的 dexElements 屬性
4.得到 DexPathList對象中 dexElements 屬性的真實值(它是一個Element數組) 作完這4個步驟,咱們獲得下面的代碼

5.用外部傳入的 Dex文件路徑,構建一個咱們本身的Element數組

6.將從外部傳入的 ClassLoader中獲得的Element數組和 咱們本身的Element數組合並起來, 注意,咱們本身的數組元素要放前面!

7.將剛纔合併的新Element數組,設置到 外部傳入的ClassLoader裏面去。

OK,收官!

TIPS

上面的內容,讀起來可能會有一些疑問,我預估到了一些,將答案寫在下面

1. 當咱們須要反射得到一個類的某個方法或者成員變量時,咱們只想拿 getDeclareXX,由於咱們只想拿本類中的成員,可是僅僅 getDeclareXX不能跨越繼承關係 拿到 父類中的非私有成員,因此我寫了 ReflectionUtil.java,支持跨越繼承關係 拿到父類的非私有成員。
2. 這種熱修復,是否是下載的包會很大,和原先的 apk差很少大?答案是,NO,咱們只須要將咱們修復bug以後的補丁 dex下載到設備,讓app重啓,去讀取這個 dex便可。補丁包很小,甚至只有1K.
3. 這種修復方式必須重啓麼? 是的,必須重啓,固然,存在不須要重啓就能夠修復bug的方法,那種方法叫作instant run方案,本文不涉及。而,當前這種方案叫作: MultipleDex 即,多 dex方案。
4.* 爲何要對 SDK 23 ,19,14 寫不一樣的hook代碼?由於 SDK版本的變遷,致使 一些類的關係,變量名,方法名,方法參數(個數和類型)都會發生變化,因此,要針對各個變遷的版本進行兼容。

請查看完整的PDF版
(更多完整項目下載。未完待續。源碼。圖文知識後續上傳github。)
能夠點擊關於我聯繫我獲取完整PDF
(VX:mm14525201314)

相關文章
相關標籤/搜索