一直关注App的热修复的技术发展,之前做的应用也没用使用到什么热修复开源框架。在App的热修复框架没有流行之前,做的应用上线后发现一个小小的Bug,就要马上发一个新的版本。今天看了热修复技术,感觉挺好玩的,就实现了使用的全过程。下面记录使用开源框架阿里巴巴的AndFix过程。
这里说的不是热修复怎么实现修bug的原理,这里说的是怎么使用AndFix。如果你想了解更多的andFix实现原理,你可以参考下面的文章:
1.使用极光推送消息到该应用的版本需要下载补丁,如果应用收到了消息后,应用判断当前的版本是否需要下载补丁。如果应用没有收到消息的通知,则下次启动App的时候,获取友盟在线参数来判断是否需要下载补丁。
2.应用启动的后,有则通过 Android 文件下载file-downloader框架来下载到SD下并且通过使用AndFix来加载到应用中。
AndFix的引入是: compile 'com.alipay.euler:andfix:0.3.1@aar'
2.导入AndFix的so库文件以及极光推送的so库文件;
极光推送集成参考文档:http://docs.jpush.io/client/android_sdk/
注意:导入AndFix的so文件时,可以先阅读这下个:https://github.com/zhonghanwen/AndFix-Ndk-Build-ADT
极光推送自定义消息(自定义消息有长度限制,所以补丁的下载url写成拼接形式:站点+下载资源名称)
定义相对应的Bean
public class MyApplication extends Application {
private static final String TAG = "AndFix";
public static String VERSION_NAME="";
public static PatchManager mPatchManager;
@Override
public void onCreate() {
super.onCreate();
try {
PackageInfo mPackageInfo = this.getPackageManager().getPackageInfo(this.getPackageName(), 0);
VERSION_NAME=mPackageInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
JPushInterface.init(this);
initAndFix();
}
private void initAndFix() {
mPatchManager=new PatchManager(this);
mPatchManager.init(VERSION_NAME);
Log.d(TAG, "initAndFix: inited.");
mPatchManager.loadPatch();
}
}
private PatchBean bean=new PatchBean();
private WeakHandler mHandler = new WeakHandler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(MainActivity.this,(String)msg.obj,Toast.LENGTH_LONG).show();
if (msg.what == MSG_WHAT_DOWNLOAD){
String message = (String) msg.obj;
if (TextUtils.isEmpty(message)) return false;
try {
bean = GsonUtils.getInstance().parse(PatchBean.class, message);
RepairBugUtil.getInstance().comparePath(MainActivity.this, bean);
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
});
//for receive customer msg from jpush server
private MessageReceiver mMessageReceiver;
public static final String MESSAGE_RECEIVED_ACTION = "MESSAGE_RECEIVED_ACTION";
public static final String KEY_MESSAGE = "message";
public void registerMessageReceiver() {
mMessageReceiver = new MessageReceiver();
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(MESSAGE_RECEIVED_ACTION);
registerReceiver(mMessageReceiver, filter);
}
public class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) {
String message = intent.getStringExtra(KEY_MESSAGE);
Message msg = new Message();
msg.what = MSG_WHAT_DOWNLOAD;
msg.obj = message;
mHandler.sendMessage(msg);
}
}
}
6.补丁包的生成
在解压apkpatch工具的目录下,打开命令行输入以下命令生成补丁包。apkpatch -m
7.自己测试一下成不成啦~
代码
package com.vson.hotfix.utils;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.vson.hotfix.MyApplication;
import com.vson.hotfix.PatchBean;
import com.vson.hotfix.SPConst;
import org.wlf.filedownloader.DownloadFileInfo;
import org.wlf.filedownloader.FileDownloadConfiguration;
import org.wlf.filedownloader.FileDownloader;
import org.wlf.filedownloader.listener.OnFileDownloadStatusListener;
import java.io.File;
import java.io.IOException;
/**
* Created by vson on 2016/3/11.
*/
public class RepairBugUtil {
private static final String TAG = "AndFix";
private static final int THREAD_COUNT = 3; //下载的线程数
private LocalPreferencesHelper mLocalPreferencesHelper;
private static class SingletonHolder {
public static final RepairBugUtil INSTANCE = new RepairBugUtil();
}
public static RepairBugUtil getInstance() {
return SingletonHolder.INSTANCE;
}
public void downloadAndLoad(final Context context,final PatchBean bean) {
// 1、创建Builder
FileDownloadConfiguration.Builder builder = new FileDownloadConfiguration.Builder(context);
// 2.配置Builder
// 配置下载文件保存的文件夹
builder.configFileDownloadDir(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator +
"FileDownloader");
// 配置同时下载任务数量,如果不配置默认为2
builder.configDownloadTaskSize(3);
// 配置失败时尝试重试的次数,如果不配置默认为0不尝试
builder.configRetryDownloadTimes(5);
// 开启调试模式,方便查看日志等调试相关,如果不配置默认不开启
builder.configDebugMode(true);
// 配置连接网络超时时间,如果不配置默认为15秒
builder.configConnectTimeout(25000);// 25秒
// 3、使用配置文件初始化FileDownloader
FileDownloadConfiguration configuration = builder.build();
FileDownloader.init(configuration);
FileDownloader.start(SPConst.URL_PREFIX+bean.url);
FileDownloader.registerDownloadStatusListener(new OnFileDownloadStatusListener() {
@Override
public void onFileDownloadStatusWaiting(DownloadFileInfo downloadFileInfo) {
}
@Override
public void onFileDownloadStatusPreparing(DownloadFileInfo downloadFileInfo) {
}
@Override
public void onFileDownloadStatusPrepared(DownloadFileInfo downloadFileInfo) {
}
@Override
public void onFileDownloadStatusDownloading(DownloadFileInfo downloadFileInfo, float downloadSpeed, long remainingTime) {
}
@Override
public void onFileDownloadStatusPaused(DownloadFileInfo downloadFileInfo) {
}
@Override
public void onFileDownloadStatusCompleted(DownloadFileInfo downloadFileInfo) {
Toast.makeText(context, "下载成功", Toast.LENGTH_LONG).show();
// add patch at runtime
try {
// .apatch file path
String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator +
"FileDownloader"+ File.separator+bean.url;
MyApplication.mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " added.");
//复制且加载补丁成功后,删除下载的补丁
File f = new File(patchFileString);
if (f.exists()) {
boolean result = new File(patchFileString).delete();
if (!result)
Log.e(TAG, patchFileString + " delete fail");
}
// mLocalPreferencesHelper.saveOrUpdate(SPConst.IsHavePathDownLoad, false);
} catch (IOException e) {
Log.e(TAG, "", e);
} catch (Throwable throwable) {
}
}
@Override
public void onFileDownloadStatusFailed(String url, DownloadFileInfo downloadFileInfo, FileDownloadStatusFailReason failReason) {
Toast.makeText(context, "下载失败", Toast.LENGTH_LONG).show();
}
});
}
public void comparePath(Context context, PatchBean RemoteBean) throws Exception {
if (mLocalPreferencesHelper == null) {
mLocalPreferencesHelper = new LocalPreferencesHelper(context, SPConst.SP_NAME);
}
String pathInfo = mLocalPreferencesHelper.getString(SPConst.PATH_INFO);
final PatchBean localBean = null; //GsonUtils.getInstance().parseIfNull(PatchBean.class, pathInfo);
//远程的应用版本跟当前应用的版本比较
if (MyApplication.VERSION_NAME.equals(RemoteBean.app_v)) {
//远程的应用版本跟本地保存的应用版本一样,但补丁不一样,则需要下载重新
/**
*第一种情况:当本地记录的Bean为空的时候(刚安装的时候可能为空)并且远程的Bean的path_v不为空的时候需要下载补丁。
* 第二种情况:当本地记录的path_v和远程Bean的path_v不一样的时候需要下载补丁。
*/
if (localBean == null && !TextUtils.isEmpty(RemoteBean.path_v)
|| localBean.app_v.equals(RemoteBean.app_v) &&
!localBean.path_v.equals(RemoteBean.path_v)) {
downloadAndLoad(context, RemoteBean);//
String json = GsonUtils.getInstance().parse(RemoteBean);
mLocalPreferencesHelper.saveOrUpdate(SPConst.PATH_INFO, json);
} /*else {
mLocalPreferencesHelper.saveOrUpdate(SPConst.IsHavePathDownLoad, false);
}*/
}
}
}