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来说,我们仅仅改变目标方法的属性来替代它。

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