版本更新 功能模塊

看了很多大神寫的博客,真是受益匪淺。從菜鳥成長,現在雖然變得不那麼菜,但還是有很多東西需要繼續的學習。

寫博客 第一是希望將自己以前寫過的東西(模塊化的東西)記錄下來,方便以後需要再來查找;第二是希望有人如果需要重複造輪子可以拿去借鑑(如果有幫助的法);第三是有些東西是成長的足跡,能記錄下來也是一種生活。

閒話不多扯,進入正題:

版本更新的模塊對於當前App來講,是最基本的功能之一,因爲一個App不會只出一個版本,絕大多數的應用都是逐步的完善,將功能和方向在版本迭代中完善和找準。

當然,版本更新也有很多第三方的庫,之前使用的是友盟更新,這是一個很不錯的平臺,但是在年初的時候,友盟通告說年底之後將不再提供版本更新的服務。所以針對版本更新不能再依附於友盟了,其他的三方更新功能也存在,爲防止再出現類似的情況,自己去實現這個基本的功能。

一:目的

        針對應用的版本更新的迭代,實現持續性的產品需求。

二:要求

       1)版本更新首要的是穩定,安全(穩定和安全都應該是軟件重要的特性之一)

       2)代碼的維護成本

三:過程

      1)首先要做的是 清楚版本更新的流程(在開發過程中 大家都很清楚,產品定下需求,做開發在開發之前要對需求過一遍)

           a)客戶端請求服務端,檢查服務端的最新版本,然後將服務端的最新版本信息獲取到客戶端,然後再根據本地版本的信息,選擇是否更新。

           b)本地版本如果等於服務器的版本,那就無需更新。如果本地版本比服務端版本要小(或者說版本舊),那就需要更新。

           c)本地更行應用,一般是在wifi先進行更新(畢竟大多數應用的下載更新還是會耗費幾十兆的流量,相對將這些流量使用到其他地方,或許會更划算些),在手機自己的流量下,不更新(當然也可以增加設置,版本更新在任何情況下都可以進行)。

           d)版本的更新,在wifi下,可能不會在一次更新下載中 立馬就能下載完成,所以就需要記錄已經下載的進度。

           e)當安裝包下載完成之後,就需要在適當的時候進行更新提示及更新操作,然後進行更新。

       2)其次是代碼實現

          a) 幾個主要的類

              UpdateManager ---用於管理和檢查版本更新的manager類

              UpdateService   ---用於處理版本更新下載新包的服務

              NetStatusReceiver --用於監聽網絡變化的廣播

          b)版本更新的檢查()

            代碼如下:

   //檢查更新的網絡請求
   private void initData(){
         mContext = this;
         int curVersionCode = CommonHelper.getVersionCode(mContext);
         String tempUrl= FinalUtil.HOST_UPDATE + "?app_version=" + curVersionCode + "&app_channel=Huasheng";

      //使用OkHttp 框架請求服務 獲取版本更新的信息
      OkHttpClient client = new OkHttpClient();
      Request request = new Request.Builder().url(tempUrl).build();
      client.newCall(request).enqueue(getCallBackListener());

      mHandler.sendEmptyMessageDelayed(TYPE_ENTER,3000);
     }

    //Okhttp的回調 
    private Callback getCallBackListener(){
           Callback listener = new Callback() {
       @Override
       public void onFailure(Request request, IOException e) {
           Log.i("android","get update info failure");
           }

       @Override
        public void onResponse(Response response) throws IOException {
        byte[] data = null;
        try{
              ResponseBody body = response.body();
              if(body != null)
             data = body.bytes();
            }catch (Exception e){
              LogUtil.e("Exception: data is null:" + e.getMessage() +" --> " + (data == null) + " response:" + response.toString());
            }
       
         if(data == null) return;
         try{
            JSONObject jsonObject = new JSONObject(new String(data));
            Object tempObject = jsonObject.get("data");
            Gson tempGson = new Gson();
            UpdateBean bean = tempGson.fromJson(tempObject.toString(), UpdateBean.class);
            checkUpdate(bean);
           }catch (Exception e){}
        }
      };
      return listener;
      }

     // 版本更新的檢查   
     private void checkUpdate(UpdateBean bean){
       if(bean == null) return;
       if(TextUtils.isEmpty(bean.download_link)) return;
       strMd5 = bean.apk_md5;
       MApplication.mUpdateManager.setServerBean(bean);
       if(MApplication.mUpdateManager.checkUpdate(mContext)) {
       MApplication.mUpdateManager.doUpdate(mContext,Integer.parseInt(bean.version_code));
       }
     }

       c)進行版本更新

           1)代碼如下:

    //檢查是否需要更新
    public boolean checkUpdate(Context context){
        if(mServerBean != null){
            mServerVersionCode = Integer.parseInt(mServerBean.version_code);
            mAppVersionCode = DeviceInfoManager.getAppVersionCode(context);
            if(mServerVersionCode > mAppVersionCode&& mServerVersionCode > 0 && mAppVersionCode > 0){//需要下載
                mDBBean = MApplication.mCommonDao.selectUpdateBean(String.valueOf(mServerVersionCode));
                if(mDBBean != null && !TextUtils.isEmpty(mDBBean.version_code)){ //重新下載
                    int tempDBCode = Integer.parseInt(mDBBean.version_code);
                    if( tempDBCode != mServerVersionCode){//之前下載的版本與當前服務器上最新版本不一致,刪除之前下載的版本
                         doDeleteLocalFile();
                         doDeleteDBRecord();
                         mDBBean = null;
                        return true;
                     }else{
                         if(mDBBean.end == mDBBean.finished)
                             return false;//表示之前已經下載完了,不需要再次下載
                          else
                             return true;
                     }
                }
                return true;
            }
            return false;//當前版本與服務器版本一致時 是不開下載服務的
        }else
          return false;
    }

    //進行更新
    public void doUpdate(final Context context,final int versionCode){
        if(mDBBean == null || TextUtils.isEmpty(mDBBean.version_code)){//初次下載處理,必須得保存下載的數據長度,不然會出現剛進入是自己網,之後切換成無線網進行下載異常情況
            ThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    long tempFileLength = doGetDownloadLength(mServerBean.download_link);
                    mDBBean = new UpdateBean();
                    mDBBean.end = tempFileLength;
                    mDBBean.download_link = mServerBean.download_link;
                    mDBBean.version_code = mServerBean.version_code;
                    mDBBean.intro = mServerBean.intro;
                    boolean isInserted = doSaveDBRecord(mDBBean);
                    if(isInserted && NetUtil.isNetworkConnected(context) && NetUtil.isNetWorkWifi(context)){//有網 有無線網時進行下載
                        Intent intentStartDownload = new Intent(context,UpdateService.class);
                        intentStartDownload.putExtra("BEAN",mDBBean);
                        context.startService(intentStartDownload);
                    }
                }
            });
        }else if(mDBBean != null && !TextUtils.isEmpty(mDBBean.download_link) && NetUtil.isNetworkConnected(context) && NetUtil.isNetWorkWifi(context)){//表示之前已經下載過,並且沒有下載完
            if(mDBBean.end > mDBBean.finished){
                ThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        Intent intentStartDownload = new Intent(context,UpdateService.class);
                        intentStartDownload.putExtra("BEAN",mDBBean);
                        context.startService(intentStartDownload);
                    }
                });
            }
        }
    }

    //刪除本地文件
    private void doDeleteLocalFile(){
        File tempFile = new File(DOWNLOAD_FILE_SAVE_PATH + File.separator + DOWNLOAD_FILE_SAVE_NAME);
        if(tempFile.exists()){
            try {
                tempFile.delete();
            }catch (Exception e){}
        }
    }

    //刪除本地的文件
    private void doDeleteDBRecord(){
        MApplication.mCommonDao.deleteAllUpdateBean();
    }

    //獲取下載文件的長度
    private long doGetDownloadLength(String urlPath){
        if(TextUtils.isEmpty(urlPath)) return 0;
        long tempFileLength = 0;
        HttpURLConnection urlcon = null;
        try{
            URL url = new URL(urlPath);
            urlcon = (HttpURLConnection) url.openConnection();
            tempFileLength = urlcon.getContentLength();
        }catch (Exception e){}
        finally {
            if(urlcon != null){
                urlcon.disconnect();
            }
        }
        return tempFileLength;
    }

    //下載的第一次保存要下載的文件(必須要做這個動作,因爲存在使用自己流量進入應用,在使用過程中切換成無線wifi)
    private boolean doSaveDBRecord(UpdateBean bean){
        boolean isInsert =  MApplication.mCommonDao.insertUpdateBean(bean);
        return isInsert;
    }
        d)安裝包的文件的下載服務處理

          1)代碼如下:

    //請求網絡
    private void doDownloadFromService(Context context, UpdateBean bean) {
        if(bean == null || TextUtils.isEmpty(bean.download_link) || bean.end <= 0){
            Intent intentStopService = new Intent(context,UpdateService.class);
            context.stopService(intentStopService);//在服務中沒有更新的bean,那麼不進行更新下載操作.同樣也實用於網絡的狀態的改變
            return;//網絡狀態的改變 開啓和關閉更新的服務,但是需要做對空的判斷
        }
        HttpURLConnection conn = null;
        RandomAccessFile raf = null;
        InputStream in = null;
        int respCode = 0;
        long finished = 0;
        try {
            URL url = new URL(bean.download_link);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(3000);
            conn.setDoInput(true);
            conn.setRequestMethod("GET");
            // 設置下載位置
            long start = bean.start + bean.finished;
            conn.setRequestProperty("Range",
                    "bytes=" + start + "-" + bean.end);
            // 設置文件寫入位置
            File file = new File(UpdateManager.DOWNLOAD_FILE_SAVE_PATH, UpdateManager.DOWNLOAD_FILE_SAVE_NAME);
            raf = new RandomAccessFile(file, "rwd");
            raf.seek(start);
            finished += bean.finished;
            respCode = conn.getResponseCode();
            // 開始下載
            if (respCode == 206 || respCode == 200) {//206 Partial Content
                // 讀取數據
                in = conn.getInputStream();
                int len = -1;
                byte[] b = new byte[1024 * 4];
                while ((len = in.read(b)) != -1) {
                    // 寫入文件
                    raf.write(b, 0, len);
                    finished += len;
                    LogUtil.e("abcde","finished:" + ((finished * 1.0f) / (bean.end * 1.0f) * 100) + "%");
                    bean.finished = finished;
                    if (finished == bean.end) {
                        bean.status = UpdateManager.DOWNLOAD_STATUS_FINISHED;
                    } else if (finished > 0 && finished < bean.end) {
                        bean.status = UpdateManager.DOWNLOAD_STATUS_UNFINISHED;
                    }

                    MApplication.mCommonDao.updateUpdateBean(bean);//已經在子線程中 所以這裏不在開子線程進行數據的操作寫數據庫數據

                    if (isPause) {//因爲網絡切換 不在聯網或者是不再是wifi情況下 就不下載,但是服務會一直存在着的
                        return;
                    }
                }
                //去關閉下載服務
                Intent intentStopDownload = new Intent(context, UpdateService.class);
                context.stopService(intentStopDownload);
            }

        } catch (Exception e) {
            if (respCode == -1 || respCode == 0) {
                if (requestCount < 3) {
                    //重新聯網一次
                    requestCount++;
                    connectionClose(raf, conn);
                    doDownloadFromService(context,bean);
                } else {
                    requestCount = 0;
                    connectionClose(raf, conn);
                }
            } else if (respCode == HttpURLConnection.HTTP_INTERNAL_ERROR
                    || respCode == HttpURLConnection.HTTP_NOT_FOUND || respCode == HttpURLConnection.HTTP_BAD_GATEWAY
                    || respCode == HttpURLConnection.HTTP_UNAUTHORIZED
                    || respCode == HttpURLConnection.HTTP_GATEWAY_TIMEOUT) {
                connectionClose(raf, conn);
            }
            e.printStackTrace();
        } finally {
            connectionClose(raf, conn);
        }
    }

       e)下載完畢之後在應用的適當位置進行提示更新

        1)代碼實現

    //應用的版本更新(最新處理)
    private void checkNeedUpdate(){
        ThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                bean = MApplication.mCommonDao.selectUpdateBean();
                if(bean == null || TextUtils.isEmpty(bean.version_code)) return;//如果數據庫只能那個無記錄, 或者記錄的版本號爲空 不更新處理
                int dbVerCode = 0;
                if(bean != null && !TextUtils.isEmpty(bean.version_code))
                    dbVerCode = Integer.parseInt(bean.version_code);
                int curVerCode =  DeviceInfoManager.getAppVersionCode(mContext);
                if(bean != null && dbVerCode <= curVerCode && dbVerCode > 0){
                    deleteUpdateDBandFile();
                }
                if(dbVerCode > curVerCode && bean != null && bean.end == bean.finished && bean.end > 0 && bean.status == 3){ //在下一版中添加這個狀態 1未下載 2下載未完成 3下載完成
                    doUpdateOperate();
                }
            }
        });
    }

    private void deleteUpdateDBandFile(){
        try{
            MApplication.mCommonDao.deleteUpdateBean(bean.version_code);
            File tempFile = new File(UpdateManager.DOWNLOAD_FILE_SAVE_PATH + File.separator
                    + UpdateManager.DOWNLOAD_FILE_SAVE_NAME);
            if(tempFile != null && tempFile.exists()){
                tempFile.delete();//如果下載的文件已經和安裝的一個版本 就直接刪除數據文件和下載的文件
            }
            return;
        }catch (Exception e){}
    }

    private void doUpdateOperate(){
        File file = new File(UpdateManager.DOWNLOAD_FILE_SAVE_PATH + File.separator + UpdateManager.DOWNLOAD_FILE_SAVE_NAME);
        String localFileMd5 = "";
        if(!file.exists()){
            try{
                File tempFile = new File(UpdateManager.DOWNLOAD_FILE_SAVE_PATH + File.separator);
                if(!tempFile.exists()){
                    tempFile.mkdirs();
                }
            }catch (Exception e){}
        }else{
            localFileMd5 = MD5Helper.getMD5(file).toUpperCase();//轉成大寫
        }
        strMd5 = strMd5.toUpperCase();                 //轉成大寫
        boolean checkFileMd5 = false;
        if(!TextUtils.isEmpty(strMd5) && !TextUtils.isEmpty(localFileMd5) && localFileMd5.equals(strMd5))
            checkFileMd5 = true;
        if(checkFileMd5 && file != null && file.exists()){
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    commonDialog = new CommonDialog(mContext);
                    commonDialog.setCanceledOnTouchOutside(false);
                    commonDialog.setShowTitle(false);
                    commonDialog.setShowClose(false);
                    View view = getLayoutInflater().inflate(R.layout.update_dialog, null);
                    ((TextView) view.findViewById(R.id.umeng_update_content)).setText(bean.intro);

                    commonDialog.setContainer(view);
                    view.findViewById(R.id.umeng_update_id_ok).setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            commonDialog.dismiss();
                            //設置你的操作事項
                            //安裝應用程序
                            Intent intent = new Intent(Intent.ACTION_VIEW);
                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            File file = new File(UpdateManager.DOWNLOAD_FILE_SAVE_PATH + File.separator + UpdateManager.DOWNLOAD_FILE_SAVE_NAME);

                            intent.setDataAndType(Uri.fromFile(file),
                                    "application/vnd.android.package-archive");
                            startActivity(intent);
                        }
                    });

                    view.findViewById(R.id.umeng_update_id_cancel).setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            commonDialog.dismiss();
                        }
                    });

                    view.findViewById(R.id.ok).setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            commonDialog.dismiss();
                            //設置你的操作事項
                            //安裝應用程序
                            Intent intent = new Intent(Intent.ACTION_VIEW);
                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            File file = new File(UpdateManager.DOWNLOAD_FILE_SAVE_PATH + File.separator
                                    + UpdateManager.DOWNLOAD_FILE_SAVE_NAME);
                            intent.setDataAndType(Uri.fromFile(file),
                                    "application/vnd.android.package-archive");
                            startActivity(intent);
                        }
                    });
                    commonDialog.showDialog();
                }
            });
        }else{//如果下載的文件MD5值不等,肯定安裝不成功.所以這是刪除本地數據庫記錄和本地文件
            ThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    deleteUpdateDBandFile();
                }
            });
        }
    }

       Ps.針對上邊代碼模塊的說明,因爲整個博客是對版本更新模塊的說明,所以上邊的代碼只是爲說明版本更新流程部分代碼,如果有需要查看源碼的請在博客最後地址查看源碼。

    四:總結

        1)版本更新是一個相對獨立的模塊,所以在應用開發中應該放在一個獨立的模塊中。便於維護。

        2)  版本更新是一個簡單 但是非常重要的功能,如果在版本更新中出現問題,那時比較嚴重的。因爲出現版本更新更新不了,直接導致的結果是用戶無法升級,要麼用戶只能體驗最初的版本,要麼用戶先刪除以前的舊版,再安裝新版(這樣的用戶體驗真是糟糕)。

         3)版本更新功能只是一個應用爲迭代新版本的開始,後續的精彩還是需要各位爲之繼續努力的。哈哈~~

       最後附上源碼地址:https://github.com/wzp09tjlg/VersionUpdate.git



發佈了20 篇原創文章 · 獲贊 8 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章