美文网首页IT圈Android开发收藏夹移动架构
Android热修复实践应用--AndFix

Android热修复实践应用--AndFix

作者: Hanwen1001 | 来源:发表于2016-03-17 14:16 被阅读6406次

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

    实现的原理

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

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

    步骤

    1. 在gradle文件中增加相应的依赖。这里我使用thindownlaodmanager来下载补丁,使用极光推送来推送自定义消息下载补丁通知,使用友盟在线参数来获取补丁包的信息。也许你会问为了修复一个补丁而增加这么多的依赖,值得吗?我认为还可以吧,因为我的项目一般会使用到这些。
    ```AndFix的引入是:
    compile 'com.alipay.euler:andfix:0.3.1@aar'
    ```
    
    1. 导入AndFix的so库文件以及极光推送的so库文件;
      极光推送集成参考文档:http://docs.jpush.io/client/android_sdk/
      注意:导入AndFix的so文件时,可以先阅读这下个:https://github.com/zhonghanwen/AndFix-Ndk-Build-ADT
    1. 配置友盟在线参数的参数以及推光推送自定义的内容
    • 友盟在线参数


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


    • 定义相对应的Bean

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


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

       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();
               }
           }
       }
      
    3. 再加上推送推送的自定义内容的处理:(当推送消息过来的时候应用处于运行状态的时候,程序会处理消息进行下载补丁包)

           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);
               }
           }
       }
      
    4. 补丁包的生成

    • 下载AndFix的补丁生成工具:here
    • 生成补丁的文件需要的文件有:原apk文件,修复Bug后生成的新apk,签名文件。


    • 在解压apkpatch工具的目录下,打开命令行输入以下命令生成补丁包。
    apkpatch -m <apatch_path...> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
    
    • 将生成的补丁放到指定服务器上。(这里我放到了七牛上)



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

    代码

    通过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

    相关文章

      网友评论

      • 顾明伟:这个总结的好
      • l1zheng:请问楼主测试过能修复Fragment中的方法么, 我试用修复Activity中的方法是通过的,就是修改Fragment中的方法 修复不了.
      • 0f7d096211a6:andfix的修复级别楼主测试了吗,是类替换还是方法替换,能不能修改xml文件,能不能新增方法,能不能修改final值,能不能修改私有方法,能不能新加类,多次打补丁是替换还是覆盖。。。。。。。我测过,andfix基本就是垃圾,不能用,当然他们的维护团队有没有做维护就不得而知了
      • 大白栈:你好,我正在实践你这篇文章的实现方法,可是发现极光推送了,但是app好像没反应,没下载也没更新那个错误的bug,你能帮我看一下吗?QQ:603957521
      • jokerhyc:你好 你的代码适用6.0么 允许在application里面下载么 如何申请的权限呢
        Hanwen1001:@jokerhyc AndFix的官方文件上写着支持2.3到6.0的!应该可以application里面下载,这个你可以实践一下。 :blush:
      • 天之大任:请问一下这个在原来的版本上改完代码,生成新的apk,再用脚本生成需要更新的部分,脚本会自动搜索差别生成呢还是我们在修改的时候有格式要求呢
        天之大任:@天之大任 一直只是听说热更新,今天看了你的实例感觉使用起来没想象的那么难,谢谢
        Hanwen1001:@天之大任 打补丁包工具会找出差异的部分并生成补丁包,但是我们在修改的时候要注意一些细节。毕竟AndFix不是什么都支持修改的,你可以官网查一下andfix有哪些的限制。
      • 弦小杰:我也希望对我有帮助
      • 尛坏蛋:Andfix的64位动态库怎么生成?我想知道这个
        Hanwen1001:@尛坏蛋 用AndFix的源码,自己编译源码生成的~
      • HuDP:👍🏻
      • 夨落旳尐孩:正在看这个,写的很有帮助
        Hanwen1001:@夨落旳尐孩 希望对你有帮忙~ :relaxed:

      本文标题:Android热修复实践应用--AndFix

      本文链接:https://www.haomeiwen.com/subject/ykdclttx.html