#适配Android7.0版本更新功能
测试手机:华为 Android 7.0
问题1 升级安装失败,程序崩溃:
前面有一篇文章讲过了使用DownloadManager在应用内实现版本更新 详见 Android学习之----利用DownLoadManager实现版本升级,之前Android 7.0也还没有普及所以一直没有更新代码,没注意到Android7.0的一些版本变化,导致了在Android 7.0 上面使用之前的代码更新安装程序出错,具体错误为:
Caused by: java.lang.SecurityException: COLUMN_LOCAL_FILENAME is deprecated; use ContentResolver.openFileDescriptor() instead
at android.app.DownloadManager$CursorTranslator.getString(DownloadManager.java:1503)
会抛出这样一个异常终止程序运行。那么导致整个问题的主要原因就是Android 7.0在文件管理方面更加严格,不随便对外开发文件访问权限,想要访问文件可以使用FileProvider的方式来访问,在7.0系统中,Android禁止通过file://这样的方式来访问,需要使用content://这样的方式来对文件进行访问。 所以在我之前代码中的读取文件更新就会出现这个问题,不过现在我们来对这个问题进行修复。
#问题2
安装出现安装包解析异常(图片引用其他文章)
我也网上到处翻查资料 大概有这样几种情况,
1 v1,v2签名的原因,
2 apk的签名不一致
3 安装文件路径问题
也可以参考 android升级安装包–包解析错误 这篇文章,但是我只遇到过以上的2,3问题,其中2的问题好解决,保证签名一致即可,第3个问题,就又可以牵扯到7.0的安装路径问题上。只要能够找到正确的文件路径,那么就可以正确的安装。
解决办法:
既然知道了问题原因,那么我们就查找对应的解决办法。
第一步骤:了解FileProvider的使用 可以参考官网例子: https://developer.android.google.cn/reference/android/support/v4/content/FileProvider.html
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.mydomain.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
第二步骤:在manifest中 application中 添加provider标签,修改provider中 android:authorities为你自己的名称 建议使用包名+fileprovider
在res文件夹下面新建一个xml目录,新建一个file_paths文件,上面meta-data中使用到的。
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="download" path=""/>
</paths>
上面的代码paths中不止external-path这一种标签属性,还有其他的,这里不作详细讲解,步骤一里面给出的官网地址里面解释的比我清楚,不过声明这个地址表示 在这个对应的路径下的文件都是可以被共享访问的。
第三步骤:编写代码适配 android7.0,这里我只给出下载部分和安装部分的代码,因为对比之前 Android学习之----利用DownLoadManager实现版本升级这篇文章除了加上第一步骤和第二步骤以外,其他的就修改了以下给出的代码,
下载部分修改实现细节
1 添加下载类型 -----> request.setMimeType("application/vnd.android.package-archive");
2 创建需要保存的File ---> downloadFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "demo.apk");
3 修改------> request.setDestinationInExternalFilesDir() 为request.setDestinationUri()
4 调用 request.setDestinationUri(Uri.fromFile(downloadFile));
更新安装部分实现细节修改
1 兼容7.0 ---判断系统版本
2 使用FileProvider获取文件uri
具体代码
下载部分
public void gotoUpdate(Context context, String url) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setMimeType("application/vnd.android.package-archive"); //修改
downloadFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "demo.apk"); //修改
request.setDestinationUri(Uri.fromFile(downloadFile)); //修改
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("下载新版本");
request.setVisibleInDownloadsUi(true);
long downloadId = downloadManager.enqueue(request);
DownCompleteReceiver downCompleteReceiver = new DownCompleteReceiver(downloadId);
context.registerReceiver(downCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
兼容安装部分
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", downloadFile); //修改 downloadFile 来源于上面下载文件时保存下来的
// BuildConfig.APPLICATION_ID + ".fileprovider" 是在manifest中 Provider里的authorities属性定义的值
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //临时授权
installIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
installIntent.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(installIntent);
} else {
// 获取下载好的 apk 路径
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
// 提示用户安装
installAPP(Uri.parse("file://" + uriString), context);
}
总的步骤就是
1 manifest文件中添加Provider声明,声明好自己的authorities值,
2 在res/xml 目录中新增 file_paths.xml文件,声明需要共享的文件路径
3 使用DownloadManager下载文件的时候使用setDestinationUri的方式,保存事先创建的file对象,
4 通过版本判断兼容Android7.0以上以及以下的版本
5 通过fileProvider获取到之前file对象的uri
6 添加flag 临时授权uri权限
VersionUpdate.java 可以直接拷贝一下代码测试 只需要使用VersionUpdate.newInstance().gotoUpdate()方法即可
public class VersionUpdate {
private static VersionUpdate versionUpdate = new VersionUpdate();
File downloadFile;
public static VersionUpdate newInstance() {
return versionUpdate;
}
private VersionUpdate() {
}
public void gotoUpdate(Context context, String url) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(context.DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setMimeType("application/vnd.android.package-archive");
downloadFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "demo.apk");
request.setDestinationUri(Uri.fromFile(downloadFile));
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setTitle("下载新版本");
request.setVisibleInDownloadsUi(true);
long downloadId = downloadManager.enqueue(request);
DownCompleteReceiver downCompleteReceiver = new DownCompleteReceiver(downloadId);
context.registerReceiver(downCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
public void createDialogUpdate(final Context context, final String url) {
AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle("更新提示")
.setMessage("发现新版本,是否立即更新?")
.setCancelable(false).setNeutralButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
gotoUpdate(context, url);
dialog.dismiss();
}
});
alert.create().show();
}
public class DownCompleteReceiver extends BroadcastReceiver {
long enqueueId;
public DownCompleteReceiver(long enqueueId) {
this.enqueueId = enqueueId;
}
@Override
public void onReceive(Context context, Intent intent) {
DownloadManager dm = (DownloadManager) context.getSystemService(context.DOWNLOAD_SERVICE);
long id = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (enqueueId != id) {
return;
}
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(enqueueId);
Cursor c = dm.query(query);
if (c != null && c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
// 下载失败也会返回这个广播,所以要判断下是否真的下载成功
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", downloadFile);
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
installIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intent);
} else {
// 获取下载好的 apk 路径
String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
// 提示用户安装
installAPP(Uri.parse("file://" + uriString), context);
}
}
c.close();
}
}
private void installAPP(Uri data, Context context) {
Intent promptInstall = new Intent(Intent.ACTION_VIEW)
.setDataAndType(data, "application/vnd.android.package-archive");
// FLAG_ACTIVITY_NEW_TASK 可以保证安装成功时可以正常打开 app
promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(promptInstall);
}
}
}
2017/9/19 22:16:41 好记性不如烂笔头。