App自动更新的步骤可分为三步:
- 检查更新(如果有更新进行第2步,否则返回)
- 下载新版的APK安装包
- 安装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项目配置里的versionCode
和versionName
(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 源码
比较好的参考资料:
DownloadManager | Android Developers
Android系统下载管理DownloadManager功能介绍及使用示例
版权声明:本文为博主原创文章,未经博主允许不得转载。