【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或其他格式)提供给客户端检查更新,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>标签指定更新内容。

客户端通过 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下载地址和更新日志,解析完后通过获取当前客户端的版本号与服务端版本号比较,如果服务端版本号更大,说明服务端有更新的版本,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()调用后显示更新提示对话框,在对话框确认按钮点击事件中,首先创建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 指定的路径中的目录不存在就创建指定目录,确保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,代码非常简单。注意,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功能介绍及使用示例

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-05 12:26:09

【Android】Android程序自动更新的相关文章

EF-使用迁移技术让程序自动更新数据库表结构

承接上一篇文章:关于类库中EntityFramework之CodeFirst(代码优先)的操作浅析 本篇讲述的是怎么使用迁移技术让程序自动通过ORM框架将模型实体类结构映射到现有数据库,并新增或修改与之对应的表结构. 无论承不承认,都要使用到visual studio的"程序包管理器控制台"执行相关的命令. 1.使用"程序包管理器控制台" 工具>NuGet程序包管理器>程序包管理器控制台 这货的界面是这样子的: 选中默认项目为DAL,因为我们在DAL项目

如何让程序自动更新

如何让程序自动更新 自动更新的软件的目的在于让客户不在为了寻找最新软件花费时间.也不用去到开发商的网站上查找.客户端的软件自动会在程序启动前查找服务器上最新的版本.和自己当前软件的版本比较,如果服务器的是最新版本.客户端则进行自动下载.解压.安装.当然了下载是要有网络的,并且用户可以根据提示去完成操作.再也不用为找不到最新版本的软件而头疼.下面是我的大体思路,已经得到了实现: 1.  写一个webservice,提供一个获取服务器xml中版本的数据的方法.(也可用其他文件格式, 此处举例XML)

使用crontab进行Android代码的自动更新和构建

引子 最近的工作是一个在Android平台上进行开发的项目,我个人基本是不改动Android部分的代码,但是我所在的项目需要使用到Android编译出来的很多目标文件.另一方面,我又不是开发apk等基于通用Android平台的项目,即Android部分的代码是有其他同事在进行维护.那么就会有这样的场景:我需要保持Android部分代码的更新和并构建出来. 编译过整个Android工程的人都知道编译一次的时间大概要30分钟以上(如果你是独占服务器且内存超大,那么请默默走开~),要是整个工程全部进行

转载:Android应用的自动更新模块

软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很流行使用Splash界面, 正好与自动更新配套在一起; 在这个自动更新Splash中, 使用到了 动画设置 ,SharedPerference ,pull解析 ,dialog对话框 ,http网络编程 ,handler 等. 注意一个错误 : 已安装具有该名称和不同签名的数据包 , 早上测试人员报告

Android实现APP自动更新功能

现在一般的android软件都是需要不断更新的,当你打开某个app的时候,如果有新的版本,它会提示你有新版本需要更新.该小程序实现的就是这个功能. 该小程序的特点是,当有更新时,会弹出一个提示框,点击确定,则在通知来创建一个进度条进行下载,点击取消,则取消更新. 以下是详细代码: 1.创建布局文件notification_item.xml,用于在通知栏生成一个进度条和下载图标. <?xml version="1.0" encoding="utf-8"?>

[转]Android应用的自动更新

软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很流行使用Splash界面, 正好与自动更新配套在一起; 在这个自动更新Splash中, 使用到了 动画设置 ,SharedPerference ,pull解析 ,dialog对话框 ,http网络编程 ,handler 等. 注意一个错误 : 已安装具有该名称和不同签名的数据包 , 早上测试人员报告

利用pre平台实现iOS应用程序自动更新

1 // 2 // AppDelegate.m 3 // PreAutoUpdateDemo 4 // 5 // Created by mac on 15/12/18. 6 // Copyright © 2015年 mac. All rights reserved. 7 // 8 9 #import "AppDelegate.h" 10 11 #define USER_KEY @"1234321344SDFDFBVVFGDSVF" // 根据实际情况替换为自己的us

android 软件apk自动更新实现注意点!!

1,解析xml时的NetWorkOnMainThread问题 代码:这里要注意的点就是在访问服务器网络时,不能将InputStream直接返回,因为若直接返回给主线程操作,很可能子线程的InputStream还在获取字节流,这时候就会导致这个UI线程访问网络异常,所以不能直接返回这个InputStream,直接在子线程里 操作这个输入流,然后将操作的结果返回. private class AsyncTask_ConnVersion extends AsyncTask<String, Void,

通用程序自动更新升级

1)服务端IIS网站上创建新的虚拟路径,给新创建的虚拟路径增加MIME类型:.bpl..ini等. 2)设置update.ini文件版本号配置文件 [ver]config.ini=1bplCommon.bpl=1bplGoods.bpl=1bplPower.bpl=1bplPurchasing.bpl=1prjMain.exe=2 3)客户端 untAutoUpdate.dfm文件: object frmAutoUpdate: TfrmAutoUpdate Left = 0 Top = 0 Ca