第三章 Android 版本更新

作者: 忆念成风 | 来源:发表于2017-10-25 15:41 被阅读232次

    1. 概述

      在我们的Android开发过程中,版本的迭代更新是很重要的一步。产品需求提出和后续迭代的开发都需要版本的迭代更新。一般的App上传到应用商店以后,开发人员只要将新的App上传到应用市场,然后应用市场会提供App升级的操作,但是很多的App除了第三方的应用商店升级之后。我们都会有自己的版本升级操作。

    2. 版本更新的思维导图

    版本更新的思维导图

    3.版本更新的思路

      抛开安卓这个概念,普通的更新,我们要做的就是先拿到新事物,然后将新事物替换掉旧事物,这样我们就能完成一次简单的更新了。安卓的更新也很简单。

    1. 首先我们需要得到当前的版本号或者版本名 (versionCode或者versionName)
    2. 请求服务器的更新接口,一般服务器的更新接口里面的数据,必定包含版本号的值。
    3. 将请求更新的接口最好放在启动的地方,或者主页,看自己的产品设计。
    4. 通过对比产品的版本号的比较,确定是否弹窗更新。
    5. 有时候我们的产品会要求强制更新,这是不值得推荐的,不更新就不能用,用户的体验性太差,一般后台接口中就会有是否强制更新的字符。
    6. 比较完了之后,如果服务器中的接口版本号比本地版本号大,那么我们就需要进行版本更新。
    7. 然后是对弹窗的更新数据进行优化,因为我们下载的是字节数,给用户展示的最好还是百分比这样比较直观一点。

    4. 实现版本更新

      因为没有对外的服务器接口,而且不同后台的服务器返回接口不一样。我只是举个例子。

    1. 版本接口 json数据
    {
          versionId : 2
          versionNum : v10.0.2      版本编号
          status : 0    //0是不强制 ,1 是强制
          delFlag : 0
          created : 1491790427
          updated : 1491790427
          downloadUrl : "http://27.221.81.15/dd.myapp.com/16891/63C4DA61823B87026BBC8C22BBBE212F.apk?mkey=575e443c53406290&f=8b5d&c=0&fsname=com.daimajia.gold_3.2.0_80.apk&p=.apk"
          content : "来测测看版本更新的内容"
    }
    
    

    创建一个关于版本接口的bean类

    
    public class UpdateAppBean  implements Serializable{
        /**
         * versionId : 2
         * versionNum : v10.0.2
         * status : 0
         * delFlag : 0
         * created : 1491790427
         * updated : 1491790427
         * downloadUrl : www.baidu.com
         * content : djfkdkkkk
         */
    
        private int versionId;
        private String versionNum;
        private int status;
        private int delFlag;
        private int created;
        private int updated;
        private String downloadUrl;
        private String content;
    
        public int getVersionId() {
            return versionId;
        }
    
        public void setVersionId(int versionId) {
            this.versionId = versionId;
        }
    
        public String getVersionNum() {
            return versionNum;
        }
    
        public void setVersionNum(String versionNum) {
            this.versionNum = versionNum;
        }
    
        public int getStatus() {
            return status;
        }
    
        public void setStatus(int status) {
            this.status = status;
        }
    
        public int getDelFlag() {
            return delFlag;
        }
    
        public void setDelFlag(int delFlag) {
            this.delFlag = delFlag;
        }
    
        public int getCreated() {
            return created;
        }
    
        public void setCreated(int created) {
            this.created = created;
        }
    
        public int getUpdated() {
            return updated;
        }
    
        public void setUpdated(int updated) {
            this.updated = updated;
        }
    
        public String getDownloadUrl() {
            return downloadUrl;
        }
    
        public void setDownloadUrl(String downloadUrl) {
            this.downloadUrl = downloadUrl;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    }
    
    
    1. 创建一个工具类去获取版本号或版本名,也可以写在自定义的Application中。
    public class AppUtils {
    
        private AppUtils() {
            throw new UnsupportedOperationException("你不能对我进行实例化操作");
        }
    
    //获取VersionCode值
        public  static int  getVersionCode(Context mContext){
            if(mContext!=null){
                try {
                    return   mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionCode;
                } catch (PackageManager.NameNotFoundException e) {
                    e.printStackTrace();
                }
            }
            return  0;
        }
    
    //获取Version Name
        public  static  String getVersionName(Context  mContext){
           if(mContext!=null){
               try {
                   return   mContext.getPackageManager().getPackageInfo(mContext.getPackageName(),0).versionName;
               } catch (PackageManager.NameNotFoundException e) {
                   e.printStackTrace();
               }
           }
           return  "";
        }
    }
    
    1. 接下来是创建一个自定义的弹窗CommonProgressDialog
    public class CommonProgressDialog extends AlertDialog {
    
        private static final String TAG = "CommonProgressDialog";
        private ProgressBar mProgress;
        private TextView mProgressNumber;
        private TextView mProgressPercent;
        private TextView mProgressMessage;
    
        private Handler   mViewUpdateHandler;
    
        private  int mMax;
        private CharSequence mMessage;
        private  boolean  mHasStarted;
        private  int mProgressVal;
    
        private  String  mProgressNumberFormat;
        private NumberFormat mProgressPercentFormat;
    
        protected CommonProgressDialog(Context context) {
            super(context);
            initFormats();
        }
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.common_progress_dialog);
            mProgress = (ProgressBar) findViewById(R.id.progress);
            mProgressNumber = (TextView) findViewById(R.id.progress_number);
            mProgressPercent = (TextView) findViewById(R.id.progress_percent);
            mProgressMessage = (TextView) findViewById(R.id.progress_message);
    
            mViewUpdateHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    int progress = mProgress.getProgress();
                    int max = mProgress.getMax();
                    double dProgress =  (double)progress/(double)(1024*1024);
                    double   dMax = (double)max/(double)(1024 * 1024);
                    if(mProgressNumberFormat !=null){
                        String format = mProgressNumberFormat;
                        mProgressNumber.setText(String.format(format,dProgress,dMax));
                    } else {
                        mProgressNumber.setText("");
                    }
    
                    if(mProgressNumberFormat !=null){
                        double percent = (double) progress/(double) max;
                        SpannableString  tmp = new SpannableString(
                                mProgressPercentFormat.format(percent));
                        tmp.setSpan(new StyleSpan(Typeface.BOLD),0,tmp.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                      mProgressPercent.setText(tmp);
                    } else {
                        mProgressPercent.setText("");
                    }
                }
            };
            onProgressChanged();
            if(mMessage != null){
                setMessage(mMessage);
            }
    
            if(mMax>0){
                setMax(mMax);
            }
            if(mProgressVal>0){
                setProgress(mProgressVal);
            }
    
        }
    
    
        private void  initFormats(){
            mProgressNumberFormat = "%1.2fM/%2.2fM";
            mProgressPercentFormat = NumberFormat.getPercentInstance();
            mProgressPercentFormat.setMaximumFractionDigits(0);
        }
    
        private void onProgressChanged() {
            mViewUpdateHandler.sendEmptyMessage(0);
        }
    
        public void setMax(int max){
            if(mProgress !=null){
                mProgress.setMax(max);
                onProgressChanged();
            }else{
                mMax = max;
            }
        }
        public  void  setProgress(int value){
            if(mHasStarted){
                mProgress.setProgress(value);
                onProgressChanged();
            } else{
                mProgressVal = value;
            }
        }
        public  void setProgressStyle(int style){
            // mProgressStyle = style;
        }
    
        public  void  setIndeterminate(boolean indeterminate){
            if(mProgress !=null){
                mProgress.setIndeterminate(indeterminate);
            }
        }
    
        @Override
        public void setMessage(CharSequence message) {
            if(mProgressMessage !=null){
                mProgressMessage.setText(message);
            } else {
                mMessage = message ;
            }
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            mHasStarted = true;
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            mHasStarted =false ;
        }
    }
    
    
    1. 下面是对代码的处理,因为没有服务器,接口,所以这里自己模拟下载的环境。考虑到Android 6.0的权限处理问题,所以我们对权限做了请求。Android 7.0文件下载的问题。加了权限判断。
    public class MainActivity extends AppCompatActivity {
    
        private CommonProgressDialog commonProgressDialog;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //进入这里之后,获取版本号是否更新
            int versionCode = AppUtils.getVersionCode(this);
            getUpdate(versionCode);
        }
    
        //版本更新
        private void getUpdate(int versionCode) {
            //一般情况下是在这里拿本地的版本号和服务器的版本号进行比较的,我没有服务器就模拟拿数据了
            String  newVersion="2.1";
            String content ="\n"+
            "就不告诉你我们更新了什么-。-\n" +
                    "\n" +
                    "----------万能的分割线-----------\n" +
                    "\n" +
                    "1.新产品上线了,界面全新改版\n" +
                    "2.修复了若干bug,还杀了一个程序员祭天 \n";//更新内容
             String url = "http://openbox.mobilem.360.cn/index/d/sid/3429345"; //安装包下载地址
             double newVersionCode = Double.parseDouble(newVersion);
             int  cc= (int)(newVersionCode);
             if(cc!=versionCode){
                 if(cc>versionCode){
                     //版本号不同,这时候我们需要开始弹窗了
                     //需要强制更新的在这里选择不同的弹窗方式
                     ShowDialog(url,content);
                 }
             }
        }
    
        /**
         * 升级版本
         * @param url
         * @param content
         */
        private void ShowDialog(final String url, String content) {
            AlertDialog.Builder  builder = new AlertDialog.Builder(this);
            builder.setTitle("版本更新");
            builder.setMessage(content);
            builder.setPositiveButton("更新", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                   //这里进行更新的操作
                    dialogInterface.dismiss();
                    //提示版本更新的信息消失之后,开始在弹出本地下载的进度条。这里自定义一个dialog
                    commonProgressDialog = new CommonProgressDialog(MainActivity.this);
                    commonProgressDialog.setCanceledOnTouchOutside(false);
                    commonProgressDialog.setTitle("正在下载");
                    commonProgressDialog.setCustomTitle(
                    LayoutInflater.from(MainActivity.this).inflate(R.layout.title_dialog,null)
                    );
                    commonProgressDialog.setIndeterminate(true);
                    commonProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                    commonProgressDialog.setCancelable(true);
    
                    //downFile(URLData.DOWNLOAD_URL);
                  final DownloadTask downloadTask = new DownloadTask(MainActivity.this);
                    downloadTask.execute(url);
                    commonProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialogInterface) {
                            downloadTask.cancel(true);
                        }
                    });
    
                }
            });
            builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialogInterface, int i) {
                    dialogInterface.dismiss();
                }
            });
            builder.create().show();
    
        }
    
        // 下载存储的文件名
        private static final String DOWNLOAD_NAME = "channelWe";
    
        /**
         * 下载应用
         *
         * @author Administrator
         */
        class DownloadTask extends AsyncTask<String, Integer, String> {
    
            private Context context;
            private PowerManager.WakeLock mWakeLock;
    
            public DownloadTask(Context context) {
                this.context = context;
            }
    
            @Override
            protected String doInBackground(String... sUrl) {
                InputStream input = null;
                OutputStream output = null;
                HttpURLConnection connection = null;
                File file = null;
                try {
                    URL url = new URL(sUrl[0]);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.connect();
                    // expect HTTP 200 OK, so we don't mistakenly save error
                    // report
                    // instead of the file
                    if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                        return "Server returned HTTP "
                                + connection.getResponseCode() + " "
                                + connection.getResponseMessage();
                    }
                    int fileLength = connection.getContentLength();
                    if (Environment.getExternalStorageState().equals(
                            Environment.MEDIA_MOUNTED)) {
                        file = new File(Environment.getExternalStorageDirectory(),
                                DOWNLOAD_NAME);
    
                        if (!file.exists()) {
                            // 判断父文件夹是否存在
                            if (!file.getParentFile().exists()) {
                                file.getParentFile().mkdirs();
                            }
                        }
    
                    } else {
                        Toast.makeText(MainActivity.this, "sd卡未挂载",
                                Toast.LENGTH_LONG).show();
                    }
                    input = connection.getInputStream();
                    output = new FileOutputStream(file);
                    byte data[] = new byte[4096];
                    long total = 0;
                    int count;
                    while ((count = input.read(data)) != -1) {
                        if (isCancelled()) {
                            input.close();
                            return null;
                        }
                        total += count;
                        if (fileLength > 0) // only if total length is known
                            publishProgress((int) (total * 100 / fileLength));
                           output.write(data, 0, count);
    
                    }
                } catch (Exception e) {
                    return e.toString();
    
                } finally {
                    try {
                        if (output != null)
                            output.close();
                        if (input != null)
                            input.close();
                    } catch (IOException ignored) {
                    }
                    if (connection != null)
                        connection.disconnect();
                }
                return null;
            }
    
            @Override
            protected void onPreExecute() {
                super.onPreExecute();
                PowerManager pm = (PowerManager) context
                        .getSystemService(Context.POWER_SERVICE);
                mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                        getClass().getName());
                mWakeLock.acquire();
                commonProgressDialog.show();
            }
    
            @Override
            protected void onProgressUpdate(Integer... progress) {
                super.onProgressUpdate(progress);
                commonProgressDialog.setIndeterminate(false);
                commonProgressDialog.setMax(100);
                commonProgressDialog.setProgress(progress[0]);
            }
    
            @Override
            protected void onPostExecute(String result) {
                mWakeLock.release();
                commonProgressDialog.dismiss();
                if (result != null) {
                    // 申请多个权限。
                    AndPermission.with(MainActivity.this)
                            .requestCode(REQUEST_CODE_PERMISSION_SD)
                            .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
                            // rationale作用是:用户拒绝一次权限,再次申请时先征求用户同意,再打开授权对话框,避免用户勾选不再提示。
                            .rationale(rationaleListener
                            )
                            .send();
                    Toast.makeText(context, "您未打开SD卡权限" + result, Toast.LENGTH_LONG).show();
                } else {
                    update();
                }
            }
        }
    
        private static final int REQUEST_CODE_PERMISSION_SD = 101;
        private static final int REQUEST_CODE_SETTING = 300;
        private RationaleListener rationaleListener = new RationaleListener() {
            @Override
            public void showRequestPermissionRationale(int requestCode, final Rationale rationale) {
                // 这里使用自定义对话框,如果不想自定义,用AndPermission默认对话框:
                // AndPermission.rationaleDialog(Context, Rationale).show();
    
                // 自定义对话框。
                com.yanzhenjie.alertdialog.AlertDialog.build(MainActivity.this)
                        .setTitle(R.string.title_dialog)
                        .setMessage(R.string.message_permission_rationale)
                        .setPositiveButton(R.string.btn_dialog_yes_permission, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.cancel();
                                rationale.resume();
                            }
                        })
    
                        .setNegativeButton(R.string.btn_dialog_no_permission, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.cancel();
                                rationale.cancel();
                            }
                        })
                        .show();
            }
        };
        //----------------------------------SD权限----------------------------------//
    
        @PermissionYes(REQUEST_CODE_PERMISSION_SD)
        private void getMultiYes(List<String> grantedPermissions) {
            Toast.makeText(this, R.string.message_post_succeed, Toast.LENGTH_SHORT).show();
        }
    
        @PermissionNo(REQUEST_CODE_PERMISSION_SD)
        private void getMultiNo(List<String> deniedPermissions) {
            Toast.makeText(this, R.string.message_post_failed, Toast.LENGTH_SHORT).show();
    
            // 用户否勾选了不再提示并且拒绝了权限,那么提示用户到设置中授权。
            if (AndPermission.hasAlwaysDeniedPermission(this, deniedPermissions)) {
                AndPermission.defaultSettingDialog(this, REQUEST_CODE_SETTING)
                        .setTitle(R.string.title_dialog)
                        .setMessage(R.string.message_permission_failed)
                        .setPositiveButton(R.string.btn_dialog_yes_permission)
                        .setNegativeButton(R.string.btn_dialog_no_permission, null)
                        .show();
    
                // 更多自定dialog,请看上面。
            }
        }
    
        //----------------------------------权限回调处理----------------------------------//
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
                grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            /**
             * 转给AndPermission分析结果。
             *
             * @param object     要接受结果的Activity、Fragment。
             * @param requestCode  请求码。
             * @param permissions  权限数组,一个或者多个。
             * @param grantResults 请求结果。
             */
            AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            switch (requestCode) {
                case REQUEST_CODE_SETTING: {
                    Toast.makeText(this, R.string.message_setting_back, Toast.LENGTH_LONG).show();
                    //设置成功,再次请求更新
                    getUpdate(AppUtils.getVersionCode(MainActivity.this));
                    break;
                }
            }
        }
    
        private void update() {
            //安装应用
            Intent intent = new Intent(Intent.ACTION_VIEW);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                Uri contentUri = FileProvider.getUriForFile(MainActivity.this, "com.demo.mymobilephonedemo.fileprovider",new File(Environment
                          .getExternalStorageDirectory(), DOWNLOAD_NAME));
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            } else {
                intent.setDataAndType(Uri.fromFile(new File(Environment
                          .getExternalStorageDirectory(), DOWNLOAD_NAME)), "application/vnd.android.package-archive");
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            MainActivity.this.startActivity(intent);
        }
    }
    
    

    显示效果图:

    下载安装

    github地址:https://github.com/wangxin3119/UpdateDemo

    5. 通过第三方的library来更新数据

      现在有很多开源的包,站在巨人的肩膀上可能做不到,但是借把力还是可以的。我在自己的项目中用了这个包,来进行版本更新,有兴趣的朋友可以自己去看看。使用起来也很简单。

    1. 首先在Android Studio 项目中的build.gradle文件中配置
    allprojects {
        repositories {
            maven { url "https://www.jitpack.io" }
        }
    }
    ...
    dependencies {
        compile 'com.github.yaming116:UpdateApp:1.0.2'
        ...
    }
    
    1. 然后在代码中使用
    UpdateService.Builder.create(URL)
                 .setStoreDir("update")
                 .setIcoResId(R.mipmap.ic_launcher)
                 .setIsSendBroadcast(true)
                 .setDownloadSuccessNotificationFlag(Notification.DEFAULT_SOUND)
                 .setDownloadErrorNotificationFlag(Notification.DEFAULT_SOUND)
                 .setUpdateProgress(1)
                 .build(this);
    
    参数 描述
    downloadUrl 下载地址
    icoResId Notification 的icon,默认应用的icon
    icoSmallResId Notification 右下角的icon,默认应用的icon
    storeDir 保存在sdcard路径,默认在sdcard/Android/package/update
    updateProgress 刷新notification 进度条,默认每次下载1%更新一次
    downloadNotificationFlag 下载进行中的Notification Flag
    downloadErrorNotificationFlag 下载失败的Notification Flag
    downloadSuccessNotificationFlag 下载成功的Notification Flag
    isSendBroadcast 是否会发送下载状态广播

    github地址:https://github.com/yaming116/UpdateApp

    相关文章

      网友评论

        本文标题:第三章 Android 版本更新

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