You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android N及以上版本ListView点击下载文件无法打开问题求助

解决Android N(API24+)无法打开下载文件的问题

嗨,我看了你遇到的问题——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

火山引擎 最新活动