Android|Android N 7.0 以上系统的文件下载报错闪退解决

收到一个客户报过来的问题,换了新采购的设备之后,下载某个文件,然后下载完成之后就闪退。初步觉得应该是系统版本过高导致的权限问题。
后台查看报错日志
java.lang.RuntimeException: Error receiving broadcast Intent { act=android.intent.action.DOWNLOAD_COMPLETE flg=0x10 pkg=com.xjj.cloud.ypjczd (has extras) }
【Android|Android N 7.0 以上系统的文件下载报错闪退解决】Caused by: java.lang.SecurityException: COLUMN_LOCAL_FILENAME is deprecated; use ContentResolver.openFileDescriptor() instead
Android|Android N 7.0 以上系统的文件下载报错闪退解决
文章图片
报错日志 原因是使用 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()

Android|Android N 7.0 以上系统的文件下载报错闪退解决
文章图片
文件打开异常 因为之前直接 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节点同级。添加如下代码

还要在 res 目录底下创建一个新的 provider_paths.xml 文件。因为我在 AndroidMenifest 文件中定义的目录是 xml/provider_paths.xml ,所以创建的 provider_paths.xml 文件放在 res/xml 目录底下。

这样子就可以在 Android N 7.0 以上系统正常下载文件以及打开文件了。

    推荐阅读