當前位置:
首頁 > 最新 > 手動實現Android熱修復

手動實現Android熱修復

作者:MinuitZ

https://www.jianshu.com/p/9e67e3eb129b

由作者授權並原創首發

周一發布了新版本,當天晚上用戶就為app未測試到的bug發飆了,恩,很快就找到了問題所在,一個容易疏忽的空指針。雖然只是一個小小的bug但是不修復是很影響用戶體驗的啊,如果要重新修復上線,波及範圍太廣了,所有用戶又要重新下載。

我們可以讓這個bug「偷偷」的修復

類載入由ClassLoader的實現類完成。玩過反編譯的都知道,我們在解壓了apk之後,最終會需要dex格式的文件來搞事,這個dex由class文件打包而成。那麼安卓中,要載入dex文件中的class文件,需要用到DexClassLoader或者PathClassLoader。

我們可以直接在AS中點開,但是卻無法正常查看,因為這些是系統級的源碼。我們可以選擇下載源碼,或者直接在http://androidxref.com/中找一找。

1.1先來看看類載入器

PathClassLoader 可以載入Android系統中的dex文件

DexClassLoader 可以載入任意目錄的dex/zip/apk/jar文件 , 但是要指定optimizedDirectory.

這兩個類載入器都繼承BaseDexClassLoader, 並且在構造函數中, DexClassLoader多傳入了一個optimizedDirectory, 這一點先暫記一下

看一下BaseDexClassLoader的構造方法:

構造方法中初始化了pathList, 傳入三個參數 , 分別為

dexPath:目標文件路徑(一般是dex文件,也可以是jar/apk/zip文件)所在目錄。熱修復時用來指定新的dex

optimizedDirectory:dex文件的輸出目錄(因為在載入jar/apk/zip等壓縮格式的程序文件時會解壓出其中的dex文件,該目錄就是專門用於存放這些被解壓出來的dex文件的)。

libraryPath:載入程序文件時需要用到的庫路徑。

parent:父載入器

1.2 載入類的過程

在BaseDexClassLoader中 , 緊接著構造函數的是一個叫findClass的方法 , 這個方法用來載入dex文件中對應的class文件.

大體上不難理解, 拿到初始化完成的 pathList 之後 , 根據類名找出相應的class位元組碼文件, 如果沒有異常直接返回class.

接下來我們繼續跟進pathList

1.3 DexPathList

DexPathList 源碼在這裡

好了, 點開源碼不要慌 , 我們目前只需要知道兩個東西:

構造函數. 我們在BaseDexClassLoader中實例化DexPathList需要用到

findClass方法, 在BaseDexClassLoader的findClass中, 本質調用了DexpathList的fndClass方法.

其他的方法姑且不用關心.

1->構造函數

首先 , 將傳入的classLoader保存起來 , 接下來使用makePathElements方法 ,來初始化Element數組 .

那接下來無疑是分析makeDexElements()方法了,因為這部分代碼比較長,引用一下大神的分析:

總體來說,DexPathList的構造函數是將一個個的目標(可能是dex、apk、jar、zip , 這些類型在一開始時就定義好了)封裝成一個個Element對象,最後添加到Element集合中。

其實,Android的類載入器(不管是PathClassLoader,還是DexClassLoader),它們最後只認dex文件,而loadDexFile()是載入dex文件的核心方法,可以從jar、apk、zip中提取出dex,但這裡先不分析了,因為第1個目標已經完成,等到後面再來分析吧。

2->findClass方法

在DexPathList的構造函數中已經初始化了dexElements,所以這個方法就很好理解了,只是對Element數組進行遍歷,一旦找到類名與name相同的類時,就直接返回這個class,找不到則返回null。

為什麼是調用DexFile的loadClassBinaryName()方法來載入class?這是因為一個Element對象對應一個dex文件,而一個dex文件則包含多個class。也就是說Element數組中存放的是一個個的dex文件,而不是class文件!!!這可以從Element這個類的源碼和dex文件的內部結構看出。

載入class會使用BaseDexClassLoader,在載入時,會遍歷文件下的element,並從element中獲取dex文件

方案 ,class文件在dex裡面 , 找到dex的方法是遍曆數組 , 那麼熱修復的原理, 就是將改好bug的dex文件放進集合的頭部, 這樣遍歷時會首先遍歷修復好的dex並找到修復好的類 . 這樣 , 我們就能在沒有發布新版本的情況下 , 修改現有的bug。雖然我們無法改變現有的dex文件,但是遍歷的順序是從前往後的,在舊dex中的目標class是沒有機會上場的。


在了解了大致的熱修復過程之後,我們要準備好以下幾個東西:

帶有bug的apk,並且可以獲取到dex文件來修復

已修復bug的dex文件

因為修復工作是需要隱秘的進行的 , 畢竟有bug也不是什麼光彩的事兒 , 所以我吧dex的插入操作放在Splash界面中. 在Splash時先檢測有沒有dex文件, 如果有則進行插入 , 否則直接進入MainActivity.

1->寫一個有bug的程序

哇, 是不是第一次見到這麼爽的需求~

首先在MainActivty中寫一個bug出來:

運行這段代碼必然會報錯的 , 但是我們要首先吧這段代碼裝到手機上 , 方便之後的修復.

接下來編寫SplashActivity以及工具類 . 大家可以根據具體邏輯修改

接下來 , 我們在Splash中進行檢測以及修復工作

接下來 , 在As中一定一定要把instance run取消勾選,因為instance run用到的原理也是熱修復的原理,也就是在重新運行app時不會完整的安裝,只會安裝你修改過的代碼。

編譯運行:

恩 , 接下來我們要修復bug,並且將修復好的包放進sd卡裡面,這樣在Splash開始時就會自動遍歷到dex。

2->編寫修復好的dex

定位一下bug是出現在BugTest 中 , 所以我們首先修復bug

然後將class文件打包成dex文件

首先點擊Build->Rebuild Project 來重新構建, 構建完成之後, 可以在app / build / interintermediate / debug / 包名/ 找到你剛剛修改的class文件 , 將他拷貝出來

注意 , 拷貝出來要連同包名一起, 像這樣

因為在dex中的class文件是包名.類名的形式 , 所以我們在做dex文件時, 也要講相對應的包名加上 . 這裡反編譯一個demo作為例子:

反編譯class文件,挑出一個類

接下來就要生成dex文件了

要將class文件打包成dex文件,就需要用到dx指令,這個dx指令類似於java指令。dx指令也需要有程序來提供,它就在Android SDK的build-tools目錄下各個Android版本目錄之中。

接下來使用指令來編譯, shift+右擊 打開命令行 , 輸入指令:

具體的語法大家手動dx --help自己看一下 , 輸入如下指令後 , 我桌面的dex文件下, 與剛剛拷貝的文件夾平級會出現一個classes.dex的文件。

接下來將dex文件拷貝到sd卡下面 , 當然如果是真實項目去下載的話 , 當然是要下載到特定目錄了

至此, 在Splash界面的檢測時會見到到目標的dex文件, 返回true , 會開始進行熱修復(拼接Element數組)的操作, 再次進入到主界面當然就不會報錯了.

那麼, 出錯的那個class去哪裡了??? 它還在整個Elements的集合中的某一個dex中, 只不過沒有機會調用到了而已。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 劉望舒 的精彩文章:

TAG:劉望舒 |