1. 程式人生 > >Android熱修復之微信Tinker使用初探

Android熱修復之微信Tinker使用初探

前幾天,萬眾期待的微信團隊的Android熱修復框架tinker終於在GitHub上開源了。

今天拿下來整合使用了一下,發現md上對整合使用的過程介紹的比較精簡(後來發現wiki上面倒是很詳細,需要的同學可以自己去看),這裡記錄一下我整合使用的過程。

一、整合

我這裡直接使用的是他1.6.2版本(1.6.0-1.6.3整合與使用的應該都類似,但是1.7.0以及以後,修改比較大,配置與使用略有不同自帶的sample,匯入到studio中。

二、初始化配置

首先我們需要在app/bulid.gradle中,設定tinkerId的值,很多人開始編譯就報錯,提示“tinkerId is not set!!!”,就是因為這個值沒有設定。獲取tinkerId走的

def gitSha() {
    return 'git rev-parse --short HEAD'.execute().text.trim()
}

這個方法,也就是獲取git最近一次commit的版本號,所以要是你的當前Project沒有配置git,或者當前的Project還沒有commit過,或者git沒有加入到環境變數中,會獲取不到該值。知道了原理,那解決方式就自己想了,我這裡就直接寫死,上面這個方法直接返回固定字串。

---------------------------------------------------------------------------

補充:關於獲取git提交版本號

git rev-parse --short HEAD
這段程式碼主要是用來顯示最近一次提交到遠端倉庫上revision的編號(類似於“b03b0c4”的字串。個人對git命令行了解不多,如果有知道的大神麻煩指教一下)。

所以前面說的,除了環境變數配置好git(可以在命令列輸入 git --version ,顯示出了版本號,便是配置成功),再把你的專案與git關聯起來,並且保證有一次commit記錄,才能獲取到該字串。

我在使用sample時先直接寫死了。正式應用到專案上時,可以根據自己的實際情況來配置該引數。

---------------------------------------------------------------------------------------------------

之後,我們會看到Manifest.xml中,SampleApplication.java這個類報紅找不到。這個並不影響,因為到時候我們在編譯的時候,tinker會為我們生成SampleApplication.java這個類,而起作用的程式碼就是SampleApplicationLike.java類宣告上面的

@DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",
                  flags = ShareConstants.TINKER_ENABLE_ALL,
                  loadVerifyFlag = false)

這段註解,我們也可以把這段註解註釋了,

自定定義一個SampleApplication類繼承 TinkerApplication類,然後加入無參構造方法,得到的類為:

/**
 * Created by anzyhui on 2016/9/26.
 */
public class SampleApplication extends TinkerApplication {


    public SampleApplication(){
        super(
                //tinkerFlags, which types is supported
                //dex only, library only, all support
                ShareConstants.TINKER_ENABLE_ALL,
                // This is passed as a string so the shell application does not
                // have a binary dependency on your ApplicationLifeCycle class.
                "tinker.sample.android.app.SampleApplicationLike");
    }
}
這些都是Tinker這邊定好的規矩,咱們得照著來(第二個引數中的".app"不要忘了,也就是路徑不要搞錯了),其中第一個引數表示支援修復的型別,有以下型別可以選:
    public static final int TINKER_DISABLE             = 0x00;  
    public static final int TINKER_DEX_MASK            = 0x01;  
    public static final int TINKER_NATIVE_LIBRARY_MASK = 0x02;
    public static final int TINKER_RESOURCE_MASK       = 0x04;
    public static final int TINKER_DEX_AND_LIBRARY     = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK;
    public static final int TINKER_ENABLE_ALL          = TINKER_DEX_MASK | TINKER_NATIVE_LIBRARY_MASK | TINKER_RESOURCE_MASK;
從字面理解就知道什麼意思了。

重新編譯一下,一般也不會有問題,如果報類重複的異常的話,看看你是不是之前已經生成過了SampleApplication了,在\build\intermediates\classes\debug\tinker路徑下對應的目錄裡,有的話,刪掉再編譯試下。

三、功能測試

(1)debug版本

先嚐試debug版本

現在我們模擬打包一個出現bug的版本。

再activity_main.xml中加入倆控制元件:

<TextView
    android:id="@+id/tvMessage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/showInfo"
    android:text="一切正常"    />
<Button    
    android:id="@+id/btnBug"    
    android:layout_width="wrap_content"    
    android:layout_height="wrap_content"    
    android:layout_alignParentLeft="true"    
    android:layout_alignParentStart="true"    
    android:layout_below="@+id/tvMessage"    
    android:text="點擊出現bug"/>


MainActivity中設定點選事件,點選btnBug時,tvMessage顯示"出現bug了"文字。
打包生成apk
這時,在build/bakApk目錄中,會生成一個apk和一個R.txt檔案,R.txt的作用後面再講。
apk執行至手機中,
點選點選btnBug按鈕,顯示:
好,現在來修復這個bug。
我們在activity_main.xml中刪除該“點擊出現bug”按鈕,增加“點選修復bug”按鈕:
(PS:也就是修改了res檔案
當然了MainActivity中的程式碼中,也修改了點選事件,刪除了“點擊出現bug”按鈕的點選事件,增加了“點選修復bug”按鈕的點選事件。
點選按鈕後,上方文字要顯示“bug已經修復了”。
程式碼改好後,我們需要配置一下前面提到了R.txt檔案,這裡面儲存了每次編譯得到的每個res檔案的id值。
具體配置就是,在app/build.gradle的ext部分,新增oldApk(也就是出現bug的那個apk)的資訊:
ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true
    //you should bak the following files
    //old apk file to build patch apk
    tinkerOldApkPath = "${bakPath}/app-debug-0927-11-47-21.apk"  //這個是你出現bug的apk完整名稱,app/build/bakApk目錄下
    //proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/"  //暫未開啟混淆,不用管
    //resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-debug-0927-11-47-21-R.txt"  //如果你修復了res檔案,需要指定你bug版本的R.txt檔案

}
然後,在studio的右上角,開啟gradle,找到tinkerPatchDebug(我一直用的是debug簽名,這個根據自己實際情況來)這個task,點選執行。就會在output目錄下生成一個tinkerpatch目錄,
在裡面找到patch_signed_7zip.apk和patch_signed.apk,這就是我們的差分包。
因為程式碼中指定的差分包路徑為:
loadPatchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
            }
        });
我們就直接使用patch_signed_7zip.apk,放到我們的手機根目錄,再開啟app,點選LOAD PATCH,
提示我們補丁載入成功,重啟後生效。
這裡,我們需要殺掉本程序,再進入app,才能應用補丁包修復成功。
有朋友評論說自己也載入成功但沒法修復,是不是按得返回鍵退出(返回鍵退出時是activity銷燬,程序還在)。
殺程序後再進入 ,應該就可以修復成功了,如果不成功,把補丁包逆向一下,看看自己修復的部分有沒有在裡面。
重啟後,注意看介面上的按鈕是不是已經被替換,


按鈕已經替換了,這裡我已經點選了按鈕,所以上方文字內容也提示修復成功。

(2)release版本+混淆

release版本總體使用方式與的bug版本類似,

每次打出正式包時,我們應該備份好。

在打包前,修改app/build.gradle裡的

/**
 * task type, you want to bak
 */
    //def taskName = "debug"
    def taskName = "release"

tastName為release,minifyEnabled設定為true,

這樣release打包時才會在bakApk下生成對應的apk,R.txt以及mapping檔案。

之後的打包是一樣的。

打差分包(補丁)的時候,我們要做的,同樣是,替換裡面的屬性:

ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true
    //you should bak the following files
    //old apk file to build patch apk
    tinkerOldApkPath = "${bakPath}/app-release-0929-11-28-36.apk"
    //proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-release-0929-11-28-36-mapping.txt"
    //resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-release-0929-11-28-36-R.txt"
    
}
開啟了混淆的話,就要設定bug版本release包的mapping檔案,以保證兩次對映一致。

同樣,右側gradle,點選tinkerPatchRelease。

我這次Gradle任務進行了好幾分鐘,當時還以為是出錯了,後來看了一下下圖,


因為我是第一次打release包,所以在下載必要的jar包。

有執行慢的可以點進這裡看看,是不是一樣的情況。

差分包打包成功後,跟之前一樣的操作,我這邊測試成功沒問題呢。

四、總結

之前研究過AndFix,主要是使用native方法,來修改出現bug的方法,雖然支援的系統版本也很廣(2.3~7.0),但是具有一定的侷限性,只能修改方法的內部實現,不支援新增方法,新增、修改成員變數等;而基於ClassLoader的各種實現,由於採用在位元組碼中在構造方法注入一段程式碼,防止被打上CLASS_ISPREVERIFIED標記,在dalvik中比較影響效能,而且支援系統也不全面,所以都不是特別的完美。而且他們都有一個缺點,不能修改資原始檔。

這次Tinker的釋出,打破了原有的效能問題,功能侷限,實現了,支援對library、java類、res檔案的修復,並且除了小部分版本的裝置(wiki上說是部分三星api19版本),其他基本都可以覆蓋到(yunOS另說)。相信在wx幾位大神的維護下,tinker會越來越好。官方Tinker熱補丁技術交流群:377388954