You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从下载目录选择文件时应用崩溃问题求助

解决Android下载文件夹文件选择时的URI查询崩溃问题

嘿,我看你在使用FileUtils获取下载文件夹文件路径时遇到了崩溃,错误是Unknown URI: content://downloads/public_downloads/230。这个问题其实挺常见的,主要是因为Downloads Provider在部分设备或Android版本下,不会给所有下载文件暴露_data列的路径,尤其是Android 10及以上的分区存储限制,直接通过ContentUris.withAppendedId构建的URI查询会失败。

问题根源

你的崩溃发生在getDataColumn方法的查询步骤,因为系统无法识别这个下载文件的URI对应的_data路径——要么是文件不在公共下载目录,要么是系统出于隐私/存储限制,没有提供这个路径的访问权限。

修复方案

我们需要修改FileUtils中处理Downloads文件的逻辑,不再依赖查询_data列,而是通过ContentResolver直接读取文件流,将文件复制到应用的私有缓存目录,返回缓存文件的路径(这是Android推荐的合规方式,尤其是新版本系统)。

步骤1:添加辅助方法到FileUtils

先加两个工具方法:一个用来获取文件名,另一个把下载文件复制到应用缓存目录:

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

// 放在FileUtils类里
private static String copyFileToCache(Context context, Uri uri) {
    try {
        InputStream inputStream = context.getContentResolver().openInputStream(uri);
        if (inputStream == null) return null;

        // 获取原文件的显示名称,作为缓存文件名
        String fileName = getFileName(context, uri);
        // 应用私有缓存目录,无需额外权限
        File cacheFile = new File(context.getCacheDir(), fileName);

        OutputStream outputStream = new FileOutputStream(cacheFile);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }

        inputStream.close();
        outputStream.close();
        return cacheFile.getAbsolutePath();
    } catch (IOException e) {
        if (DEBUG) Log.e(TAG, "复制文件到缓存失败", e);
        return null;
    }
}

private static String getFileName(Context context, Uri uri) {
    String fileName = "temp_" + System.currentTimeMillis();
    Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
    if (cursor != null && cursor.moveToFirst()) {
        int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        if (nameIndex != -1) {
            fileName = cursor.getString(nameIndex);
        }
        cursor.close();
    }
    return fileName;
}

步骤2:修改DownloadsDocument的处理逻辑

替换getPath方法中处理DownloadsProvider的代码块:

// DownloadsProvider
else if (isDownloadsDocument(uri)) {
    final String id = DocumentsContract.getDocumentId(uri);
    // 处理raw类型的URI,直接返回路径
    if (id.startsWith("raw:")) {
        return id.replaceFirst("raw:", "");
    }
    try {
        // 优先复制文件到缓存目录,返回缓存路径
        return copyFileToCache(context, uri);
    } catch (Exception e) {
        // 兼容旧系统,如果复制失败,尝试原查询方式
        final Uri contentUri = ContentUris.withAppendedId(
                Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
        String fallbackPath = getDataColumn(context, contentUri, null, null);
        if (fallbackPath != null) {
            return fallbackPath;
        }
        // 所有方式都失败,返回null
        return null;
    }
}

额外注意事项

  • 权限问题:Android 6.0+需要动态申请READ_EXTERNAL_STORAGE权限;Android 10+使用分区存储时,无需该权限即可通过ContentResolver访问下载文件。
  • 推荐做法:如果你的业务只是读取文件内容(比如上传、解析),其实可以直接使用Uri配合ContentResolver操作,不需要转换为文件路径,这样能避免很多路径相关的兼容性问题。

这样修改后,就能解决下载文件夹文件选择时的崩溃问题,同时兼容更多设备和Android版本。

内容的提问来源于stack exchange,提问作者HB.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:18:25