整个流程分为三步:
1.版本检测
2.新apk文件下载
3.覆盖安装
基本流程就是这样的了, 区别在于怎么安排, 用什么策略更新. 网上的思路基本上是, 在主页里检测apk版本, 需要更新就弹一个对话框给用户, 提醒其更新. 用户确定更新, 就在主页中或者开一个服务, 利用downloadmanager进行文件下载广播注册, 下载完了在广播中进行自动安装(downloadmanager下载完了会发出一条广播).
在实际项目中, 考虑到版本维护的成本, 更新策略选择强制用户更新. 具体做法有点类似与应用商店的静默安装, 有更新就先后台下载, 等到下次打开弹出一个对话框进行安装.
一. 版本检测
思路
一般最新的版本信息会以json文件的形式放在服务器上, 我们要做的就是利用OKhttp3下载这个json文件, 解析取出对应字段, 和本地versionName比较, 不同就进入下一步.
OKhttp3
Android OkHttp完全解析 是时候来了解OkHttp了
Android 一个改善的okHttp封装库
Android Https相关完全解析 当OkHttp遇到Https
本地版本信息获取
PackageManager packageManager = getPackageManager();
#0 means all the flags are turned off
PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0);
String version = packageInfo.versionName;
二. 新apk文件下载
思路
这里使用Google推荐的DownloadManager来进行异步下载
DownloadManager
如果你不想纠结什么断点续传, 网络环境啥的, 那么你就使用DownloadManager吧
//首先, 构建一个下载请求, uri 是你的下载地址,可以使用Uri.parse("http://")包装成Uri对象
DownloadManager.Request req = new DownloadManager.Request(uri);
//通过setAllowedNetworkTypes方法可以设置允许在何种网络下下载
req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// 此方法表示在下载过程中通知栏会一直显示该下载,在下载完成后仍然会显示,
// 直到用户点击该通知或者消除该通知。还有其他参数可供选择
//req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//我希望静悄悄地下载
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
// 设置下载文件存放的路径,同样你可以选择以下方法存放在你想要的位置。
// setDestinationUri
// setDestinationInExternalPublicDir
req.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, title);
// 设置一些基本显示信息
//req.setTitle("Android.apk");
//req.setDescription("下载完后请点击打开");
req.setMimeType("application/vnd.android.package-archive");
// 构建完下载任务, 传入downloadmanager进行下载, 是不是很像我们使用迅雷下载电影
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
long downloadId = dm.enqueue(req);
下载的基本配置就是这样, 还是挺简单的, 并且性能较高.
在实际使用中我们更应该关心的是下载前的准备工作和下载后的善后工作.
下载前的准备工作
首先, 我的需求是发现有新版本, 先下载, 下载完后等到用户再次打开软件进行更新操作. 所以这里就有一个判断最新软件包是否下载完的逻辑判断, 没有就下载, 如果已经就直接进入到安装环节.
/**
* 下载APK文件
*
* @param context
* @param url
* @param info
* @param appName
*/
public void downloadApk(MainActivity mainActivity, final Context context, String url, String info, final String appName) {
//获取存储的下载ID
long downloadId = (long) SPUtil.get(context, DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
if (downloadId != -1) {
//获取当前状态
int status = getDownloadStatus(downloadId);
//状态为下载成功
if (DownloadManager.STATUS_SUCCESSFUL == status) {
//获取下载路径URI
final Uri downloadUri = getDownloadUri(downloadId);
if (downloadUri != null) {
//存在下载的APK,如果两个APK相同,启动更新界面。否之则删除,重新下载。
//安装逻辑......
return;
} else {
//删除下载任务以及文件
mDownloadManager.remove(downloadId);
}
}
start(context, url, info, appName);
} else if (DownloadManager.STATUS_FAILED == status) {
//下载失败,重新下载
start(context, url, info, appName);
} else {
Log.d(context.getPackageName(), "apk is already downloading");
}
} else {
//不存在downloadId,没有下载过APK
start(context, url, info, appName);
}
}
上面代码中涉及获取下载文件以及判断下载状态的操作, 均与downloadmanager有关:
//获取下载文件
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dm.query(query);
if (c != null) {
if (c.moveToFirst()) {
String fileUri = c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI));
// TODO 你可以在这里处理你的文件
}
c.close();
}
//获取下载状态
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor c = dm.query(query);
if (c != null && c.moveToFirst()) {
int status = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
switch (status) {
case DownloadManager.STATUS_PENDING:
break;
case DownloadManager.STATUS_PAUSED:
break;
case DownloadManager.STATUS_RUNNING:
break;
case DownloadManager.STATUS_SUCCESSFUL:
break;
case DownloadManager.STATUS_FAILED:
break;
}
if (c != null) {
c.close();
}
当然, 下载前需要先判断一下能不能下载, WiFi有没有打开, 没有打开跳转到手机设置页
public boolean canDownload() {
try {
int state = mContext.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads");
if (state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|| state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
|| state == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
public void skipToDownloadManager() {
String packageName = "com.android.providers.downloads";
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + packageName));
mContext.startActivity(intent);
}
下载后的善后工作
下载完后, downloadmanager会发送一条DownloadManager.ACTION_DOWNLOAD_COMPLETE广播,并传递downloadId作为参数, 我们可以自定义一个广播接收器接收这条广播完成自动安装. 但这里我们时重启APP后安装, 所以用不到这个广播功能.
但是这里我们要做的工作是安装完后删除apk文件, 虽然很多手机能够自动安装后删除, 但为了以防万一嘛.
这个删除判断放在下载文件前, 逻辑是: 如果存在apk文件并且已经安装过了就删除.
public void removeFile(Context context) {
//获取存储的下载ID
long downloadId = (long) SPUtil.get(context, DownloadManager.EXTRA_DOWNLOAD_ID, -1L);
if (downloadId != -1) {
//或者使用mDownloadManager.remove(downloadId)直接删除文件;
Uri filePath = getDownloadUri(downloadId);
if (filePath != null) {
//删除之前先判断用户是否已经安装了,安装了才删除, 判断逻辑还是检测versionCode
if (!compare(getApkInfo(context, filePath.getPath()), context)) {
File downloadFile = new File(filePath.getPath());
if (null != downloadFile && downloadFile.exists()) {
downloadFile.delete();
}
}
}
}
}
三. 覆盖安装
思路
最后就是覆盖安装了, 看你选择什么策略了, 我这里是直接弹出一个用户不能拒绝的对话框, 让他更新, 以节省后期维护成本
if (compare(getApkInfo(context, downloadUri.getPath()), context)) {
AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity);
builder.setTitle("检测到有新版本!");
builder.setMessage(info);
builder.setCancelable(false);
builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startInstall(context, downloadUri);
}
});
mAlertDialog = builder.create();
handler.sendEmptyMessageDelayed(0, 1000);