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

时间: 2024-11-05 16:34:55

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

Android数据自己主动更新库DataAutoRefresh

非常多android应用.比方音乐播放器.视频播放器.小说阅读器或者其他须要获取本地磁盘指定数据格式数据列表的应用,在磁盘数据有变化(新增或者删除.比方下载完毕,拔TF卡.换TF卡)时.须要自己主动更新数据列表.非常多android应用都是须要用户手动去刷新才会更新列表,通过DataAutoRefresh能够实现自己主动更新. project和Demo放在GitHub上了,project在实际使用中须要不断改进,DataAutoRefresh下载地址

Android程序版本更新--通知栏更新下载安装(转)

Android应用检查版本更新后,在通知栏下载,更新下载进度,下载完成自动安装,效果图如下: 检查当前版本号 AndroidManifest文件中的versionCode用来标识版本,在服务器放一个新版本的apk,versioncode大于当前版本,下面代码用来获取versioncode的值 PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); int

Android实现微信自己主动抢红包的程序

简单实现了微信自己主动抢红包的服务,原理就是依据keyword找到对应的View, 然后自己主动点击.主要是用到AccessibilityService这个辅助服务,基本能够满足自己主动抢红包的功能,可是有些逻辑须要优化,比方,拆完一个红包后,必须手动点击返回键,才干进行下一次自己主动抢红包. AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:and

【苦读官方文档】2.Android应用程序基本原理概述

官方文档原文地址 应用程序原理 Android应用程序是通过Java编程语言来写.Android软件开发工具把你的代码和其它数据.资源文件一起编译.打包成一个APK文件,这个文档以.apk为后缀,保存了一个Android应用程序全部的内容.Android设备通过它来安装相应的应用. 一旦安装到设备上.每一个Android应用程序就执行在各自独立的安全沙盒中: Android系统是一个多用户的Linux系统.每一个应用都是一个用户. Android系统默认会给每一个应用分配一个唯一的用户ID(这个

12个被滥用的Android应用程序权限

Android应用程序须要权限才干正常运作,只是网络犯罪分子会将其用在个人私利上.来看看最常被要求的权限以及它们会怎样被滥用. 1.网络定位功能 这代表什么:同意应用程序通过网络定位(像是基地台或无线网络)来取得大概位置.应用程序开发者能够用它从基于位置的广告获利. 它怎样被滥用:恶意应用程序用它来发动基于位置的攻击或恶意软件.例如说,网络犯罪分子能够将俄罗斯的移动用户导到恶意俄文站点. 须要此权限的应用程序:位置相关应用程序,打卡应用程序 2.全球定位系统 这代表什么:同意应用程序通过全球定位

在 Android 应用程序中使用 SQLite 数据库以及怎么用

part one : android SQLite 简单介绍 SQLite 介绍 SQLite 一个非常流行的嵌入式数据库.它支持 SQL 语言,而且仅仅利用非常少的内存就有非常好的性能.此外它还是开源的,不论什么人都能够使用它.很多开源项目((Mozilla, PHP, Python)都使用了 SQLite. SQLite 由下面几个组件组成:SQL 编译器.内核.后端以及附件.SQLite 通过利用虚拟机和虚拟数据库引擎(VDBE).使调试.改动和扩展 SQLite 的内核变得更加方便. 图

Android应用程序键盘(Keyboard)消息处理机制分析

在Android系统中,键盘按键事件是由WindowManagerService服务来管理的,然后再以消息的形 式来分发给应用程序处理,不过和普通消息不一样,它是由硬件中断触发的:在上一篇文章<Android应用程序消息处理机制(Looper. Handler)分析>中,我们分析了Android应用程序的消息处理机制,本文将结合这种消息处理机制来详细分析Android应用程序是如何获得键 盘按键消息的. 在系统启动的时候,SystemServer会启动窗口管理服务WindowManagerSe

Android应用程序的组成部分

Android应用程序由松散耦合的组件组成,并使用应用程序Manifest绑定到一起:应用程序Manifest描述了每一个组件和它们之间的交互方式,还用于指定应用程序元数据,其硬件和平台要求,外部库以及必需的权限. 下面几个组件提供了应用程序的基本结构模块: Activity:应用程序的表示层.应用程序中的每一个UI都是通过Activity类的一个或多个扩展实现的.Activity使用Fragment和视图来布局和显示信息,以及响应用户动作. Service:应用程序中不可见的工作者.Servi

Android应用程序签名

Android系统要求每一个Android应用程序必须要经过数字签名才能够安卓到系统中.Android通过数字签名来标识应用程序的作者和在应用程序之间建立信任关系,这个数字签名由应用程序的作者完成,并不需要权威的数字证书签名机构认证,它只是用来让应用程序自我认证的. Android应用程序签名的影响 使用同一个签名证书,则不同签名的应用无法覆盖生成的应用程序,即使包名相同,因此,主要的签名影响有以下两点: 1.应用升级.使用相同签名的升级软件可以正常覆盖安装老版本的软件,否则,系统发现签名证书不