Android热修复实践应用—AndFix

一直关注App的热修复的技术发展,之前做的应用也没用使用到什么热修复开源框架。在App的热修复框架没有流行之前,做的应用上线后发现一个小小的Bug,就要马上发一个新的版本。我亲身经历过一周发两个版本,真的折腾用户的节奏~~所以,要开始考虑引入热修复。下面记录使用开源框架阿里巴巴的AndFix过程。

实现的原理

这里说的不是热修复怎么实现修bug的原理,这里说的是怎么使用AndFix。如果你想了解更多的andFix实现原理,你可以参考下面的文章:

  • https://github.com/alibaba/AndFix (AndFix的官网)
  • http://blog.csdn.net/lmj623565791/article/details/49883661 (Android大神鸿洋的Bolg文章)

  1. 应用启动的时候,在 onCreate() 方法中获取友盟的在线参数来判断当前的应用版本是否有补丁需要下载,有则通过ThinDonloadManager来下载到SD下并且通过使用AndFix来加载到应用中。
  2. 使用极光推送消息到该应用的版本需要下载补丁,如果应用收到了消息后,应用判断当前的版本是否需要下载补丁。如果应用没有收到消息的通知,则下次启动App的时候,获取友盟在线参数来判断是否需要下载补丁。

步骤

1.在gradle文件中增加相应的依赖。这里我使用thindownlaodmanager来下载补丁,使用极光推送来推送自定义消息下载补丁通知,使用友盟在线参数来获取补丁包的信息。也许你会问为了修复一个补丁而增加这么多的依赖,值得吗?我认为还可以吧,因为我的项目一般会使用到这些。

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

  • 接着,集成友盟在线参数 参考官方文档:http://dev.umeng.com/online-parameters/android-doc/intergration

3.配置友盟在线参数的参数以及推光推送自定义的内容

  • 友盟在线参数
  • 极光推送自定义消息(自定义消息有长度限制,所以补丁的下载url写成拼接形式:站点+下载资源名称)
  • 定义相对应的Bean

4.在启动的自定义Application类进行初始化工作(AndFix、极光的初始化)

5.在程序的入口类进行友盟补丁的检测:

private void getUmengParamAndFix() {
     //获取友盟在线参数对应key的values
     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();
         }
     }
 }

6.再加上推送推送的自定义内容的处理:(当推送消息过来的时候应用处于运行状态的时候,程序会处理消息进行下载补丁包)

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;
     }
 });

//for receive customer msg from jpush server
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);
         }
     }
 }

7.补丁包的生成

  • 下载AndFix的补丁生成工具:here
  • 生成补丁的文件需要的文件有:原apk文件,修复Bug后生成的新apk,签名文件。
  • 在解压apkpatch工具的目录下,打开命令行输入以下命令生成补丁包。
kpatch -m <apatch_path...> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
  • 将生成的补丁放到指定服务器上。(这里我放到了七牛上)

8.自己测试一下成不成啦~

代码

通过ThinDownloadManager下载补丁包,下载成功后使用AndFix加载补丁包的方法:

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) {
                    // add patch at runtime
                    try {
                        // .apatch file path
                        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");
                        }

                    // mLocalPreferencesHelper.saveOrUpdate(SPConst.IsHavePathDownLoad, false);
                    } catch (IOException e) {
                        Log.e(TAG, "", e);
                    } catch (Throwable throwable) {

                    }
                }

                @Override
                public void onDownloadFailed(int id, int errorCode, String errorMessage) {
                    //下载失败的时候,标注标记位,等下次重新打开应用的时候重新下载
                    //mLocalPreferencesHelper.saveOrUpdate(SPConst.IsHavePathDownLoad, true);
                    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);

}

判断是否有补丁包需要下载的方法:

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)) {
        //远程的应用版本跟本地保存的应用版本一样,但补丁不一样,则需要下载重新

        /**
         *第一种情况:当本地记录的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,
                    SPConst.URL_PREFIX + RemoteBean.url);
            String json = GsonUtils.getInstance().parse(RemoteBean);
            mLocalPreferencesHelper.saveOrUpdate(SPConst.PATH_INFO, json);
        } /*else {
           mLocalPreferencesHelper.saveOrUpdate(SPConst.IsHavePathDownLoad, false);
        }*/
    }

}

项目GitHub地址:https://github.com/zhonghanwen/AndFix-Bad-Practices

原文发布于微信公众号 - 非著名程序员(non-famous-coder)

原文发表时间:2016-03-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏猿天地

Netty 实现简单的HTTP服务

本篇文章是Netty专题的第八篇,前面七篇文章如下: 高性能NIO框架Netty入门篇 高性能NIO框架Netty-对象传输 高性能NIO框架Netty-整合k...

33960
来自专栏Kubernetes

Clair介绍和源码分析

更多关于kubernetes的深入文章,请看我csdn或者oschina的博客主页。 本文主要描述Clair架构、编译、部署、源码分析等内容。 Clair架构 ...

45980
来自专栏Zachary46

Android最火热修复实战

Sophix官网文档地址 https://help.aliyun.com/document_detail/53240.html

34340
来自专栏7号代码

Android开发高级进阶——多进程间通信

当一个应用在开始运行时,系统会为它创建一个进程,一个应用默认只有一个进程,这个进程(主进程)的名称就是应用的包名。

15610
来自专栏黑白安全

Jenkins拿shell方法

最近一直在学习怎么利用jenkins反序列化,妹的,就是没有一个顺手的GUI工具,能让我直接秒杀服务器。

56420
来自专栏欧阳大哥的轮子

Windows服务编程

一、服务(Service) 服务程序是NT系统支持的一种可执行文件,通常服务程序不与用户进行交互,在系统启动时会自动启动服务程序。所有的服务程序都由SCM进行...

21530
来自专栏开发之途

在 Android 设备上搭建 Web 服务器

一般而言,Android 应用在请求数据时都是以 Get 或 Post 等方式向远程服务器发起请求,那你有没有想过其实我们也可以在 Android 设备上搭建一...

1.1K30
来自专栏Coding+

Android Studio 快速启动 Android NDK 项目开发

通过本篇教程,您将学习如何使用 Android Studio 轻松启动 Android NDK 项目开发。

13130
来自专栏Android 研究

Android跨进程通信IPC之14——其他IPC方式

前面几篇文章,我们介绍了IPC的基础知识和Binder机制,本篇文章主要讲解各种跨进程的通信方式。

23130
来自专栏Hadoop实操

如何使用Java API访问CDH的Kudu

55560

扫码关注云+社区

领取腾讯云代金券