收到一个客户报过来的问题,换了新采购的设备之后,下载某个文件,然后下载完成之后就闪退。初步觉得应该是系统版本过高导致的权限问题。
后台查看报错日志
java.lang.RuntimeException: Error receiving broadcast Intent { act=android.intent.action.DOWNLOAD_COMPLETE flg=0x10 pkg=com.xjj.cloud.ypjczd (has extras) }
Caused by: java.lang.SecurityException: COLUMN_LOCAL_FILENAME is deprecated; use ContentResolver.openFileDescriptor() instead
报错日志
原因是使用 DownloadManager.COLUMN_LOCAL_FILENAME 报错,看了下代码,这个已经过时了,源码推荐使用 ContentResolver 来获取文件名。
原来的代码
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downId);
Cursor c = manager.query(query);
String filename = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
使用 ContentResolver 之后
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downId);
Cursor c = manager.query(query);
String filename = "";
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
int fileUriIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
String fileUri = c.getString(fileUriIdx);
if (fileUri != null) {
filename = Uri.parse(fileUri).getPath();
}
} else {
//过时的方式:DownloadManager.COLUMN_LOCAL_FILENAME
int fileNameIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
filename = c.getString(fileNameIdx);
}
获取文件名可以获取了,打开文件又报错。抛出异常
android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/com./files/1529633205126858.pdf exposed beyond app through Intent.getData()
文件打开异常
因为之前直接 Uri.fromFile(apkFile) 构建出一个 Uri ,而这个 Uri 在 7.0 以上系统是不能用的,改使用 FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileProvider", apkFile);
原先打开文件的代码:
String extName = fileName.substring(fileName.lastIndexOf(".") + 1);
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory("android.intent.category.DEFAULT");
File file = new File(fileName);
if (file.exists()) {
intent.setDataAndType(Uri.fromFile(file), getMIMEType(extName));
context.startActivity(intent);
} else {
Toast.makeText(context, "文件不存在", Toast.LENGTH_LONG).show();
}
改动之后
String extName = fileName.substring(fileName.lastIndexOf(".") + 1);
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory("android.intent.category.DEFAULT");
File file = new File(fileName);
if (file.exists()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(context,
context.getApplicationContext().getPackageName() + ".fileprovider", file);
intent.setDataAndType(contentUri, getMIMEType(extName));
context.startActivity(intent);
} else {
intent.setDataAndType(Uri.fromFile(file), getMIMEType(extName));
context.startActivity(intent);
}
} else {
Toast.makeText(context, "文件不存在", Toast.LENGTH_LONG).show();
}
使用 FileProvider 要同时在配置文件中定义 FileProvider ,在application节点底下,与activity节点同级。添加如下代码
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
还要在 res 目录底下创建一个新的 provider_paths.xml 文件。因为我在 AndroidMenifest 文件中定义的目录是 xml/provider_paths.xml ,所以创建的 provider_paths.xml 文件放在 res/xml 目录底下。
这样子就可以在 Android N 7.0 以上系统正常下载文件以及打开文件了。