阿里百川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="your-app-secret" /> <meta-data android:name="com.taobao.android.hotfix.RSASECRET" android:value="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工具,我就不细说了 走走停停,不要害怕错过什么,

阿里百川HotFix1.3.3初体验

相关文章:

你感兴趣的文章:

标签云: