前言
App的版本升级迭代,是每一个开发者必须要考虑的事情。App的bug修复、新功能的开发都需要用户下载新的安装包,安装新版本来实现App的更新。
这篇文章主要就是介绍如何提示并引导用户在应用内下载最新的安装包并且安装新版本。
一、下载安装包
下载文件使用系统自带的DownloadManager实现。
1.什么是DownloadManager
下载管理器是一项系统服务,可处理长时间运行的HTTP下载。 客户端可以请求将URI下载到特定的目标文件。 下载管理器将在后台进行下载,处理HTTP交互,并在出现故障或在连接更改和系统重新启动后重试下载。
看一下DownloadManager中两个比较重要的类:
- DownloadManager.Request 包含一个下载任务所需要的所有信息,例如文件下载地址、文件存储路径、通知栏内容、文件种类等等。
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl)); //下载地址
request.setTitle("软件更新") //通知栏标题
.setDescription("新版安装包正在下载中...") //通知栏描述
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) //通知栏展示方式
.setAllowedOverMetered(true) //是否可以通过计量网络进行连接
.setAllowedOverRoaming(true) //是否可以通过漫游网络进行连接
.setMimeType("application/vnd.android.package-archive") //设置文件类型
.setVisibleInDownloadsUi(true); //是否显示系统下载进度
指定目标存储路径
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
File file = new File(activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), getFileName(activity));
Uri uri = Uri.fromFile(file);
activity.grantUriPermission(activity.getPackageName(), uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
//如果使用content://开头 的 Uri 指定下载目标路径,会报错java.lang.IllegalArgumentException: Not a file URI
//必须还是以file://开头的
request.setDestinationUri(uri);
} else {
PermissionUtil.requestPermissiom(activity, new OnPermissionListener() {
/**
* 同意授权
*/
@Override
protected void grant() {
super.grant();
request.setDestinationInExternalFilesDir(activity, Environment.DIRECTORY_DOWNLOADS, getFileName(activity));
}
}, Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
开始下载
DownloadManager downloadManager = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
//将下载请求加入下载队列,加入下载队列后会给该任务返回一个long型的id,通过该id可以取消任务,重启任务、获取下载的文件等等
if (downloadManager != null) {
long downloadId = downloadManager.enqueue(request);
}
关于版本适配需要注意的几点:
1、从Android M开始,存储文件到手机需要动态申请WRITE_EXTERNAL_STORAGE
权限
2、设置文件存储路径在Android Q版本之前使用的是request.setDestinationInExternalFilesDir(Context context, String dirType, String subPath)
方法,而在Android Q及以上版本提供了新方法setDestinationUri(Uri uri)
3、setDestinationUri
方法的参数Uri如果使用content://开头 的 Uri 指定下载目标路径,会报错java.lang.IllegalArgumentException: Not a file URI
,必须使用以file://开头的Uri
4、对于setVisibleInDownloadsUi
方法,从Android Q开始,只有将文件保存到公共下载目录(Context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS))时才能在通知栏显示下载进度。
对于上述第4点我本人还存在一些疑惑,按照Api文档的说法,仅当文件下载地址为getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)时才能在通知栏显示下载进度。但是getExternalStoragePublicDirectory已经明确标示为Deprecated方法,并且推荐我们使用getExternalFilesDir方法访问存储空间,希望有知道的大神给我解答疑惑,非常感谢。
- DownloadManager.Query 过滤下载器查询条件,例如查询指定下载任务的状态。
/**
* 查询指定下载任务的状态
*
* @param downloadId 下载任务的id,DownloadManager.enqueue(request)时返回
*/
private static int getDownloadStatus(long downloadId, DownloadManager downloadManager) {
DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId);
Cursor cursor = downloadManager.query(query);
if (cursor != null && cursor.moveToNext()) {
return cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
}
return DownloadManager.ERROR_UNKNOWN;
}
大部分App都需要在安装包下载完成的时候,直接启动安装器帮助用户完成新版本的安装。我们可以使用轮询或广播检查下载任务的状态,当status等于DownloadManager.STATUS_SUCCESSFUL的时候就可以开始安装工作。
这些都是DownloadManager最基本的使用方法,想要了解更多DownloadManager Api可查阅官方文档https://developer.android.google.cn/reference/android/app/DownloadManager。
二、安装apk
关于版本适配
Android N版本开始,使用file://开头的Uri可能会触发FileUriExposedException,所以需要通过FileProvider的方式创建Uri,具体方式可以参考https://blog.csdn.net/lmj623565791/article/details/72859156。
Android O版本开始,安装apk需要申请REQUEST_INSTALL_PACKAGES【安装未知来源应用权限】。
//获取文件Uri
public static Uri getUri(Context context, File file) {
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//第二个参数为 包名.fileprovider
uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
} else {
uri = Uri.fromFile(file);
}
return uri;
}
//安装apk
private static void tryInstallApk(Activity activity) {
File downloadFile = getDownloadFile(activity);
if (!downloadFile.exists()) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
//Android7.0之后需要使用FileProvider创建Uri
Uri apkUri = FileUtil.getUri(activity, getDownloadFile(activity));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
//Android8.0开始需要获取应用内安装权限
boolean allowInstall = activity.getPackageManager().canRequestPackageInstalls();
//如果还没有授权安装应用,去设置内开启应用内安装权限
if (!allowInstall) {
//注意这个是8.0新API
Uri packageUri = Uri.parse("package:" + activity.getPackageName());
Intent intentX = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageUri);
activity.startActivityForResult(intentX, GET_UNKNOWN_APP_SOURCES);
return;
}
}
//Android N开始必须临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(intent);
}
//在设置界面授权完成后回到应用,检查是否授权成功。如果授权成功,执行安装;否则提示用户授权被拒绝
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == GET_UNKNOWN_APP_SOURCES) {
//8.0应用设置界面未知安装开源返回时候
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean allowInstall = getPackageManager().canRequestPackageInstalls();
if (allowInstall) {
// 执行安装app的逻辑
} else {
// 拒绝权限逻辑
ToastUtil.showShort(getApplicationContext(), "您拒绝了安装未知来源应用,应用暂时无法更新");
}
}
}
}
}
归纳总结
- Android M版本开始,存储文件到手机需要动态申请WRITE_EXTERNAL_STORAGE权限
- Android N版本开始,使用file://开头的Uri可能会触发FileUriExposedException,所以需要通过FileProvider的方式创建Uri,具体方式可以参考https://blog.csdn.net/lmj623565791/article/details/72859156。
- Android O版本开始,安装apk需要申请REQUEST_INSTALL_PACKAGES【安装未知来源应用权限】。
- Android Q版本开始,改变了应用程序访问设备外部存储上文件的方式。具体变更可查看官方文档https://developer.android.google.cn/about/versions/10/privacy/changes。这些存储方式的变更,会影响我们对DownloadManager的使用,主要体现为:
1、设置文件存储路径在Android Q版本之前使用的是request.setDestinationInExternalFilesDir(Context context, String dirType, String subPath)方法,而在Android Q及以上版本提供了新方法setDestinationUri(Uri uri)。
2、setDestinationUri方法的参数Uri如果使用content://开头 的 Uri 指定下载目标路径,会抛异常java.lang.IllegalArgumentException: Not a file URI,必须使用以file://开头的Uri。
3、对于setVisibleInDownloadsUi方法,从Android Q开始,只有将文件保存到公共下载目录(Context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS))时才能在通知栏显示下载进度。