一直关注App的热修复的技术发展,之前做的应用也没用使用到什么热修复开源框架。在App的热修复框架没有流行之前,做的应用上线后发现一个小小的Bug,就要马上发一个新的版本。我亲身经历过一周发两个版本,真的折腾用户的节奏~~所以,要开始考虑引入热修复。下面记录使用开源框架阿里巴巴的AndFix过程。
实现的原理
这里说的不是热修复怎么实现修bug的原理,这里说的是怎么使用AndFix。如果你想了解更多的andFix实现原理,你可以参考下面的文章:
- 应用启动的时候,在 onCreate() 方法中获取友盟的在线参数来判断当前的应用版本是否有补丁需要下载,有则通过ThinDonloadManager来下载到SD下并且通过使用AndFix来加载到应用中。
- 使用极光推送消息到该应用的版本需要下载补丁,如果应用收到了消息后,应用判断当前的版本是否需要下载补丁。如果应用没有收到消息的通知,则下次启动App的时候,获取友盟在线参数来判断是否需要下载补丁。
步骤
- 在gradle文件中增加相应的依赖。这里我使用thindownlaodmanager来下载补丁,使用极光推送来推送自定义消息下载补丁通知,使用友盟在线参数来获取补丁包的信息。也许你会问为了修复一个补丁而增加这么多的依赖,值得吗?我认为还可以吧,因为我的项目一般会使用到这些。

| 1
 | compile 'com.alipay.euler:andfix:0.3.1@aar'
 | 
- 导入AndFix的so库文件以及极光推送的so库文件;
 极光推送集成参考文档:http://docs.jpush.io/client/android_sdk/
 注意:导入AndFix的so文件时,可以先阅读这下个:https://github.com/zhonghanwen/AndFix-Ndk-Build-ADT
  
- 配置友盟在线参数的参数以及推光推送自定义的内容
- 在启动的自定义Application类进行初始化工作(AndFix、极光的初始化)
  
 
- 在程序的入口类进行友盟补丁的检测: | 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | private void getUmengParamAndFix() {
 String pathInfo = OnlineConfigAgent.getInstance().getConfigParams(this, UMENG_ONLINE_PARAM);
 if (!TextUtils.isEmpty(pathInfo)){
 PatchBean onLineBean = GsonUtils.getInstance().parseIfNull(PatchBean.class , pathInfo);
 try {
 
 RepairBugUtil.getInstance().comparePath(this, onLineBean);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }
 
 |  
 
- 再加上推送推送的自定义内容的处理:(当推送消息过来的时候应用处于运行状态的时候,程序会处理消息进行下载补丁包) 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 |     private WeakHandler mHandler = new WeakHandler(new Handler.Callback() {@Override
 public boolean handleMessage(Message msg) {
 if (msg.what == MSG_WHAT_DOWNLOAD){
 String message = (String) msg.obj;
 if (TextUtils.isEmpty(message)) return false;
 try {
 PatchBean bean = GsonUtils.getInstance().parse(PatchBean.class, message);
 RepairBugUtil.getInstance().comparePath(MainActivity.this, bean);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 return false;
 }
 });
 
 
 private MessageReceiver mMessageReceiver;
 public static final String MESSAGE_RECEIVED_ACTION = "com.zhw.andfix.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);
 }
 }
 }
 
 | 
- 补丁包的生成
- 下载AndFix的补丁生成工具:here 
- 生成补丁的文件需要的文件有:原apk文件,修复Bug后生成的新apk,签名文件。
  
- 在解压apkpatch工具的目录下,打开命令行输入以下命令生成补丁包。| 1
 | apkpatch -m <apatch_path...> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
 |  
 
 
- 将生成的补丁放到指定服务器上。(这里我放到了七牛上)
  
  
- 自己测试一下成不成啦~
代码
通过ThinDownloadManager下载补丁包,下载成功后使用AndFix加载补丁包的方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 
 | public void downloadAndLoad(Context context, final PatchBean bean, String downloadUrl) {if (mLocalPreferencesHelper == null) {
 mLocalPreferencesHelper = new LocalPreferencesHelper(context, SPConst.SP_NAME);
 }
 Uri downloadUri = Uri.parse(downloadUrl);
 Uri destinationUri = Uri.parse(Environment.getExternalStorageDirectory()
 .getAbsolutePath() + bean.url);
 DownloadRequest downloadRequest = new DownloadRequest(downloadUri)
 .setDestinationURI(destinationUri)
 .setPriority(DownloadRequest.Priority.HIGH)
 .setDownloadListener(new DownloadStatusListener() {
 @Override
 public void onDownloadComplete(int id) {
 
 try {
 
 String patchFileString = Environment.getExternalStorageDirectory()
 .getAbsolutePath() + bean.url;
 BaseApplication.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");
 }
 
 } catch (IOException e) {
 Log.e(TAG, "", e);
 } catch (Throwable throwable) {
 
 }
 }
 
 @Override
 public void onDownloadFailed(int id, int errorCode, String errorMessage) {
 
 
 Log.e(TAG, "onDownloadFailed");
 
 }
 
 @Override
 public void onProgress(int id, long totalBytes, int progress) {
 Log.e(TAG, "progress:" + progress);
 }
 });
 mDownloadManager = new ThinDownloadManager(THREAD_COUNT);
 mDownloadManager.add(downloadRequest);
 }
 
 | 
判断是否有补丁包需要下载的方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 |     public void comparePath(Context context, PatchBean RemoteBean) throws Exception {String pathInfo = mLocalPreferencesHelper.getString(SPConst.PATH_INFO);
 final PatchBean localBean = GsonUtils.getInstance().parseIfNull(PatchBean.class, pathInfo);
 
 if (BaseApplication.VERSION_NAME.equals(RemoteBean.app_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,
 SPConst.URL_PREFIX + RemoteBean.url);
 String json = GsonUtils.getInstance().parse(RemoteBean);
 mLocalPreferencesHelper.saveOrUpdate(SPConst.PATH_INFO, json);
 }
 
 
 }
 }
 
 | 
附
项目GitHub地址:https://github.com/zhonghanwen/AndFix-Bad-Practices