AndFix簡單使用

簡介

AndFix是阿里開源的一個Android熱補丁框架,App可以在不重新發布版本的情況下,通過補丁替換出現bug的方法,達到修復bug的目的。現在支持android2.3到7.0,支持ARM 和X86 (AndFix supports Android version from 2.3 to 7.0, both ARM and X86 architecture, both Dalvik and ART runtime, both 32bit and 64bit)
優點:集成方便,使用簡單
缺點:只能替換方法,不支持新增方法,新增類,新增field等
AndFix文檔

集成步驟

發現bug生成apatch—>將apatch下發到用戶手機存儲系統—>利用AndFix完成apatch安裝,解決Bug

1.AndroidStudio 創建一個工程AndFixDemo,在app下的build.gradle添加引用

 implementation 'com.alipay.euler:andfix:0.5.0@aar'//AndFix

根據官方文檔,對AndFix進行使用
在這裏插入圖片描述

創建一個AndFixPatchManager,管理AndFix所有的api

public class AndFixPatchManager
{
    private static AndFixPatchManager mInstance = null;

    private static PatchManager mPatchManager = null;

    public static AndFixPatchManager getInstance()
    {
        if (mInstance == null)
        {
            synchronized (AndFixPatchManager.class)
            {
                if (mInstance == null)
                {
                    mInstance = new AndFixPatchManager();
                }
            }
        }
        return mInstance;
    }

    /**
     * Initialize PatchManager
     * @param context
     */
    public void initPatch(Context context){

        mPatchManager = new PatchManager(context);
        mPatchManager.init(Utils.getAppversion(context));
        //Load patch
        mPatchManager.loadPatch();
    }

    /**
     * Add patch
     * @param path
     */
    public void addPatch(String path){
        if(mPatchManager!=null){
            try
            {
                mPatchManager.addPatch(path);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
}

創建一個工具類Utils

public class Utils
{
    /**
     * 獲取應用程序appversion
     */

    public static String getAppversion(Context context)
    {
        String appversion = "";
        try
        {
            PackageManager pm = context.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0);
            appversion = pi.versionName;
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return appversion;
    }

    public static void printLog(){
        //error設置成會閃退,用於生成bug
        String error=null;
        Log.e("error_log",error);
    }
}

創建自定義的MyAppliction

public class MyAppliction extends Application
{
    @Override
    public void onCreate()
    {
        super.onCreate();
        //初始化AndFix模塊
        initAndFix();
    }

    private void initAndFix(){
        AndFixPatchManager.getInstance().initPatch(this);
    }
}

在AndroidManifest.xml進行引用

<application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme"
            android:name=".MyAppliction">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

因爲AndFix補丁的後綴是 .apatch,所以思路就是在程序裏將.apatch文件保存到手機指定目錄裏,然後從本地目錄加載.apatch進行修復

public class MainActivity extends AppCompatActivity
{

    private static final String FILE_END = ".apatch";
    private String mPatchDir;///storage/emulated/0/Android/data/drag.mandala.com.andfixdemo/cache/apatch/

    private Button btnCreateBug,btnFixBug;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnCreateBug = findViewById(R.id.btn_create_bug);
        btnFixBug = findViewById(R.id.btn_fix_bug);
        mPatchDir = getExternalCacheDir().getAbsolutePath() + "/apatch/";
        //創建文件夾
        File file = new File(mPatchDir);
        if (file == null || !file.exists())
        {
            file.mkdir();
        }

    }

    public void createBug(View view){
        Utils.printLog();
    }

    public void fixBug(View view){
        AndFixPatchManager.getInstance().addPatch(getPatchName());
    }
    //patch文件名
    private String getPatchName(){
        return mPatchDir.concat("andFixRelease").concat(FILE_END);
    }
}

我華爲手機,本地的apatch目錄在/storage/emulated/0/Android/data/drag.mandala.com.andfixdemo/cache/apatch/
佈局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        android:orientation="vertical"
        android:gravity="center">

    <Button
            android:id="@+id/btn_create_bug"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="產生bug"
            android:onClick="createBug"
            android:padding="20dp"
            android:textSize="20sp"
    />
    <Button
            android:id="@+id/btn_fix_bug"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:text="修復bug"
            android:onClick="fixBug"
            android:textSize="20sp"
    />
</LinearLayout>

就兩個button,一個用於生成bug,一個用於修復bug
現在生成一個jks,打包一個apk,命名爲old.apk,安裝到手機上,點擊“產生bug”按鈕因爲

public static void printLog(){
        //error設置成會閃退,用於生成bug
        String error=null;
        Log.e("error_log",error);
    }

所以會閃退
現在修復這個Bug

 public static void printLog(){
        //error設置成會閃退,用於生成bug
        String error="修復bug";
        Log.e("error_log",error);
    }

重新用jks打包,命名爲new.apk,官方文檔下載apkpatch
在這裏插入圖片描述
下載好了,把兩個apk放進去,新建一個outputs文件夾,根據官方文檔,生成.apatch 文件
在這裏插入圖片描述
命令行定位到apkpatch所在目錄
在這裏插入圖片描述
我放在了H盤

C:\Users\intel>H:

H:\>cd H:\apkpatch-1.0.3
H:\apkpatch-1.0.3>apkpatch.bat
ApkPatch v1.0.3 - a tool for build/merge Android Patch file (.apatch).
Copyright 2015 supern lee <sanping.li@alipay.com>

usage: apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias>
-e <***>
 -a,--alias <alias>     alias.
 -e,--epassword <***>   entry password.
 -f,--from <loc>        new Apk file path.
 -k,--keystore <loc>    keystore path.
 -n,--name <name>       patch name.
 -o,--out <dir>         output dir.
 -p,--kpassword <***>   keystore password.
 -t,--to <loc>          old Apk file path.

usage: apkpatch -m <apatch_path...> -k <keystore> -p <***> -a <alias> -e <***>
 -a,--alias <alias>     alias.
 -e,--epassword <***>   entry password.
 -k,--keystore <loc>    keystore path.
 -m,--merge <loc...>    path of .apatch files.
 -n,--name <name>       patch name.
 -o,--out <dir>         output dir.
 -p,--kpassword <***>   keystore password.

H:\apkpatch-1.0.3>apkpatch.bat  -f new.apk -t old.apk -o outputs/ -k demo.jks
 -p aaaaaa -a demo t -e aaaaaa
add modified Method:V  printLog()  in Class:Ldrag/mandala/com/andfixdemo/Utils;

H:\apkpatch-1.0.3>

“add modified Method:V printLog() in Class:Ldrag/mandala/com/andfixdemo/Utils;”這一句表示已經修改的方法

這樣在outputs文件夾裏就生成了apatch文件,修改文件名爲andFixRelease.apatch,
和MainActivity裏的保持一致,防止加載不到

  //patch文件名
    private String getPatchName(){
        return mPatchDir.concat("andFixRelease").concat(FILE_END);
    }

將andFixRelease.apatch放入手機指定文件夾裏,就是MainActivity定義的目錄地址

 mPatchDir = getExternalCacheDir().getAbsolutePath() + "/apatch/";

重新運行app,點擊產生bug,會閃退,點擊修復bug,就會Add patch,進行修復

在實際開發中,不可能讓用戶自己將apatch文件傳入手機,這個需要放到服務器,通過接口,獲取是否有新的apatch文件,然後下載,下載到指定目錄,成功以後,Add patch
注意:每次產生的apatch文件的名字如果是相同的,結果會導致只有第一次的補丁能生效。只有每次名字不同才能加載,可以在保存之前,將已經存在的補丁刪除掉。

原理

先分析一下生成的apatch文件,先下載反編譯工具jadx
jdax
在這裏插入圖片描述

在這裏插入圖片描述

將andFixRelease.apatch後綴修改成.zip,解壓,解壓以後的文件目錄是
在這裏插入圖片描述

打開命令行,定位到jadx-1.0.0\lib的目錄,然後運行

java -jar jadx-gui-1.0.0.jar

會打開一個界面,選擇classes.dex,
在這裏插入圖片描述
發現有一個MethodReplace註解,再在Android項目中,打開源碼,在AndFixManager類中有一個fixClass方法
在這裏插入圖片描述
裏邊也有MethodReplace,
在這裏插入圖片描述

引用一篇文章Andfix熱修復技術使用對原理的解析:

apkpatch將兩個apk做一次對比,然後找出不同的部分。可以看到生成的apatch了文件,後綴改成zip再解壓開,裏面有一個dex文件。通過jadx查看一下源碼,裏面就是被修復的代碼所在的類文件,這些更改過的類都加上了一個_CF的後綴,並且變動的方法都被加上了一個叫@MethodReplace的annotation,通過clazz和method指定了需要替換的方法。然後客戶端sdk得到補丁文件後就會根據annotation來尋找需要替換的方法。最後由JNI層完成方法的替換。如果本地保存了多個補丁,那麼AndFix會按照補丁生成的時間順序加載補丁。具體是根據.apatch文件中的PATCH.MF的字段Created-Time。
AndFix通過Java的自定義註解來判斷一個方法是否應該被替換,如果可以就會hook該方法並進行替換。AndFix在ART架構上的Native方法是art_replaceMethod 、在X86架構上的Native方法是dalvik_replaceMethod。他們的實現方式是不同的。對於Dalvik,它將改變目標方法的類型爲Native同時hook方法的實現至AndFix自己的Native方法,這個方法稱爲dalvik_dispatcher,這個方法將會喚醒已經註冊的回調,這就是我們通常說的hooked(掛鉤)。對於ART來說,我們僅僅改變目標方法的屬性來替代它。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章