Android AndFix熱補丁動態修復框架使用教程

簡介

已經上線的項目發現BUG,緊急修復BUG發佈新版本?No,也許你需要AndFix。

AndFix 是阿里巴巴開源的 Android 應用熱修復工具,幫助 Anroid 開發者修復應用的線上問題。Andfix 是 “Android hot-fix” 的縮寫。支持 Android 2.3 - 6.0,ARM 和 x86 架構,dalvik 運行時和 art 運行時。AndFix 的分支是 .apatch 文件。



GitHub地址 : https://github.com/alibaba/AndFix

原理圖:

原理圖

使用概述

1. 添加依賴

dependencies {
    compile 'com.alipay.euler:andfix:0.3.1@aar'
}

2 . 儘可能早的加載補丁

package com.dyk.andfixtest;

import android.app.Application;
import android.content.pm.PackageManager;

import com.alipay.euler.andfix.patch.PatchManager;

/**
 * Created by dyk on 2016/3/24.
 */
public class MyApplication extends Application {

    private static MyApplication instance;
    private PatchManager patchManager;

    public static MyApplication getInstance(){
        return instance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        // 初始化patch管理類
        patchManager = new PatchManager(this);
        String appVersion = null;
        try {
            appVersion = getPackageManager().getPackageInfo(getPackageName(),0).versionName;
            // 初始化patch版本
            patchManager.init(appVersion);
            // 加載已經添加到PatchManager中的patch
            patchManager.loadPatch();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

    }

    public PatchManager getPatchManager() {
        return patchManager;
    }
}

每次appVersion變更都會導致所有補丁被刪除,如果appversion沒有改變,則會加載已經保存的所有補丁。

3. 加載新補丁

MyApplication.getInstance().getPatchManager().addPatch(patchPath);

詳細使用教程

1 . 添加依賴

2. 儘可能早的加載補丁

3. 修復bug,生成沒有bug的apk文件

4. 對比新舊apk生成.apatch補丁文件

5. 加載新補丁,修復bug

前兩步在使用概述中已經說明,不在贅述。修復bug,生成新apk也和正常一樣。這裏注意一個地方:生成新舊apk要使用同一個.jks簽名文件。下面是一個示例

從一個Demo開始

製造bug

下面的代碼假設在waitFix()裏存在bug(Log.i(TAG, “waitFix bug”),我們目標是不發佈新版本,改變這行log)

package com.dyk.andfixtest;

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.Window;

import java.io.IOException;

public class MainActivity extends Activity {

    private static final String TAG = "AndFix";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        findViewById(R.id.showLogBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                waitFix();
            }
        });

        findViewById(R.id.fixBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String patchPath = Environment.getExternalStorageDirectory()+"/tmp/first.apatch";
                try {
                    MyApplication.getInstance().getPatchManager().addPatch(patchPath);
                    Log.i(TAG,"fix bug, please reClick");
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.i(TAG,"error:"+e.toString());
                }
            }
        });

    }

    // 假設待修復的Bug在此方法中
    private void waitFix() {
        Log.i(TAG,"waitFix bug");
    }
}

因爲要讀取.apatch文件,不要忘了添加限權。

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

此時我們簽名運行apk,生成bug.apk。

// 已修復
private void waitFix() {
     Log.i(TAG,"fix bug");
}

修改bug後生成fix.apk。

生成.aptch補丁文件需要一個工具,點我下載。現在打開命令行(cmd),進入從剛下載工具的tools文件夾(我的是D:\Temp\AndFix\AndFix-master\tools,命令行爲:d:回車,cd D:\Temp\AndFix\AndFix-master\tools)。將bug.apk、fix.apk和簽名文件放入tools文件夾下。輸入命令

apkpatch -o D:\Temp\AndFix\output -k AndFix.jks -p admin888 -a 亞洲 -e admin888 -f fix.apk -t bug.apk

字段說明:
-o <output> : 輸出目錄
-k <keystore>: 打包所用的keystore
-p <password>: keystore的密碼
-a <alias>: keystore 用戶別名
-e <alias password>: keystore 用戶別名密碼
-f <new.apk> :新版本
-t <old.apk> : 舊版本

cmd

看見如上提示即生成.apatch文件成功。進入輸出目錄(-o 後面)。可以看到一個smail文件夾、diff.dex和一個.apatch文件。經常反編譯別人apk的同學一定對smail文件夾不陌生。可以查看到代碼,可惜才疏學淺看不懂.smail文件。這裏我們要用到的是.apatch文件。

1. 更改.apatch文件名爲first.apatch。

2. 安裝bug.apk,真是個不吉利的名字。

3. 將first.apatch放入MainActivity裏的patchPath路徑下(我這裏是/tmp/)。

4. 運行bug.apk

依次點擊showLogBtn、fixBtn、showLogBtn。Log輸出如下

Log

可以看到Log輸出已經改變,這也意味着帶有Bug的方法被成功修復。

多次打補丁

如果本地保存了多個補丁,那麼AndFix會按照補丁生成的時間順序加載補丁。具體是根據.apatch文件中的PATCH.MF的字段Created-Time。

混淆

-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
    native <methods>;
}

探索

1. 修改first.apatch後綴名爲.zip

2. 解壓文件

apatchZip

打開META-INF中的文件PATCH.MF,可以看到如下內容

Manifest-Version: 1.0
Patch-Name: fix
Created-Time: 25 Mar 2016 08:28:00 GMT
From-File: fix.apk
To-File: bug.apk
Patch-Classes: com.dyk.andfixtest.MainActivity_CF
Created-By: 1.0 (ApkPatch)

可以發現Patch-Classes就是被改動過的文件,後面還加個_CF(CrossFire ?)。

使用dex2jar反編譯classes.dex

1. 下載dex2jar和jd-gui,點我下載

2. 將classes.dex文件複製到dex2jar解壓目錄

3. 命令行進入dex2jar解壓目錄

4. 輸入命令:d2j-dex2jar classes.dex

經過四招還我漂漂拳後,可以在dex2jar解壓目錄下發現classes-dex2jar.jar這麼一個文件。

使用jd-gui打開classes-dex2jar.jar

package com.dyk.andfixtest;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.alipay.euler.andfix.annotation.MethodReplace;

public class MainActivity_CF extends Activity
{
  private static final String TAG = "AndFix";

  @MethodReplace(clazz="com.dyk.andfixtest.MainActivity", method="waitFix")
  private void waitFix()
  {
    Log.i("AndFix", "fix bug");
  }

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    requestWindowFeature(1);
    setContentView(2130968601);
    findViewById(2131492944).setOnClickListener(new MainActivity.1(this));
    findViewById(2131492943).setOnClickListener(new MainActivity.2(this));
  }
}

可以發現.patch只是差異文件,這裏給需要替換的方法添加了一個指定class和method的註解@MethodReplace(clazz="com.dyk.andfixtest.MainActivity", method="waitFix")。客戶端sdk得到補丁文件後根據註解尋找需要替換的方法。最後由JNI層完成方法的替換。

發佈了56 篇原創文章 · 獲贊 213 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章