Android熱修復實踐應用--AndFix

一直關注App的熱修復的技術發展,以前作的應用也沒用使用到什麼熱修復開源框架。在App的熱修復框架沒有流行以前,作的應用上線後發現一個小小的Bug,就要立刻發一個新的版本。我親身經歷過一週發兩個版本,真的折騰用戶的節奏~~因此,要開始考慮引入熱修復。下面記錄使用開源框架阿里巴巴的AndFix過程。android

實現的原理

這裏說的不是熱修復怎麼實現修bug的原理,這裏說的是怎麼使用AndFix。若是你想了解更多的andFix實現原理,你能夠參考下面的文章:git

  1. 應用啓動的時候,在 onCreate() 方法中獲取友盟的在線參數來判斷當前的應用版本是否有補丁須要下載,有則經過ThinDonloadManager來下載到SD下而且經過使用AndFix來加載到應用中。服務器

  2. 使用極光推送消息到該應用的版本須要下載補丁,若是應用收到了消息後,應用判斷當前的版本是否須要下載補丁。若是應用沒有收到消息的通知,則下次啓動App的時候,獲取友盟在線參數來判斷是否須要下載補丁。app

步驟

  1. 在gradle文件中增長相應的依賴。這裏我使用thindownlaodmanager來下載補丁,使用極光推送來推送自定義消息下載補丁通知,使用友盟在線參數來獲取補丁包的信息。也許你會問爲了修復一個補丁而增長這麼多的依賴,值得嗎?我認爲還能夠吧,由於個人項目通常會使用到這些。
    框架

compile 'com.alipay.euler:andfix:0.3.1@aar'
  1. 導入AndFix的so庫文件以及極光推送的so庫文件;
    極光推送集成參考文檔:http://docs.jpush.io/client/android_sdk/ide

注意:導入AndFix的so文件時,能夠先閱讀這下個:https://github.com/zhonghanwen/AndFix-Ndk-Build-ADT

工具

    1. 配置友盟在線參數的參數以及推光推送自定義的內容

      • 友盟在線參數

      • 極光推送自定義消息(自定義消息有長度限制,因此補丁的下載url寫成拼接形式:站點+下載資源名稱)

      • 定義相對應的Bean

    2. 在啓動的自定義Application類進行初始化工做(AndFix、極光的初始化)

    3. 在程序的入口類進行友盟補丁的檢測:

      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();
                 }
             }
         }
    4. 再加上推送推送的自定義內容的處理:(當推送消息過來的時候應用處於運行狀態的時候,程序會處理消息進行下載補丁包)

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

      • 下載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不同的時候須要下載補丁。
      */

    1. (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

    相關文章
    相關標籤/搜索