Android N及以上版本ListView点击下载文件无法打开问题求助
嗨,我看了你遇到的问题——API24以下能正常打开ListView条目对应的下载文件,但到了Android N及以上就提示“无法打开文件”。结合你贴的代码,这基本是FileProvider使用时的几个常见错误导致的,我帮你一步步排查修复:
1. 先改掉最致命的硬编码Cursor索引
你在BroadcastReceiver里用c.getString(8)来获取下载后的文件路径,这绝对是大坑!不同Android版本的Cursor列索引是可能变化的,硬编码数字会导致在部分系统上获取到错误的内容。正确的做法是用DownloadManager.COLUMN_LOCAL_URI来获取列索引:
// 替换原来的apkUri = c.getString(8); int localUriColumn = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI); if (localUriColumn != -1) { apkUri = c.getString(localUriColumn); } else { // 处理找不到列的情况,比如提示下载失败 Toast.makeText(getActivity(), "获取文件路径失败", Toast.LENGTH_SHORT).show(); return; }
2. 必须给Intent添加读取权限Flag
Android 7.0+要求共享FileProvider生成的Uri时,必须给目标应用授予临时读取权限,否则对方根本没权限访问这个文件,自然会提示“无法打开”。修改打开文件的Intent代码:
Intent mpOpenintent = new Intent(Intent.ACTION_VIEW); // 加上FLAG_GRANT_READ_URI_PERMISSION,这是API24+必须的 mpOpenintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_GRANT_READ_URI_PERMISSION); mpOpenintent.setDataAndType(uri, "*/*"); try { startActivity(mpOpenintent); } catch (ActivityNotFoundException e) { Toast.makeText(getActivity(), "没有可用的应用打开该文件", Toast.LENGTH_SHORT).show(); }
3. 简化Uri处理逻辑,别瞎截字符串
你代码里对Uri路径做了多次substring截取,很容易把正确路径改错。从DownloadManager获取的COLUMN_LOCAL_URI已经是标准的file://开头的Uri字符串,直接转成File就行:
else { // 直接从filepath[position]拿到Uri,转成File Uri originalUri = filepath[position]; File file = new File(originalUri.getPath()); if (!file.exists()) { Toast.makeText(getActivity(), "文件不存在", Toast.LENGTH_SHORT).show(); return; } // 生成FileProvider的Uri uri = FileProvider.getUriForFile(getActivity(), getActivity().getPackageName() + ".provider", file); }
4. 修复DownloadManager的存储路径配置
你用request.setDestinationInExternalFilesDir(getActivity(), Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), listitemname);这里参数用错了!setDestinationInExternalFilesDir的第二个参数是相对于APP私有外部存储目录的子路径,不是绝对路径。正确写法分两种情况:
- 如果你想存在APP私有Download目录(不需要额外权限,API29+推荐):
request.setDestinationInExternalFilesDir(getActivity(), Environment.DIRECTORY_DOWNLOADS, listitemname);
- 如果要存在公共Download目录(需要
WRITE_EXTERNAL_STORAGE权限,API30+还需要MANAGE_EXTERNAL_STORAGE):
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, listitemname);
路径错了的话,后续你找文件自然找不到,这也是常见原因。
5. 确认FileProvider在Manifest里正确注册
你已经有provider_paths.xml,但要确保AndroidManifest.xml里注册了FileProvider,不然生成的Uri无效:
<application> ... <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider> </application>
注意authorities要和你代码里的getActivity().getPackageName() + ".provider"完全一致。
6. 优化BroadcastReceiver里的Uri存储逻辑
现在你在BroadcastReceiver的API24+分支里还是用Uri.fromFile(file),这会在API24+抛出FileUriExposedException。其实更稳妥的是存储文件的绝对路径,而不是Uri,后续处理更简单:
// 把原来的Uri[] filepath改成String[] filePaths,存储文件绝对路径 String[] filePaths = new String[15]; // 在BroadcastReceiver的成功分支里: if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) { int localUriColumn = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI); String localUriStr = c.getString(localUriColumn); File file = new File(Uri.parse(localUriStr).getPath()); // 存储绝对路径 filePaths[clickposition] = file.getAbsolutePath(); }
然后在点击事件里:
String filePath = filePaths[position]; if (filePath == null) { Toast.makeText(getActivity(), "文件未下载", Toast.LENGTH_SHORT).show(); return; } File file = new File(filePath); if (!file.exists()) { Toast.makeText(getActivity(), "文件不存在", Toast.LENGTH_SHORT).show(); return; } Uri uri; if (Build.VERSION.SDK_INT < 24) { uri = Uri.fromFile(file); } else { uri = FileProvider.getUriForFile(getActivity(), getActivity().getPackageName() + ".provider", file); }
最后总结下关键错误点
- 硬编码Cursor列索引:不同系统版本获取的路径错误,直接导致找不到文件
- 缺少Uri权限Flag:目标应用没有读取权限,无法打开文件
- DownloadManager路径参数错误:文件存到了错误的位置,后续找不到
- 冗余的Uri字符串截取:容易把正确路径改错
按照上面的步骤修改后,应该就能解决API24+无法打开文件的问题了。
内容的提问来源于stack exchange,提问作者ankit jad




