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

Android中如何从"/document/(数字)"格式的Uri获取文件路径?

解决Android Lollipop中从/document/(数字)格式Uri获取文件路径的问题

嘿,这个问题我太熟了!在Android 5.0(Lollipop)之后,系统引入了Storage Access Framework(SAF),你拿到的/document/xxx这种格式的Uri就是SAF提供的文档Uri——这跟传统的content:// Uri逻辑不一样,直接用_data列查询自然失效。我给你整理了一套完整的解决方案,涵盖常见场景:

核心思路

SAF的文档Uri需要根据不同的authority(也就是Uri的权限部分)来区分处理,比如下载目录、外部存储、媒体库的文档Uri处理逻辑都不一样。我们可以先判断Uri是否属于DocumentProvider,再分情况解析。

完整工具代码

你可以直接把下面的方法加到你的工具类里,配合你原来的getDataColumn方法使用:

import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.Downloads;
import android.provider.MediaStore;

public class FileUriUtils {

    // 核心方法:解析各种Uri到真实路径
    public static String getRealPathFromUri(Context context, Uri uri) {
        // 先判断是不是SAF的文档Uri
        if (DocumentsContract.isDocumentUri(context, uri)) {
            String authority = uri.getAuthority();
            // 处理下载目录的文档Uri(你遇到的场景)
            if ("com.android.providers.downloads.documents".equals(authority)) {
                String documentId = DocumentsContract.getDocumentId(uri);
                // 特殊情况:raw格式的Uri直接返回路径
                if (documentId.startsWith("raw:")) {
                    return documentId.substring(4);
                }
                // 普通情况:通过下载内容提供者查询真实路径
                String[] projection = {Downloads.DATA};
                String selection = Downloads._ID + "=?";
                String[] selectionArgs = {documentId.split(":")[1]};
                
                Cursor cursor = context.getContentResolver().query(
                        Downloads.EXTERNAL_CONTENT_URI,
                        projection,
                        selection,
                        selectionArgs,
                        null
                );
                if (cursor != null && cursor.moveToFirst()) {
                    int columnIndex = cursor.getColumnIndex(projection[0]);
                    String path = cursor.getString(columnIndex);
                    cursor.close();
                    return path;
                }
            }
            // 处理外部存储的文档Uri
            else if ("com.android.externalstorage.documents".equals(authority)) {
                String documentId = DocumentsContract.getDocumentId(uri);
                String[] parts = documentId.split(":");
                String storageType = parts[0];
                String filePath = parts[1];
                
                // 主存储直接拼接路径
                if ("primary".equalsIgnoreCase(storageType)) {
                    return Environment.getExternalStorageDirectory() + "/" + filePath;
                }
                // 其他外部存储(比如SD卡)
                else {
                    return "/storage/" + storageType + "/" + filePath;
                }
            }
            // 处理媒体库的文档Uri(图片、视频、音频)
            else if ("com.android.providers.media.documents".equals(authority)) {
                String documentId = DocumentsContract.getDocumentId(uri);
                String[] parts = documentId.split(":");
                String mediaType = parts[0];
                Uri contentUri = null;
                
                switch (mediaType) {
                    case "image":
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                        break;
                    case "video":
                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                        break;
                    case "audio":
                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                        break;
                }
                
                String selection = "_id=?";
                String[] selectionArgs = {parts[1]};
                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // 处理传统的content:// Uri(你原来的逻辑)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // 处理file:// Uri
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }
        return null;
    }

    // 保留你原来的getDataColumn方法
    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {column};
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }
}

重要注意事项

  • Android 10+兼容性提醒:从API 29开始,系统启用了Scoped Storage,直接获取文件路径可能会遇到权限限制。更推荐的做法是直接使用ContentResolver.openInputStream(uri)读取文件内容,而不是依赖真实路径,这样能更好适配新版本系统。
  • 权限配置:别忘了添加必要的权限,Android 10及以前需要READ_EXTERNAL_STORAGE;Android 13及以后,针对媒体文件需要READ_MEDIA_IMAGES/READ_MEDIA_VIDEO等细分权限,下载目录文件仍需READ_EXTERNAL_STORAGE。
  • 测试覆盖:建议测试不同场景的文件,比如下载目录的普通文件、压缩包,外部SD卡的文件,媒体库的图片视频等,确保逻辑覆盖全面。

内容的提问来源于stack exchange,提问作者priojeet priyom

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:38:28