Android热修复框架——AndFix

一直关注App的热修复的技术发展,之前做的应用也没用使用到什么热修复开源框架。在App的热修复框架没有流行之前,做的应用上线后发现一个小小的Bug,就要马上发一个新的版本。今天看了热修复技术,感觉挺好玩的,就实现了使用的全过程。下面记录使用开源框架阿里巴巴的AndFix过程。


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

1.使用极光推送消息到该应用的版本需要下载补丁,如果应用收到了消息后,应用判断当前的版本是否需要下载补丁。如果应用没有收到消息的通知,则下次启动App的时候,获取友盟在线参数来判断是否需要下载补丁。

2.应用启动的后,有则通过 Android 文件下载file-downloader框架来下载到SD下并且通过使用AndFix来加载到应用中。

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


AndFix的引入是: compile 'com.alipay.euler:andfix:0.3.1@aar'

file-downloader框架的引入是:compile 'org.wlf:FileDownloader:0.3.1'

2.导入AndFix的so库文件以及极光推送的so库文件;
极光推送集成参考文档:http://docs.jpush.io/client/android_sdk/
注意:导入AndFix的so文件时,可以先阅读这下个:https://github.com/zhonghanwen/AndFix-Ndk-Build-ADT



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


定义相对应的Bean



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

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

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

    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.补丁包的生成

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


在解压apkpatch工具的目录下,打开命令行输入以下命令生成补丁包。apkpatch -m <apatch_path...> -o <output> -k <keystore> -p <***> -a <alias> -e <***>



  • 将生成的补丁放到指定服务器上。


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

}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值