从URI,Android KitKat新的存储访问框架获取真实路径

Álv*_*aro 199 android uri path android-4.4-kitkat

Android 4.4(KitKat)中访问新图库之前,我使用此方法在SD卡上获得了真正的路径:

public String getPath(Uri uri) {
   String[] projection = { MediaStore.Images.Media.DATA };
   Cursor cursor = managedQuery(uri, projection, null, null, null);
   startManagingCursor(cursor);
   int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
   cursor.moveToFirst();
 return cursor.getString(column_index);
}
Run Code Online (Sandbox Code Playgroud)

现在,Intent.ACTION_GET_CONTENT返回不同的数据:

之前:

content://media/external/images/media/62
Run Code Online (Sandbox Code Playgroud)

现在:

content://com.android.providers.media.documents/document/image:62
Run Code Online (Sandbox Code Playgroud)

我怎么能设法获得SD卡上的真实路径?

Pau*_*rke 500

这将从MediaProvider,DownloadsProvider和ExternalStorageProvider获取文件路径,同时回退到您提到的非官方ContentProvider方法.

/**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @author paulburke
 */
public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[] {
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

/**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @param selection (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
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;
}


/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}
Run Code Online (Sandbox Code Playgroud)

这些来自我的开源库aFileChooser.

  • 请务必在https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java上查看这些方法的最新版本以进行更改. (35认同)
  • 这对于数以亿计的Android设备(Android 4.4+附带的任何设备)来说都是无用的,其中"Uri"指向可移动存储上的东西,因为您没有任意访问可移动存储.对于来自其他一些不支持`_data`模式的`ContentProvider`的`Uri`,它也会失败(例如,`FileProvider`).'Uri`是一个不透明的手柄; 这样对待它,使用`ContentResolver`来获取`Uri`标识的内容. (21认同)
  • 我似乎无法使用Google Drive com.google.android.apps.docs.files中的文件 (17认同)
  • @PaulBurke - 谢谢你,但那是什么"TODO处理非主要卷"? (7认同)
  • @PaulBurke,如果你回答一年前在这里提出的一些问题会很好...我想使用你的lib,但有很多未回答的问题和1年前的最后一次提交我的结论是它是停产. (5认同)
  • 它不适用于API级别27(oreo 8.1),在这里我在选择图像/文档文件时遇到非法参数异常。 (4认同)
  • 如果内容uri为`content:// com.android.providers.downloads.documents / document / 4016`,则不适用于Oreo设备。我尝试使用上面提到的代码,但是出现了类似的崩溃:造成原因:java.lang.IllegalArgumentException:未知URI:content:// downloads / public_downloads / 4016` (4认同)
  • `Uri`不一定是文件:http://commonsware.com/blog/2014/07/04/uri-not-necessarily-file.html (3认同)
  • 请注意:对于需要网络的提供商,代码返回null.使用Google云端硬盘和Picasa进行测试.我的解决方案是将文件复制到本地缓存目录. (2认同)
  • 内容://com.google.android.apps.photos.contentprovider时返回null (2认同)
  • 这对LG Nexus 5不起作用,对于Android 4.4.4,我在打开文件路径返回时得到FileNotFoundException.URI是这样的:content://com.android.providers.downloads.documents/document/532,从这个方法返回的文件路径是/storage/emulated/0/Download/portrait.jpg在这种情况下,在用户界面,下载选择器中有两个图像叫做portrait.jpg,所以文件名很简单似乎很奇怪,例如它指的是portrait.jpg. (2认同)

Vik*_*ram 119

注意:此答案解决了部分问题.要获得完整的解决方案(以库的形式),请查看Paul Burke的答案.

您可以使用URI获取document id,然后查询MediaStore.Images.Media.EXTERNAL_CONTENT_URIMediaStore.Images.Media.INTERNAL_CONTENT_URI(根据SD卡情况).

获取文档ID:

// Will return "image:x*"
String wholeID = DocumentsContract.getDocumentId(uriThatYouCurrentlyHave);

// Split at colon, use second item in the array
String id = wholeID.split(":")[1];

String[] column = { MediaStore.Images.Media.DATA };     

// where id is equal to             
String sel = MediaStore.Images.Media._ID + "=?";

Cursor cursor = getContentResolver().
                          query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
                          column, sel, new String[]{ id }, null);

String filePath = "";

int columnIndex = cursor.getColumnIndex(column[0]);

if (cursor.moveToFirst()) {
    filePath = cursor.getString(columnIndex);
}   

cursor.close();
Run Code Online (Sandbox Code Playgroud)

参考:我无法找到这个解决方案的帖子.我想问原始海报在这里做出贡献.今晚会再看一些.

  • @Magnus感谢您没有尝试上面的代码,并分享您的意见.`...这与MediaStore中的ID无关.如果你以某种方式在MediaStore中找到匹配的ID,那么**肯定是**而不是DocumentsProvider在其URI中返回的相应图像.如果有任何官方文档支持我的答案,我会引用它.但是,我只能说上面的代码在我测试它的几次产生了所需的输出.如果你在解决之前尝试了解决方案是错误的,那将是有礼貌的. (7认同)
  • 是的,但DocumentsProvider的整体思路是,它抽象掉底层数据,因此你的方法是不使用安全,它可能是一个在线服务的图像,因此没有文件URI,因此该方法不使用安全. (6认同)
  • @Magnus请查看[MediaDocumentsProvider]的源代码(https://android.googlesource.com/platform/packages/providers/MediaProvider/+/android-4.4_r0.9/src/com/android/providers/media/ MediaDocumentsProvider.java).`queryDocument(...)`方法使用文档ID(在使用String #substring(..)拆分`type`和`id`之后)查询`MediaStore.Images.Media.EXTERNAL_CONTENT_URI`.也许,只需读取`else if(TYPE_IMAGE.equals(ident.type))`块. (3认同)
  • @Pam您拥有的URI是_not_文档URI - 文档URI以`document:`开头.您可以使用`DocumentsContract.isDocumentUri(uri)`来确认这一点,它基本上检查`uri`是否有前缀`"document"`.看来你已经有了`file` schemed URI.要获取文件路径,可以使用`uri.getPath()`. (2认同)

gun*_*ess 70

以下答案由/sf/users/215787771/写在一个不再存在的页面上,因为他没有足够的回复来回答问题,我发布了它.没有我的学分.

public String getImagePath(Uri uri){
   Cursor cursor = getContentResolver().query(uri, null, null, null, null);
   cursor.moveToFirst();
   String document_id = cursor.getString(0);
   document_id = document_id.substring(document_id.lastIndexOf(":")+1);
   cursor.close();

   cursor = getContentResolver().query( 
   android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
   null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
   cursor.moveToFirst();
   String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
   cursor.close();

   return path;
}
Run Code Online (Sandbox Code Playgroud)

编辑:代码上有一个流程; 如果设备有多个外部存储器(外部SD卡,外部USB等),则代码上方将无法使用非主存储器.


Com*_*are 28

在使用KitKat访问新图库之前,我使用此方法在sdcard中获得了真正的路径

这永远不可靠.不要求Uri您从一个ACTION_GET_CONTENT或一个ACTION_PICK请求返回的内容必须由索引编制索引MediaStore,或者甚至必须表示文件系统上的文件.在Uri可以,例如,代表一个流,其中一个加密的文件进行解密你的飞行.

我怎么能设法获得SD卡中的真实路径?

不要求存在与之对应的文件Uri.

是的,我真的需要一条路

然后将文件从流中复制到您自己的临时文件中,并使用它.更好的是,只需直接使用流,并避免临时文件.

我已经为Intent.ACTION_PICK更改了我的Intent.ACTION_GET_CONTENT

这对你的情况没有帮助.没有要求ACTION_PICK响应是针对Uri文件系统上有文件的文件,你可以以某种方式神奇地推导出来.

  • @SharpEdge:没有路径.你越早停止思考文件和路径,而是考虑内容和"Uri"值,你会越好.毕竟,图库的内容可能包括来自云存储提供商(例如Google照片)的文件,以及您无法直接访问的文件(例如,在可移动存储上).使用`ContentResolver`和`DocumentFile`. (4认同)
  • @ılǝ:"通过将流复制到新文件,exif标签是不是丢失了?" - 他们不应该.但是,将图像解码为"位图"将丢失EXIF标记.但是,只要您将其保留为`byte []`,并且不尝试对这些字节进行任何修改,我希望EXIF标记能够存活. (2认同)

Mag*_*nus 8

这个答案是基于你有点模糊的描述.我假设您通过操作触发了一个意图:Intent.ACTION_GET_CONTENT

现在你content://com.android.providers.media.documents/document/image:62回来而不是以前的媒体提供者URI,对吗?

在Android 4.4(KitKat)上,新的DocumentsActivity在Intent.ACTION_GET_CONTENT被触发时打开,从而导致网格视图(或列表视图),您可以在其中选择图像,这将返回以下URI到调用上下文(示例):( content://com.android.providers.media.documents/document/image:62这些是URI对于新文档提供者,它通过向客户提供通用文档提供者URI来抽象出基础数据.

但是,您可以Intent.ACTION_GET_CONTENT使用DocumentsActivity中的抽屉访问图库和响应的其他活动(从左向右拖动,您将看到带有图库的抽屉UI可供选择).就像KitKat之前一样.

如果你仍然选择DocumentsActivity类并需要文件URI,你应该能够执行以下操作(警告这是hacky!)查询(使用contentresolver):content://com.android.providers.media.documents/document/image:62URI并从游标中读取_display_name值.这是一些独特的名称(只是本地文件上的文件名),并在选择(查询时)到mediaprovider以从此处获取与此选择对应的正确行时使用该名称,您也可以获取文件URI.

可以在此处找到访问文档提供程序的推荐方法(获取输入流或文件描述符以读取文件/位图):

使用documentprovider的示例

  • Google刚刚更新了文档,非常棒.抱歉,我的模糊描述.我想知道从新的选择器ui到离线过程的真实路径.我知道如何获取文件的名称而不是完整的路径,我想从这个数据内容://com.android.providers.media获取此路径/storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg.文件/文件/图片:62 (2认同)

小智 8

我有同样的问题.我需要文件名,以便能够将其上传到网站.

如果我把意图改为PICK,它对我有用.这在AVD for Android 4.4和AVD for Android 2.1中进行了测试.

添加权限READ_EXTERNAL_STORAGE:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Run Code Online (Sandbox Code Playgroud)

改变意图:

Intent i = new Intent(
  Intent.ACTION_PICK,
  android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI
  );
startActivityForResult(i, 66453666);

/* OLD CODE
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(
  Intent.createChooser( intent, "Select Image" ),
  66453666
  );
*/
Run Code Online (Sandbox Code Playgroud)

我没有必要更改我的代码获取实际路径:

// Convert the image URI to the direct file system path of the image file
 public String mf_szGetRealPathFromURI(final Context context, final Uri ac_Uri )
 {
     String result = "";
     boolean isok = false;

     Cursor cursor = null;
      try { 
        String[] proj = { MediaStore.Images.Media.DATA };
        cursor = context.getContentResolver().query(ac_Uri,  proj, null, null, null);
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        result = cursor.getString(column_index);
        isok = true;
      } finally {
        if (cursor != null) {
          cursor.close();
        }
      }

      return isok ? result : "";
 }
Run Code Online (Sandbox Code Playgroud)


rah*_*esh 7

试试这个:

//KITKAT
i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, CHOOSE_IMAGE_REQUEST);
Run Code Online (Sandbox Code Playgroud)

在onActivityResult中使用以下内容:

Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
BitmapFactory.decodeStream(input , null, opts);
Run Code Online (Sandbox Code Playgroud)


Dan*_*okh 5

这是Paul Burke答案的更新版本.在Android 4.4(KitKat)下面的版本中,我们没有DocumentsContract类.

为了在KitKat下面的版本上工作,创建这个类:

public class DocumentsContract {
    private static final String DOCUMENT_URIS =
        "com.android.providers.media.documents " +
        "com.android.externalstorage.documents " +
        "com.android.providers.downloads.documents " +
        "com.android.providers.media.documents";

    private static final String PATH_DOCUMENT = "document";
    private static final String TAG = DocumentsContract.class.getSimpleName();

    public static String getDocumentId(Uri documentUri) {
        final List<String> paths = documentUri.getPathSegments();
        if (paths.size() < 2) {
            throw new IllegalArgumentException("Not a document: " + documentUri);
        }

        if (!PATH_DOCUMENT.equals(paths.get(0))) {
            throw new IllegalArgumentException("Not a document: " + documentUri);
        }
        return paths.get(1);
    }

    public static boolean isDocumentUri(Uri uri) {
        final List<String> paths = uri.getPathSegments();
        Logger.v(TAG, "paths[" + paths + "]");
        if (paths.size() < 2) {
            return false;
        }
        if (!PATH_DOCUMENT.equals(paths.get(0))) {
            return false;
        }
        return DOCUMENT_URIS.contains(uri.getAuthority());
    }
}
Run Code Online (Sandbox Code Playgroud)