AndroidAndroid程序自己主动更新

Posted gavanwanggw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AndroidAndroid程序自己主动更新相关的知识,希望对你有一定的参考价值。

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功能介绍及使用演示样例


以上是关于AndroidAndroid程序自己主动更新的主要内容,如果未能解决你的问题,请参考以下文章

AndroidAndroid安全机制

确保 Xcode 每次 Build 时都自己主动更新资源

Android数据自己主动更新库DataAutoRefresh

Unity5 怎样做资源管理和增量更新

自己主动更新 -- 版本比較

AndroidAndroid开发小功能,倒计时的实现。时间计时器倒计时功能。