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
相关产品推荐
相关产品推荐

