zoukankan      html  css  js  c++  java
  • 【Android】Android程序自己主动更新

    App自己主动更新的步骤可分为三步:

    1. 检查更新(假设有更新进行第2步,否则返回)
    2. 下载新版的APK安装包
    3. 安装APK

    以下对这三步进行解释。当中会穿插相应代码。App自己主动更新的这三步所有被封装到了一个单独的Updater类中,能够直接拿来使用,我会在文章最后贴出源代码github地址。

    Updater 使用演示样例

    通过单一的类Updater能够方便的实现自己主动检查更新、下载安装包和自己主动安装。能够监听下载进度,能够自己定义更新提示等。保存路径能够自由书写,假设路径中某个文件夹不存在会自己主动创建。流式API接口易于使用。以下是使用演示样例。一行代码搞定自己主动更新:

    String savePath = Environment.getExternalStorageDirectory() 
                        + "/whinc/download/whinc.apk";
    String updateUrl = "http://192.168.1.168:8000/update.xml";
    Updater.with(mContext)
            .downloadListener(mListener)
            .update(updateUrl)
            .save(savePath)
            .create()
            .checkUpdate();

    这里写图片描写叙述

    第一步:检查更新

    这一步须要服务端的配合。服务端存放一个XML格式的配置文件(也能够用JSON或其它格式)提供给client检查更新。update.xml 格式例如以下:

    <?xml version="1.0" encoding="utf-8"?>
    <info>
        <version>
            <code>4</code>
            <name>1.0.4</name>
        </version>
        <url>http://192.168.1.168:8000/test.apk</url>
        <description>更新 - 吧啦吧啦;修复 - 吧啦吧啦;添加 - 巴拉巴拉巴</description>
    </info>
    • <version>标签指定服务端的版本号号和版本号名称,该版本号号和版本号名称相应Android项目配置里的versionCodeversionName(Eclipse ADT项目可在 AndroidManifest.xml中的标签中找到。Android Studio项目在module的build.gradle中的defaultConfig中找到)。
    • <url>标签指定APK的下载地址,
    • <description>标签指定更新内容。

    client通过 HTTP 请求服务端的 update.xml文件。然后解析 update.xml,比較服务端的版本号号与本地版本号号,假设服务端版本号号大于本地版本号号说明有更新,则依据 update.xml中指定的APK下载地址下载最新的APK,以下将会具体说明。

    以下是检查更新的代码:

        /**
         * 检查 App 版本号号
         *
         * @return 假设有新版本号返回true。否则返回false
         */
        private boolean checkVersion() {
            URL url;
            HttpURLConnection httpConn = null;
            try {
                url = new URL(mCheckUpdateUrl);
                httpConn = (HttpURLConnection) url.openConnection();
                httpConn.setConnectTimeout(200000);
                httpConn.setReadTimeout(200000);
                httpConn.setUseCaches(false);       // disable cache for current http connection
                httpConn.connect();
                if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    InputStream inputStream = httpConn.getInputStream();
                    // 解析 XML 数据
                    if (!parseXml(inputStream)) {
                        return false;
                    }
                    // 比較本地版本号号与服务器版本号号
                    PackageInfo packageInfo = mContext.getPackageManager()
                            .getPackageInfo(mContext.getPackageName(), 0);
                    if (packageInfo.versionCode < mRemoteVersionCode) {
                        return true;
                    }
                } else {
                    return false;
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            } finally {
                httpConn.disconnect();
            }
    
            return false;
        }

    首先创建HTTPURLConnection訪问服务端update.xml文件,然后解析服务端返回的update.xml文件,并保存版本号信息、APK下载地址和更新日志。解析完后通过获取当前client的版本号号与服务端版本号号比較。假设服务端版本号号更大,说明服务端有更新的版本号。checkVersion() 方法返回true,否则返回false。

    以下时检查更新的代码。须要注意的是。Android中不同意在主线程(UI线程)中发起网络请求,所以checkVersion()的调用须要放在非主线程中。实现异步请求的方式有多种,这里我使用 AsyncTask。

        public void checkUpdate() {
            new AsyncTask<Void, Void, Boolean>() {
    
                @Override
                protected Boolean doInBackground(Void... params) {
                    boolean hasNewVersion = checkVersion();
                    return hasNewVersion;
                }
    
                @Override
                protected void onPostExecute(Boolean hasNewVersion) {
                    super.onPostExecute(hasNewVersion);
    
                    if (mCheckUpdateListener == null
                            || !mCheckUpdateListener.onCompleted(hasNewVersion, mRemoteVersionCode,
                            mRemoteVersionName, mUpdateLog, mApkDownloadUrl)) {
                        if (hasNewVersion) {
                            showUpdateDialog();
                        }
                    }
                }
            }.execute();
        }

    下载新版的APK安装包

    showUpdateDialog()调用后显示更新提示对话框,在对话框确认button点击事件中,首先创建DownloadManager.Request对象,然后设置该对象的各种属性例如以下载保存路径、通知栏标题等,最后将该下载请求放到系统服务DownloadManager的下载队列中。交给系统去处理下载逻辑。 为了监听下载完毕事件,代码里注冊了广播DownloadManager.ACTION_DOWNLOAD_COMPLETE。下载进度通过注冊ContentObserver来监听。

        /**
         * 显示更新对话框
         */
        private void showUpdateDialog() {
            AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
            builder.setTitle(mTitle);
            builder.setMessage(mUpdateLog);
            builder.setPositiveButton(mDialogOkBtnTxt, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
    
                    // 后台下载
                    mDownloadMgr = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
                    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mApkDownloadUrl));
                    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                        // 假设保存路径包括子文件夹,须要先递归创建文件夹
                        if (!createDirIfAbsent(mSavePath)) {
                            Log.e("TAG", "apk save path can not be created:" + mSavePath);
                            return;
                        }
    
                        request.setDestinationUri(Uri.fromFile(new File(mSavePath)));
                        request.setTitle(mNotificationTitle);
                        request.setTitle(mNotificationMessage);
                        // 注冊广播,监听下载完毕事件
                        mContext.registerReceiver(mCompleteReceiver,
                                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
                        // 注冊监听下载进度
                        mContext.getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"),
                                true, mContentObserver);
                        mDownloadId = mDownloadMgr.enqueue(request);
                    } else {
                        Log.e("TAG", "can not access external storage!");
                        return;
                    }
                    Toast.makeText(mContext, "正在后台下载...", Toast.LENGTH_SHORT).show();
                }
            });
            builder.setNegativeButton(mDialogCancelBtnTxt, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.cancel();
                }
            });
            builder.create().show();
        }
    
        /**
         * 假设參数 path 指定的路径中的文件夹不存在就创建指定文件夹
         *
         * @param path 绝对路径(包括文件名称,比如 '/sdcard/storage/download/test.apk')
         * @return 假设成功创建文件夹返回true,否则返回false
         */
        private boolean createDirIfAbsent(String path) {
            String[] array = path.trim().split(File.separator);
            List<String> dirNames = Arrays.asList(array).subList(1, array.length - 1);
            StringBuilder pathBuilder = new StringBuilder(File.separator);
            for (String d : dirNames) {
                pathBuilder.append(d);
                File f = new File(pathBuilder.toString());
                if (!f.exists() && !f.mkdir()) {
                    return false;
                }
                pathBuilder.append(File.separator);
            }
            return true;
        }
    

    安装APK

    一旦Apk下载完毕就会收到广播消息,此时能够运行安装APK的动作,只是要先通过下载Id推断该广播事件是否是由于我们的APK下载完毕发出的,由于系统可能同一时候有多个下载任务,通过下载id区分。

            mCompleteReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                    if (downloadId == mDownloadId) {
                        installApk();
                        release();
                    }
                }
            };

    以下是 installApk() 方法,首先通过下载Id从DownloadManager中检索到下载的APK存储路径,然后通过Intent安装下载的APK,代码很easy。注意,Intent设置标识为Intent.FLAG_ACTIVITY_NEW_TASK。否则不能正常启动安装程序。

        /**
         * 替换安装当前App。注意:签名一致
         */
        private void installApk() {
            // 获取下载的 APK 地址
            Uri apkUri = mDownloadMgr.getUriForDownloadedFile(mDownloadId);
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
            mContext.startActivity(intent);
        }

    github 源代码

    whinc/Android-UpdateManager

    比較好的參考资料:

    DownloadManager | Android Developers
    Android系统下载管理DownloadManager功能介绍及使用演示样例

  • 相关阅读:
    【算法学习笔记】27.动态规划 解题报告 SJTU OJ 1254 传手绢
    【算法学习笔记】26.扫描维护法 解题报告 SJTU OJ 1133 数星星
    【算法学习笔记】25.贪心法 均分纸牌问题的分析
    【算法学习笔记】24.记忆化搜索 解题报告 SJTU OJ 1002 二哥种花生
    【算法学习笔记】23.动态规划 解题报告 SJTU OJ 1280 整装待发
    【算法学习笔记】22.算法设计初步 二分查找 上下界判断
    【算法学习笔记】21.算法设计初步 求第k个数 划分法 快排法
    【算法学习笔记】20.算法设计初步 归并排序 求逆序数
    【算法学习笔记】19.算法设计初步 最大子列和问题的几种方法
    【算法学习笔记】18.暴力求解法06 隐式图搜索2 八数码问题 未启发
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/7073310.html
Copyright © 2011-2022 走看看