首页 > 代码库 > 阿里百川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工具,我就不细说了
阿里百川HotFix1.3.3初体验