首页 > 代码库 > 阿里百川HotFix1.3.3初体验

阿里百川HotFix1.3.3初体验

先来说说它适用的场景及一些局限性:

首先,它是一个热修复的解决方案,可以紧急修复我们的线上bug,并且针对于第一次加载补丁的时候,它是及时生效的,并且集成起来相对简单,这是它的优点。
当然,它的局限性也是显而易见的:

1.只能修改方法体内部:

public static void test(Context context) {
    //旧代码
    //Toast.makeText(context.getApplicationContext(), "It‘s a Bug", Toast.LENGTH_SHORT).show();
    //新代码
    Toast.makeText(context.getApplicationContext(), "It‘s a Bug ,but already fixed", Toast.LENGTH_SHORT).show();
}

2.不允许直接添加/修改全局实例变量(包括静态变量), 不允许修改构造函:

//新增temp变量不允许!
private static String temp = "new apk...";
public static void test(Context context) {
    Toast.makeText(context.getApplicationContext(), temp, Toast.LENGTH_SHORT).show();
}

//private static String str = "old apk...";
private static String str= "new apk...";//修改全局变量也是不允许的,静态变量也同样不允许

public Test() {
     //temp = "old"; 修复前
     temp = "new"; //这样也是不允许的,不允许修改构造函数
}

//另一方面代码块也是不允许修改的, 也会被打补丁工具直接忽略. 包括静态代码块 
{
    Log.d(TAG, "old block");
}
static{
    Log.d(TAG, "new block");
}

3.不允许直接添加新的方法

public static void test(Context context) {
    newMethod(context);
}
//新增方法是不可以的
public static void newMethod(Context context) {
    Toast.makeText(context.getApplicationContext(), "new method", Toast.LENGTH_SHORT).show();
}

4.方法被反射调用

假设test方法被反射调用, 因为静态方法反射调用可以有两种方式, 所以会出现以下两种情况, 一一说明

Class cls = BaseBug.class;
Method method = cls.getDeclaredMethod("test", new Class[]{Context.class});

情况1. 静态方法类调用 不支持

method.invoke(new BaseBug(), new Object[]{mContext});//这样是不被允许的

情况2. 静态方法对象调用

method.invoke(null, new Object[]{mContext});//这样是可以的,因为参数是null

情况3.非静态方法反射调用

非静态方法调用肯定是类似method.invoke(new BaseBug(), new Object[]{}); 参考上面,所以此时test方法不能被patch.

还有一点需要注意,旧版本的EvenBus框架是通过反射调用onEventMainThread/onEvent方法实现事件总线机制, 所以onEventMainThread/onEvent方法等方法其实是不能被patch的

5.关于注解,总结的来说,就是对于注解编译期的修改,也就是修复注解本身,是不被支持的,但是在运行时去修复被注解的方法,这样是可以的,下面举个例子

public @interface MethodInfo {
    String info() default "old apk...";
}

public @interface MethodInfo {
    String info() default "new apk...";//这样是不可以的,打补丁时会忽略此修改
}

//==============另一个例子

//修复前 @MethodInfo(info="old")
 @MethodInfo(info="new")//修复后。  这样是可以的
public static void test(Context context {
    Toast.makeText(context.getApplicationContext(), "old apk...", Toast.LENGTH_SHORT).show();
}

6.方法参数的一些限制

6.1 参数包括:long, double, float基本类型的方法不能被patch. 比如:test(Context context, long value), 注意这几种基本类型的封装类是支持的. 比如:test(Context context, Long value)这样是支持的

6.2 参数超过8的方法不能被patch.

6.3 泛型参数的方法不能被patch. 比如:test(Context context, T t);

7.虽然HotFix不支持修改全局变量、静态变量、不允许增加新方法等等之类的限制,但是可以通过新增类去实现特定修复需求,也就是说,新增类,是支持的。

OK,它的局限性说到这里,下面就开始准备接入HotFix了

首先,你要成为阿里百川的开发者,成为开发者以后创建你的应用,即可实现后续集成步骤。 。

在添加依赖过程中,使用gradle的同学们,你可以直接在app目录下的build.gradle 文件添加

(这里一定要注意是app目录下的)

repositories {
        maven {
            url "http://repo.baichuan-android.taobao.com/content/groups/public/"
        }
}

官方里是这样介绍的,你需要添加依赖:

dependencies {
    compile ‘com.alibaba.sdk.android.plugins:alisdk-hotfix:1.3.3‘
    compile ‘com.alibaba.sdk.android.plugins.jar:alisdk-utdid:0.0.1‘
}
//注意:如果你的项目已经依赖alisdk-utdid,则不需要再进行依赖

然而事实并不是你注释了compile utdid 的语句就能解决的,如果只compile hotfix,还是会自动依赖utdid,正确的姿势是这样的

compile (‘com.alibaba.sdk.android.plugins:alisdk-hotfix:1.3.3‘){
//        exclude module:"alisdk-utdid"
        transitive false
        //这里exclude 这一句和transitive这一句可以二选一
    }

然后你按照官网的要求配置Manifest节点和权限就可以初始化了

配置appsecret和rsasecret

<meta-data 
android:name="com.taobao.android.hotfix.APPSECRET" 
android:value=http://www.mamicode.com/"your-app-secret" /> 
<meta-data 
android:name="com.taobao.android.hotfix.RSASECRET" 
android:value=http://www.mamicode.com/"your-rsa-secret" /> 

添加权限

<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 存储读写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

接下来开始Application代码

package com.tango.hotfdemo;

import android.app.Application;

import com.taobao.hotfix.HotFixManager;
import com.taobao.hotfix.PatchLoadStatusListener;
import com.taobao.hotfix.util.PatchStatusCode;

/**
 * Created by Administrator on 2016/12/13.
 */

public class MyApplication extends Application {
    public static String appVersion;
    public static String appId;
    @Override
    public void onCreate() {
        super.onCreate();
        initApp();
//        initHotfix();   为了演示,这里没有调用。而是要结合它的Debug工具使用
    }

    private void initApp() {
        this.appId = "000000"; //替换掉自己应用的appId,这里我是瞎写的
        try {
            this.appVersion = this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName;
        } catch (Exception e) {
            this.appVersion = "1.0.0";
        }
    }

    /**
     * 建议在Application.onCreate方法中执行initialize和queryNewHotPatch操作, 尽可能早的执行
     * 本demo只是为了测试的方便, 所以没有调用
     */
    private void initHotfix() {
        HotFixManager.getInstance().initialize(this, appVersion, appId, true, new PatchLoadStatusListener() {
            @Override
            public void onload(int mode, int code, String info, int handlePatchVersion) {
                // 补丁加载回调通知
                if (code == PatchStatusCode.CODE_SUCCESS_LOAD) {
                    // TODO: 10/24/16 表明补丁加载成功
                } else if (code == PatchStatusCode.CODE_ERROR_NEEDRESTART) {
                    // TODO: 10/24/16 表明新补丁生效需要重启. 业务方可自行实现逻辑, 提示用户或者强制重启, 建议: 用户可以监听进入后台事件, 然后应用自杀
                } else {
                    // TODO: 10/25/16 其它错误信息, 查看PatchStatusCode类说明
                }
            }
        });
        HotFixManager.getInstance().queryNewHotPatch();
    }
}

Activity代码如下

package com.tango.hotfdemo;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.taobao.hotfix.HotFixManager;
import com.taobao.hotfix.PatchLoadStatusListener;

public class MainActivity extends AppCompatActivity {
    private Button btn_click;
    private static final int REQUEST_EXTERNAL_STORAGE_PERMISSION = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initHotfix();
    }
    /**
     * 为了测试的方便, initialize放在activity中, 但是实际上initialize应该放在Application的onCreate方法中, 需要尽可能早的做初始化.
     */
    private void initHotfix() {
        HotFixManager.getInstance().initialize(this.getApplication(), MyApplication.appVersion, MyApplication.appId, true, new PatchLoadStatusListener() {
            @Override
            public void onload(final int mode, final int code, final String info, final int handlePatchVersion) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        StringBuilder stringBuilder = new StringBuilder();
                        stringBuilder.append("Mode:").append(mode).append(" Code:").append(code).append(" Info:").append(info).append(" HandlePatchVersion:").append(handlePatchVersion);
//                        updateConsole(stringBuilder.toString());
                    }
                });
            }
        });
        if (Build.VERSION.SDK_INT >= 23) {
            requestExternalStoragePermission();
        }
    }
    /**
     * 如果本地补丁放在了外部存储卡中, 6.0以上需要申请读外部存储卡权限才能够使用. 应用内部存储则不受影响
     */
    private void requestExternalStoragePermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    REQUEST_EXTERNAL_STORAGE_PERMISSION);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case REQUEST_EXTERNAL_STORAGE_PERMISSION:
                if (grantResults.length <= 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                }
                break;
            default:
        }
    }

    private void initView() {
        btn_click = (Button) findViewById(R.id.btn_click);

        btn_click.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(),"我是一个大bug",Toast.LENGTH_LONG).show();
            }
        });
    }
}

混淆配置如下

#基线包使用,生成mapping.txt
#-printmapping mapping.txt
#生成的mapping.txt在app/buidl/outputs/mapping/release路径下,移动到/app路径下
#修复后的项目使用,保证混淆结果一致
-applymapping mapping.txt
#这里需要解释一下,当生成基准包的时候呢,打开这个配置-printmapping mapping.txt,目的是为了生成一个混淆的规则文件,然后copy到/app路径下,这样,在打补丁包的时候,再打开这个配置-applymapping mapping.txt,保证两次混淆的规则是一致的,以免不必要的冲突

#HotFix
-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
    native <methods>;
}
-keep class com.alipay.euler.andfix.**{
    *;
}
-keep class com.taobao.hotfix.aidl.**{*;}
-keep class com.ta.utdid2.device.**{*;}
-keep class com.taobao.hotfix.HotFixManager{
    public *;
}

OK,可以开始打包,这里需要注意,不要用Instant run 去打包,而是用gradle的assembleRelease脚本去打包。

打包好了之后,可以安装到手机上,然后呢,我们要开始打补丁包了

首先,在Activity里,我们改点东西

btn_click.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(),"I‘m a Big Bug!But I‘m already Fixed!",Toast.LENGTH_LONG).show();
            }
        });

接下来,要生成补丁包了,分以下几步

1.我们把刚才打包生成的mapping.txt 在app/buidl/outputs/mapping/release路径下,移动到/app路径下

2.按照混淆文件里说的,打开-applymapping mapping.txt,使两次打包使用同一份规则

3.生成我们改动后的apk,同样,用assembleRelease的方式生成。

4.我们把上一份有bug的apk命名为old.apk,新生成的命名为new.apk。将它们copy到桌面的一个文件夹去(哪里都可以,只是我用的是桌面)。

5.在文件夹中,将我们在官网下载的BCFixPathTools-1.3.0.jar还有我们签名时用到的签名文件也copy进来.创建一个名为BuDing的文件夹(用来存放生成的补丁包的,要保证它是空的,当然,它的命名也是叫什么都可以)

最后的一步,win+r 进入cmd命令行。输入命令:

java -jar BCFixPatchTools-1.3.0.jar -cmd patch -src_apk c:\Users\Administrator\Desktop\hotFix打包文件夹\old.apk -fixed_apk c:\Users\Administrator\Desktop\hotFix打包文件夹\new.apk -wp c:\Users\Administrator\Desktop\hotFix打包文件夹\BuDing -sign_file_url c:\Users\Administrator\Desktop\hotFix打包文件夹\babytree.keystore -sign_file_pass tangotango -sign_alias tangotango -sign_alias_pass tangotango

拆分以上命令,是这样的

java -jar BCFixPatchTools-1.3.0.jar -cmd patch //固定格式
-src_apk 基准包的地址,也就是old.apk 这里要用绝对路径
-fixed_apk 新包的地址,同样要绝对路径
-wp 我们生成的补丁包将会放到哪个路径下 也是绝对路径(也就是我们创建的那个文件夹)
-sign_file_url 签名文件 绝对路径
-sign_file_pass
-sign_alias
-sign_alias_pass 这就不用多说了,签名文件的那些口令

补丁包会出现在刚才创建的BuDing文件夹中,然后利用官网提供的Debug工具,我就不细说了

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    阿里百川HotFix1.3.3初体验