在Android中从文件路径获取内容URI

311

我知道一张图片的绝对路径(例如,/sdcard/cats.jpg)。有没有办法获取此文件的内容URI?

实际上,在我的代码中,我下载一张图片并将其保存在特定位置。为了将图像设置在ImageView实例中,目前我使用路径打开文件,获取字节并创建位图,然后将位图设置在ImageView实例中。这是一个非常慢的过程,如果我能够获得内容URI,那么我就可以很容易地使用方法imageView.setImageUri(uri)


28
URI uri = URI.parse("file:///sdcard/img.png"); 这行代码的作用是将一个本地文件路径转换成一个URI对象。其中,“file:///”表示协议,表示这是一个本地文件,而“/sdcard/img.png”则是文件的绝对路径。 - Anand Tiwari
14
给评论点赞,只需要使用Uri.parse("file://" + filePath)就可以解决问题。 - German Latorre
Uri.Parse 已过时,将被添加。 - pollaris
@pollaris Uri.parse在API 1中添加,且未被标记为弃用。 - JP de la Torre
Uri.parse(“something”); 在我的机器上无法工作,我找不到原因... - Bay
10个回答

534

请尝试以下操作:

ImageView.setImageURI(Uri.fromFile(new File("/sdcard/cats.jpg")));

或者使用:

ImageView.setImageURI(Uri.parse(new File("/sdcard/cats.jpg").toString()));

3
谢谢!第二种方法在1.6、2.1和2.2上都可以正常工作,但第一种方法只能在2.2上使用。 - mishkin
39
这两种方法都不能将文件路径解析为内容URI。不过我明白这解决了眼前的问题。 - Jeffrey Blattman
42
不要硬编码“/sdcard/”,而是使用Environment.getExternalStorageDirectory().getPath()。 - ekatz
11
这是上述两种解决方案的返回结果:
  1. file:///storage/emulated/0/DCIM/Camera/VID_20140312_171146.mp4
  2. /storage/emulated/0/DCIM/Camera/VID_20140312_171146.mp4 但我正在寻找的是不同的东西。我需要content://格式的URI。Jinal的答案似乎完美地解决了我的问题。
- Ajith M A
6
嘿,Uri.fromFile在Android 26及以上版本将无法使用,你应该使用文件提供程序(file provider)。 - vuhung3990
显示剩余6条评论

99

更新

假设您的媒体(图像/视频)已添加到内容媒体提供程序中。如果没有,则将无法获取所需的内容URL。相反,将会是文件URI。

我在我的文件浏览器活动中遇到了同样的问题。您应该知道,仅支持媒体存储数据,如图像、音频和视频的文件的contenturi。我将为您提供从sd卡中选择图像时获取图像内容uri的代码。尝试这段代码,也许对您有用...

public static Uri getImageContentUri(Context context, File imageFile) {
  String filePath = imageFile.getAbsolutePath();
  Cursor cursor = context.getContentResolver().query(
      MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
      new String[] { MediaStore.Images.Media._ID },
      MediaStore.Images.Media.DATA + "=? ",
      new String[] { filePath }, null);
  if (cursor != null && cursor.moveToFirst()) {
    int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
    cursor.close();
    return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id);
  } else {
    if (imageFile.exists()) {
      ContentValues values = new ContentValues();
      values.put(MediaStore.Images.Media.DATA, filePath);
      return context.getContentResolver().insert(
          MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    } else {
      return null;
    }
  }
}

支持 Android Q

public static Uri getImageContentUri(Context context, File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        new String[]{MediaStore.Images.Media._ID},
        MediaStore.Images.Media.DATA + "=? ",
        new String[]{filePath}, null);
if (cursor != null && cursor.moveToFirst()) {
    int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
    cursor.close();
    return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id);
} else {
    if (imageFile.exists()) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ContentResolver resolver = context.getContentResolver();
            Uri picCollection = MediaStore.Images.Media
                    .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
            ContentValues picDetail = new ContentValues();
            picDetail.put(MediaStore.Images.Media.DISPLAY_NAME, imageFile.getName());
            picDetail.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg");
            picDetail.put(MediaStore.Images.Media.RELATIVE_PATH,"DCIM/" + UUID.randomUUID().toString());
            picDetail.put(MediaStore.Images.Media.IS_PENDING,1);
            Uri finaluri = resolver.insert(picCollection, picDetail);
            picDetail.clear();
            picDetail.put(MediaStore.Images.Media.IS_PENDING, 0);
            resolver.update(picCollection, picDetail, null, null);
            return finaluri;
        }else {
            ContentValues values = new ContentValues();
            values.put(MediaStore.Images.Media.DATA, filePath);
            return context.getContentResolver().insert(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        }

    } else {
        return null;
    }
  }
}

我正在寻找一种方法来查找我的录制视频文件的content:// URI,上面的代码似乎在Nexus 4(Android 4.3)上完美运行。如果您能解释一下这段代码,那就太好了。 - Ajith M A
我尝试使用绝对路径“/sdcard/Image Depo/picture.png”获取文件的Content Uri。但是它没有起作用,所以我调试了代码路径,并发现游标为空,当条目被添加到ContentProvider时,它会将null作为Content Uri。请帮忙。 - Sri Krishna
1
我有文件路径 - file:///storage/emulated/0/Android/data/com.packagename/files/out.mp4,但是当我尝试获取contentUri时却得到了null。我还尝试将MediaStore.Images.Media更改为MediaStore.Video.Media,但仍然没有成功。 - Narendra Singh
1
这在Android Pie API 28上无法工作。游标返回null。 - Ibrahim Gharyali
2
你能否更新这个方法以适应Android 10,因为在Android 10中无法访问DATA列? - Khushbu Shah
显示剩余2条评论

18

被接受的解决方案可能是您的最佳选择,但实际上回答主题中的问题:

在我的应用程序中,我必须从URI获取路径并从路径获取URI。 前者:

/**
 * Gets the corresponding path to a file from the given content:// URI
 * @param selectedVideoUri The content:// URI to find the file path from
 * @param contentResolver The content resolver to use to perform the query.
 * @return the file path as a string
 */
private String getFilePathFromContentUri(Uri selectedVideoUri,
        ContentResolver contentResolver) {
    String filePath;
    String[] filePathColumn = {MediaColumns.DATA};

    Cursor cursor = contentResolver.query(selectedVideoUri, filePathColumn, null, null, null);
    cursor.moveToFirst();

    int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
    filePath = cursor.getString(columnIndex);
    cursor.close();
    return filePath;
}

我使用这种方法来处理视频(同时也可以通过将MediaStore.Video替换为MediaStore.Audio等来处理音频、文件或其他类型的存储内容):

/**
 * Gets the MediaStore video ID of a given file on external storage
 * @param filePath The path (on external storage) of the file to resolve the ID of
 * @param contentResolver The content resolver to use to perform the query.
 * @return the video ID as a long
 */
private long getVideoIdFromFilePath(String filePath,
        ContentResolver contentResolver) {


    long videoId;
    Log.d(TAG,"Loading file " + filePath);

            // This returns us content://media/external/videos/media (or something like that)
            // I pass in "external" because that's the MediaStore's name for the external
            // storage on my device (the other possibility is "internal")
    Uri videosUri = MediaStore.Video.Media.getContentUri("external");

    Log.d(TAG,"videosUri = " + videosUri.toString());

    String[] projection = {MediaStore.Video.VideoColumns._ID};

    // TODO This will break if we have no matching item in the MediaStore.
    Cursor cursor = contentResolver.query(videosUri, projection, MediaStore.Video.VideoColumns.DATA + " LIKE ?", new String[] { filePath }, null);
    cursor.moveToFirst();

    int columnIndex = cursor.getColumnIndex(projection[0]);
    videoId = cursor.getLong(columnIndex);

    Log.d(TAG,"Video ID is " + videoId);
    cursor.close();
    return videoId;
}

基本上,MediaStoreDATA列(或者你正在查询的其它子段)存储了文件路径信息,因此您可以使用该信息来查找它。


并非总是如此,有两个半保证的列被返回,即OpenableColumns.DISPLAY_NAME和OpenableColumns.SIZE,如果发送应用程序遵循“规则”的话。我发现一些主要的应用程序只返回这两个字段,而不总是返回_data字段。在没有数据字段的情况下,通常包含内容的直接路径,您必须首先读取内容并将其写入文件或内存中,然后您就拥有了自己的路径。 - Pierre

16

// 这段代码适用于 2.2 版本的图片,不确定是否适用于其他媒体类型

   //Your file path - Example here is "/sdcard/cats.jpg"
   final String filePathThis = imagePaths.get(position).toString();

   MediaScannerConnectionClient mediaScannerClient = new
   MediaScannerConnectionClient() {
    private MediaScannerConnection msc = null;
    {
        msc = new MediaScannerConnection(getApplicationContext(), this);
        msc.connect();
    }

    public void onMediaScannerConnected(){
        msc.scanFile(filePathThis, null);
    }


    public void onScanCompleted(String path, Uri uri) {
        //This is where you get your content uri
            Log.d(TAG, uri.toString());
        msc.disconnect();
    }
   };

1
很棒,帮助我分享到Google+,因为该应用程序需要具有内容Uri的媒体流--绝对路径无法工作。 - Ridcully
1
太好了!我可以确认这也适用于音频媒体类型。 - Matt M

10
创建文件的Content Uri content:// 最简单和最有效的方法是使用FileProvider。 由FileProvider提供的Uri也可以用于向其他应用程序共享文件的Uri。 要从File的绝对路径获取文件Uri,可以使用DocumentFile.fromFile(new File(path,name)),它在Api 22中添加,并且对版本低于此的返回null。
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = FileProvider.getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);

10

您可以根据使用情况使用以下两种方式:

Uri uri = Uri.parse("文件位置字符串");

或者

Uri uri = Uri.fromFile(new File("文件位置字符串"));

我已经尝试了这两种方法,都可以正常工作。


8

虽然有点晚了,但希望能对未来的某个人有所帮助。

要获取文件的内容URI,您可以使用以下方法:

FileProvider.getUriForFile(Context context, String authority, File file)

它将返回内容URI。

点击这里了解如何设置FileProvider


4

只需使用 adb shell 命令行工具,无需编写任何代码即可获取文件ID:

adb shell content query --uri "content://media/external/video/media" | grep FILE_NAME | grep -Eo " _id=([0-9]+)," | grep -Eo "[0-9]+"

谷歌搜索“adb从内容uri获取真实路径”,这个问题排名第一,你的0票答案的内容出现在搜索结果摘要中。所以让我成为第一个投票的人。谢谢兄弟! - Weekend
这很酷。但是,除非您已经获取了root权限,否则似乎会出现“Permission Denial: Do not have permission in call getContentProviderExternal() from pid=15660, uid=10113 requires android.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY”的错误。 - not2qubit
所以这需要手机的 root 访问权限吗? - skaveesh

2
最好使用验证来支持 Android N 之前的版本,例如:
  if (Build.VERSION.SDK_INT >=  Build.VERSION_CODES.N) {
     imageUri = Uri.parse(filepath);
  } else{
     imageUri = Uri.fromFile(new File(filepath));
  }

  if (Build.VERSION.SDK_INT >=  Build.VERSION_CODES.N) {
     ImageView.setImageURI(Uri.parse(new File("/sdcard/cats.jpg").toString()));         
  } else{
     ImageView.setImageURI(Uri.fromFile(new File("/sdcard/cats.jpg")));
  }

https://es.stackoverflow.com/questions/71443/reporte-crash-android-os-fileuriexposedexception-en-android-n


0

你可以尝试下面的代码片段

    public Uri getUri(ContentResolver cr, String path){
    Uri mediaUri = MediaStore.Files.getContentUri(VOLUME_NAME);
    Cursor ca = cr.query(mediaUri, new String[] { MediaStore.MediaColumns._ID }, MediaStore.MediaColumns.DATA + "=?", new String[] {path}, null);
    if (ca != null && ca.moveToFirst()) {
        int id = ca.getInt(ca.getColumnIndex(MediaStore.MediaColumns._ID));
        ca.close();
        return  MediaStore.Files.getContentUri(VOLUME_NAME,id);
    }
    if(ca != null) {
        ca.close();
    }
    return null;
}

1
VOLUME_NAME是什么? - Evgenii Vorobei
它是“外部的”还是“内部的”。 - caopeng

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接