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

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

  1. Android应用程序的自动更新升级(自身升级、通过tomcat)(转)

    Android应用程序的自动更新升级(自身升级.通过tomcat) http://blog.csdn.net/mu0206mu/article/details/7204746 刚入手android一个 ...

  2. Android应用程序更新并下载

    创建一个新类,名为UpdateManager,代码如下: package com.af.service; import java.io.BufferedReader; import java.io.F ...

  3. Android数据自己主动更新库DataAutoRefresh

    非常多android应用.比方音乐播放器.视频播放器.小说阅读器或者其他须要获取本地磁盘指定数据格式数据列表的应用,在磁盘数据有变化(新增或者删除.比方下载完毕,拔TF卡.换TF卡)时.须要自己主动更 ...

  4. 【实用篇】Android之应用程序实现自动更新功能

    我个人用的是友盟提供的自动更新组件,因此在这里只描述如何实用友盟提供的组件来完成程序的自动更新,步骤如下: 1.登录友盟官网,点击注册一个友盟账号. 2.注册成功后将会自动进入到添加新应用界面,选择添 ...

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

    Android应用检查版本更新后,在通知栏下载,更新下载进度,下载完成自动安装,效果图如下: 检查当前版本号 AndroidManifest文件中的versionCode用来标识版本,在服务器放一个新 ...

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

    简单实现了微信自己主动抢红包的服务,原理就是依据keyword找到对应的View, 然后自己主动点击.主要是用到AccessibilityService这个辅助服务,基本能够满足自己主动抢红包的功能, ...

  7. 最全的PHP开发Android应用程序

    第一部分是指在Android系统的手机上直接写PHP脚本代码并立即运行: 第二部分则继续讲解如何把写好的PHP脚本代码打包成akp安装文件. 首先,在手机上安装两个apk包. 一个是SL4A(Scri ...

  8. PHP开发Android应用程序(转)

    第一部分是指在Android系统的手机上直接写PHP脚本代码并立即运行:第二部分则继续讲解如何把写好的PHP脚本代码打包成akp安装文件. 首先,在手机上安装两个apk包. 一个是SL4A(Scrip ...

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

    在Android系统中,键盘按键事件是由WindowManagerService服务来管理的,然后再以消息的形 式来分发给应用程序处理,不过和普通消息不一样,它是由硬件中断触发的:在上一篇文章< ...

随机推荐

  1. Linux--对文件夹下的配置文件批量改动IP

    sed -i 's/10.1.1.1/10.1.1.2/g' `grep -ir 10.1.1.1 * |grep -E '.xml:|.cfg:|.ini:|.wsdl:|.properties:' ...

  2. SQL语法精讲(包括建库、建表、建视图、查询、增加、删除、)

    SQL语法精讲(包括建库.建表.建视图.查询.增加.删除.修改) SQL分类: DDL—数据定义语言(CREATE,ALTER,DROP,DECLARE) DML—数据操纵语言(SELECT,DELE ...

  3. poj1286 Necklace of Beads【裸polya】

    非常裸的polya,只是我看polya看了非常久 吉大ACM模板里面也有 #include <cstdio> #include <cmath> #include <ios ...

  4. 2015.05.15,外语,学习笔记-《Word Power Made Easy》 01 “如何讨论人格特点”

    2015.03.17,外语,读书笔记-<Word Power Made Easy> 01 “如何讨论人格特点”学习笔记 SESSIONS 1 本来这些章节都是在一两年前学习的,现在趁给友人 ...

  5. netty结构

    一.先纵览一下Netty,看看Netty都有哪些组件? 为了更好的理解和进一步深入Netty,我们先总体认识一下Netty用到的组件及它们在整个Netty架构中是怎么协调工作的.Netty应用中必不可 ...

  6. Vmware 安装虚拟工具 (二)

    打开虚拟机,以root超级用户登陆,菜单栏选择虚拟机,install安装虚拟机 拷贝虚拟工具到 在根目录下建立文件夹,并将工具拷贝到该文件夹,例如vmtool 打开终端,进入该目录开始安装 如图,进入 ...

  7. ubuntu中不能远程连接解决

    今天装好ubuntu19.04之后不能远程连接,网上找了很久终于自己解决了.ap 步骤如下:希望对各位有用,哪里不对请指出 第一步我们需要加载openssh-server   等待加载完毕后,     ...

  8. Core篇——初探Core的Http请求管道&&Middleware

    目录: 1.Core 处理HTTP请求流程 2.中间件(Middleware)&&处理流程 3.创建自定义中间件&&模拟Core的请求管道 Core 处理HTTP请求流 ...

  9. iOS11中navigationBar上 按钮图片设置frame无效 不受约束 产生错位问题 解决

    问题描述: 正常样式: 在iOS 11 iPhone X上显示效果: 观察顶部navBar上的左侧按钮  在ios 11 上  这个按钮的图片不受设置的尺寸约束,按其真实大小展示,造成图片错位,影响界 ...

  10. 移除HTML5 input在type="search"时的清除按钮

    input[type="search"]::-webkit-search-cancel-button { display: none; }