版本更新 功能模块

看了很多大神写的博客,真是受益匪浅。从菜鸟成长,现在虽然变得不那么菜,但还是有很多东西需要继续的学习。

写博客 第一是希望将自己以前写过的东西(模块化的东西)记录下来,方便以后需要再来查找;第二是希望有人如果需要重复造轮子可以拿去借鉴(如果有帮助的法);第三是有些东西是成长的足迹,能记录下来也是一种生活。

闲话不多扯,进入正题:

版本更新的模块对于当前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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章