1. 程式人生 > >Android中熱修復框架Robust原理解析+並將框架程式碼從"閉源"變成"開源"(上篇)

Android中熱修復框架Robust原理解析+並將框架程式碼從"閉源"變成"開源"(上篇)

一、前言

Android中熱修復框架比較多,每家公司都有對應的方案和框架,比如阿里的AndFix框架,關於這個框架在之前的文章已經詳細講解了,不瞭解的同學可以點選這裡:AndFix熱修復框架原理分析 。本文繼續來看另外一個熱修復框架,也就是美團團隊開發的Robust框架。關於這個框架網上已經有詳細解釋了,具體用法也有。不過他沒有開源,所以本文就先簡單介紹他的原理,用一個案例來演示這個框架的作用,但是重點是咋們自己編碼將其框架機制實現,讓其"閉源"變成"開源"。

二、原理解析

關於熱修復技術點,其實雖然每家都有對應的框架,但是核心點都離不開動態載入機制。有了動態載入機制,然後就是具體修復方案問題了,對於Robust修復方案也是比較簡單的。下面來簡單看一下他的大致原理:

Robust外掛對每個產品程式碼的每個函式都在編譯打包階段自動的插入了一段程式碼,插入過程對業務開發是完全透明。如State.java的getIndex函式:

public long getIndex() {
    return 100;
}

被處理成如下的實現:

public static ChangeQuickRedirect changeQuickRedirect;
    public long getIndex() {
        if(changeQuickRedirect != null) {
            //PatchProxy中封裝了獲取當前className和methodName的邏輯,並在其內部最終呼叫了changeQuickRedirect的對應函式
            if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) {
                return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue();
            }
        }
    return 100L;
}

可以看到Robust為每個class增加了個型別為ChangeQuickRedirect的靜態成員,而在每個方法前都插入了使用changeQuickRedirect相關的邏輯,當changeQuickRedirect不為null時,可能會執行到accessDispatch從而替換掉之前老的邏輯,達到fix的目的。如果需將getIndex函式的返回值改為return 106,那麼對應生成的patch,主要包含兩個class:PatchesInfoImpl.java和StatePatch.java。

PatchesInfoImpl.java:

public class PatchesInfoImpl implements PatchesInfo {
    public List<PatchedClassInfo> getPatchedClassesInfo() {
        List<PatchedClassInfo> patchedClassesInfos = new ArrayList<PatchedClassInfo>();
        PatchedClassInfo patchedClass = new PatchedClassInfo("com.meituan.sample.d", StatePatch.class.getCanonicalName());
        patchedClassesInfos.add(patchedClass);
        return patchedClassesInfos;
    }
}

StatePatch.java:

public class StatePatch implements ChangeQuickRedirect {
    @Override
    public Object accessDispatch(String methodSignature, Object[] paramArrayOfObject) {
        String[] signature = methodSignature.split(":");
        if (TextUtils.equals(signature[1], "a")) {//long getIndex() -> a
            return 106;
        }
        return null;
    }

    @Override
    public boolean isSupport(String methodSignature, Object[] paramArrayOfObject) {
        String[] signature = methodSignature.split(":");
        if (TextUtils.equals(signature[1], "a")) {//long getIndex() -> a
            return true;
        }
        return false;
    }
}

客戶端拿到含有PatchesInfoImpl.java和StatePatch.java的patch.dex後,用DexClassLoader載入patch.dex,反射拿到PatchesInfoImpl.java這個class。拿到後,建立這個class的一個物件。然後通過這個物件的getPatchedClassesInfo函式,知道需要patch的class為com.meituan.sample.d(com.meituan.sample.State混淆後的名字),再反射得到當前執行環境中的com.meituan.sample.d class,將其中的changeQuickRedirect欄位賦值為用patch.dex中的StatePatch.java這個class new出來的物件。這就是打patch的主要過程。通過原理分析,其實Robust只是在正常的使用DexClassLoader,所以可以說這套框架是沒有相容性問題的。

下面直接上一張圖來看看人家的原理結構(點選下載檢視高清大圖):


再來看一下美團team公開的原理圖:


原理真的很簡單:直接用DexClassLoader載入修復包,然後用loadClass方法載入修復類,new出新物件,在用反射把這新的修復物件設定到指定類的changeQuickRedirect靜態變數中即可。其實這種修復類似於Java中的設計模式之靜態代理。

三、案例實踐

知道了大致原理,下面就用一個簡單的案例來執行,看到效果,這裡我們需要新建三個專案,直接拷貝之前開發外掛的那個專案即可,可以看這篇文章:Android中外掛開發原理解析,三個專案圖結構如下:


本文依然採用這種結構圖如下:


咋們這裡主要看RobustHost,RobustPatch,RobustPatchImpl這三個工程,RobustInsertCodeTools是個Java工程用於後面會重點介紹的動態插入程式碼工具。

第一、修復包介面工程RobustPatch


這個工程比較簡單,大部分都是介面定義,主要有這三個類和介面:

1、ChangeQuickRedirect介面


這個介面主要是後面修復包中每個修復類必須實現的一個介面,內部有兩個方法:一個是判斷當前方法是否支援修復,一個方法是具體的修復邏輯了。

兩個方法的引數都是一樣的:

第一個引數是方法的簽名,這個簽名的格式很簡單:方法所屬類全稱:方法名:方法是否為static型別,注意中間使用冒號進行連線的。

第二個引數是方法的引數資訊,而對於這個引數後面分析動態插入程式碼邏輯的時候會發現操作非常麻煩,才把這個引數弄到手的。

2、PatchesInfo介面


這個介面比較簡單,只有一個方法,使用者存放一個修復包中所有待修復類的具體資訊,因為一個修復包中可能有多個需要修復的類。這個介面也是後面宿主工程中動態載入需要用到的。通過這個介面獲取到指定修復的類和舊類資訊。

3、PatchedClassInfo類


這個類比較簡單,主要就是存放兩個欄位:一個是本次修復類資訊,一個是需要修復舊類資訊,讓這個類會被上面的介面用到。

第二、修復包工程

到這裡咋們就看完了修復包介面工程。下面繼續來看一下修復包工程:


修復包工程比較簡單,直接增加修復類即可,這裡可以看到,我們需要修復宿主工程中的一個MoneyBean類,這個類必須實現上面的修復包介面工程中的ChangeQuickRedirect介面,然後就是需要儲存修復類資訊和待修復舊類資訊,在PatchesInfoImpl中進行儲存返回的,而這個類實現了修復包介面工程中的PatchesInfo介面。

1、MoneyBeanStatePatch修復類


這個類看到了,實現了修復介面ChangeQuickRedirect,然後實現具體兩個方法即可。

1》在isSupport方法中,會通過方法的簽名信息得到方法名,然後判斷支援修復的方法。這裡看到的是待修復類中的getMoneyValue和desc這兩個方法需要進行修復。

2》accessDispatch方法中,主要進行了具體修復方案實行了,開始也是先通過方法簽名信息,得到方法名,然後在判斷哪些方法需要進行修復,這裡把getMoneyValue方法的返回值修復成了10000,把desc方法的返回值修復成了"Patch Desc"。

第三、宿主工程RobustHost


宿主工程最大的工作就是需要載入修復包,這個不用多解釋了,放在Application中即可。載入邏輯沒什麼可說的。這裡我們有一個MoneyBean類:


這個類的每個方法之前都加上了具備修復功能的程式碼段。比如現在如果線上的包中這個類的方法返回值出了問題,這時候就可以藉助熱修復功能。把第二個修復包工程打包好釋出出去即可。

再來看一下這裡有一個PatchProxy類:


這個類其實是對修復類進行了包裝了,內部有一個重要邏輯就是獲取當前執行方法的資訊,包括類名和方法名,這裡主要是通過方法棧資訊得到的:


然後就是宿主工程中的載入修復包邏輯了:


使用DexClassLoader進行載入修復包檔案,不多解釋了。有了載入器就開始後續的載入邏輯了。首先咋們得獲取到PatchesInfoImpl類資訊,因為這個類中儲存了修復類資訊。這裡是new出一個新物件。然後就呼叫其方法得到修復類的相信資訊,依次遍歷修復類資訊列表,得到修復類和待修復舊類資訊,依然使用反射new出新物件,再把修復類物件設定到待修復舊類的靜態變數changeQuickRedirect中即可。

四、執行效果

好了,到這裡我們就分析完了Robust熱修復框架中涉及的工程,主要就是三個工程。下面開始直接執行結果了。不過咋們還是得先弄出修復包,這個比較簡單,直接執行修復包RobustPatchImpl工程,得到apk檔案即可,本文中載入的是dex檔案,所以咋們把這個apk中的classes.dex檔案弄出來,然後在改成patch.dex放到裝置的sd卡目錄下即可。執行的時候可能會報錯:


關於這個錯誤這裡,不在多解釋了,因為咋們把修復包介面工程的程式碼也加到修復包dex中了。具體錯誤原因和解決辦法,一定要去這裡找:Android中外掛開發原理。記得是一定哦。

到這裡假設我們已經解決了所有問題,下面執行看看效果:


咋們再看看沒有修復之前的效果:


從這裡可以看到,咋們就修復方法成功了。

五、分析美團的實踐案例

不過咋們還沒有結束工作,因為美團的這個框架沒有開源,所以上面的案例並不能具備說服性。所以為了驗證我們的實現邏輯是沒有錯的,咋們需要反編譯美團的app了,看他的熱修復邏輯是什麼樣的。這裡直接使用Jadx工具開啟美團app即可:


咋們開啟app之後,可以直接全域性搜尋PatchProxy類,記得要勾選上Class,不然內容太多了。然後找到了這個類的定義:


看到這個類的實現了,其實到這裡我想告訴大家,我的案例工程中的PatchProxy類就是把這個類匯出來的,因為我懶得寫程式碼。正好這個類又沒有混淆。

咋們繼續來看他的載入程式碼邏輯,這裡有個小技巧了:在檢視一個應用中動態載入邏輯的時候,可以全域性搜尋資訊:DexClassLoader,如下結果:


其實就是美團的PatchExecutor類,點進去直接檢視:


看到這裡的核心載入邏輯,不多解釋,程式碼邏輯很簡單,大致和我們實現的差不多了。最後咋們再來看一下app中的類中的每個方法是否插入了修復程式碼段:


隨便開啟一個類,類中的每個方法之前都有這段修復程式碼。哈哈,到這裡我們就非常確定了,我們的案例實現和美團的修復框架實現邏輯大致不差了。

六、自動插入修復程式碼

其實到這裡還不算結束,因為這篇文章其實不是我想介紹Robust框架的用意,因為大家看完上面的原理之後,發現其實修復原理沒多大難度的。而我的用意是如何讓每個類中的每個方法都插入一段修復程式碼。

因為從上面我們可以看到,這個框架的最大難度是在每個類每個方法之前都必須有修復程式碼段。如果沒有那麼方法就喪失了修復功能。但是問題在於,一個龐大的專案比如美團,如何能夠保證每個開發人員在編寫一個方法的時候都需要手動加上這段修復程式碼呢?這個是不可能約束成功的。所以這個就需要藉助自動化操作了,其實在美團官方說明這個框架的時候提到依據:"插入程式碼是在編譯期自動進行的,對於開發者是透明的"。所以這句話對我的誘惑性最大,因為這句話我研究了這個框架。所以限於篇幅原因,不能在一篇文章中講解完了。從這裡可以看到,Robust框架的核心點不在這篇,而是在下一篇文章,我會帶大家手動編寫如何在編譯期中自動插入修復程式碼,無需開發人員操心。等那篇文章介紹完之後,會在詳細介紹一下美團這個熱修復框架Robust的優缺點。

特此說明

關於美團的這個Robust熱修復框架等下一篇介紹完自動插入程式碼功能之後,會統一總結一下這個框架的優缺點。還是那句話,這個框架的重點其實是自動插入程式碼模組,或許這個就是美團沒有開源的一個原因,當然也有其他原因。關於本文中實踐的案例是大致上實現了Robust框架原理,但是還是有很多細節問題需要優化的。從我的三個專案程式碼也可以看到,寫的比較粗糙。很多情況並沒有考慮到。所以如果想自己完善優化的話,可以自行操作即可。

七、總結

本文主要介紹了美團的熱修復框架Robust原理,因為他沒有開源,所以咋們就通過官方給出的原理解析,大致實現了簡單案例,為了驗證我們的案例和美團app的框架邏輯類似,咋們通過反編譯美團app,核對之後確定了咋們的實現邏輯就是和Robust實現大致相同,還有具體細節問題就不說了。但是我們在實現案例的時候會發現這個框架有一個很大的約束,就是每個類的每個方法之前必須加上一段修復程式碼,如果沒有新增該方法就失去了修復功能。當然這段程式碼是不可能約束每個開發人員在開發過程中手動新增的。官方給出的解釋是:在專案編譯階段自動新增,對於開發人員是透明的。所以這個就是我這次解析這個框架的重點,也是下一篇的重點,如何編寫工具實現自動插入修復程式碼。說到這裡我感覺挺內疚的,本來是想在這篇文章一起都講解完了,可是沒想到文章寫著寫著就內容多了,真心不能在一篇中介紹了,只能分篇介紹了。不過這個也不影響咋們後面的重點篇,希望大家能夠看完本篇之後保持著高度熱情期待下一篇內容。小編週末吸著霧霾給大家寫文章,還是希望大家能夠多多分享,要有打賞就在好不過了。

《Android應用安全防護和逆向分析》

點選立即購買:京東  天貓

更多內容:點選這裡

關注微信公眾號,最新技術乾貨實時推送

掃一掃加小編微信
新增時註明:“編碼美麗”否則不予通過!