从URI中获取真实路径,Android KitKat新的存储访问框架
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20067508/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get real path from URI, Android KitKat new storage access framework
提问by álvaro
Before the new gallery access in Android 4.4(KitKat) I got my real path on the SD card with this method:
在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);
}
Now, the Intent.ACTION_GET_CONTENT return different data:
现在, Intent.ACTION_GET_CONTENT 返回不同的数据:
Before:
前:
content://media/external/images/media/62
Now:
现在:
content://com.android.providers.media.documents/document/image:62
How could I manage to obtain the real path on the SD card?
我怎样才能设法获得 SD 卡上的真实路径?
采纳答案by Vikram
Note: This answer addresses part of the problem. For a complete solution (in the form of a library), look at Paul Burke's answer.
注意:此答案解决了部分问题。对于完整的解决方案(以图书馆的形式),请查看Paul Burke 的回答。
You could use the URI to obtain document id
, and then query either MediaStore.Images.Media.EXTERNAL_CONTENT_URI
or MediaStore.Images.Media.INTERNAL_CONTENT_URI
(depending on the SD card situation).
您可以使用 URI 获取document id
,然后查询MediaStore.Images.Media.EXTERNAL_CONTENT_URI
或MediaStore.Images.Media.INTERNAL_CONTENT_URI
(取决于 SD 卡情况)。
To get document id:
要获取文档 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();
Reference: I'm not able to find the post that this solution is taken from. I wanted to ask the original poster to contribute here. Will look some more tonight.
参考:我找不到该解决方案来自的帖子。我想请原始海报在这里做出贡献。今晚会再看一些。
回答by Paul Burke
This will get the file path from the MediaProvider, DownloadsProvider, and ExternalStorageProvider, while falling back to the unofficial ContentProvider method you mention.
这将从 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());
}
These are taken from my open source library, aFileChooser.
这些取自我的开源库aFileChooser。
回答by guness
the answer below is written by https://stackoverflow.com/users/3082682/cvizvon a page which does not exist anymore, since he has not enough rep to answer a question, I am posting it. No credits by me.
下面的答案是由https://stackoverflow.com/users/3082682/cvizv在一个不再存在的页面上写的,因为他没有足够的代表来回答问题,所以我发布了它。我没有信用。
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;
}
Edit: There is a flow on the code; if device has more than one external storage (external sdcard, external usb etc.), above the code won't work non primary storages.
编辑:代码上有一个流程;如果设备有多个外部存储(外部 sdcard、外部 USB 等),上面的代码将无法在非主存储上工作。
回答by CommonsWare
Before new gallery access in KitKat I got my real path in sdcard with this method
在 KitKat 中访问新画廊之前,我使用此方法在 sdcard 中获得了真实路径
That was never reliable. There is no requirement that the Uri
that you are returned from an ACTION_GET_CONTENT
or ACTION_PICK
request has to be indexed by the MediaStore
, or even has to represent a file on the file system. The Uri
could, for example, represent a stream, where an encrypted file is decrypted for you on the fly.
那从来都不可靠。没有要求Uri
您从ACTION_GET_CONTENT
或ACTION_PICK
请求返回的必须由 索引MediaStore
,甚至不必表示文件系统上的文件。Uri
例如,它可以代表一个流,其中一个加密的文件被即时为您解密。
How could I manage to obtain the real path in sdcard?
我怎样才能获得 sdcard 中的真实路径?
There is no requirement that there is a file corresponding to the Uri
.
不要求有对应的文件Uri
。
Yes, I really need a path
是的,我真的需要一条路
Then copy the file from the stream to your own temporary file, and use it. Better yet, just use the stream directly, and avoid the temporary file.
然后将文件从流复制到您自己的临时文件,并使用它。更好的是,直接使用流,避免临时文件。
I have changed my Intent.ACTION_GET_CONTENT for Intent.ACTION_PICK
我已将 Intent.ACTION_GET_CONTENT 更改为 Intent.ACTION_PICK
That will not help your situation. There is no requirement that an ACTION_PICK
response be for a Uri
that has a file on the filesystem that you can somehow magically derive.
这对你的情况没有帮助。没有要求ACTION_PICK
响应是针对Uri
在文件系统上具有您可以以某种方式神奇地导出的文件的。
回答by Jan Van Overbeke
I had the exact same problem. I need the filename so to be able to upload it to a website.
我有同样的问题。我需要文件名以便能够将其上传到网站。
It worked for me, if I changed the intent to PICK. This was tested in AVD for Android 4.4 and in AVD for Android 2.1.
如果我将意图更改为 PICK,它对我有用。这在 AVD for Android 4.4 和 AVD for Android 2.1 中进行了测试。
Add permission READ_EXTERNAL_STORAGE :
添加权限 READ_EXTERNAL_STORAGE :
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Change the Intent :
改变意图:
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
);
*/
I did not have to change my code the get the actual path:
我不必更改我的代码即可获得实际路径:
// 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 : "";
}
回答by Magnus
This answer is based on your somewhat vague description. I assume that you fired an intent with action: Intent.ACTION_GET_CONTENT
这个答案是基于你有些模糊的描述。我假设你用行动激发了一个意图:Intent.ACTION_GET_CONTENT
And now you get content://com.android.providers.media.documents/document/image:62
back instead of the previously media provider URI, correct?
现在您content://com.android.providers.media.documents/document/image:62
返回而不是以前的媒体提供商 URI,对吗?
On Android 4.4 (KitKat) the new DocumentsActivity gets opened when an Intent.ACTION_GET_CONTENT
is fired thus leading to grid view (or list view) where you can pick an image, this will return the following URIs to calling context (example): content://com.android.providers.media.documents/document/image:62
(these are the URIs to the new document provider, it abstracts away the underlying data by providing generic document provider URIs to clients).
在 Android 4.4 (KitKat) 上,新的 DocumentsActivity 在Intent.ACTION_GET_CONTENT
触发时打开,从而导致您可以选择图像的网格视图(或列表视图),这将返回以下 URI 以调用上下文(示例):(content://com.android.providers.media.documents/document/image:62
这些是 URI到新的文档提供者,它通过向客户端提供通用文档提供者 URI 来抽象出底层数据)。
You can however access both gallery and other activities responding to Intent.ACTION_GET_CONTENT
by using the drawer in the DocumentsActivity (drag from left to right and you'll see a drawer UI with Gallery to choose from). Just as pre KitKat.
但是,您可以Intent.ACTION_GET_CONTENT
使用 DocumentsActivity 中的抽屉访问图库和其他响应的活动(从左到右拖动,您将看到一个带有图库的抽屉 UI 可供选择)。就像之前 KitKat 一样。
If you still which to pick in DocumentsActivity class and need the file URI, you should be able to do the following (warning this is hacky!) query (with contentresolver):content://com.android.providers.media.documents/document/image:62
URI and read the _display_name value from the cursor. This is somewhat unique name (just the filename on local files) and use that in a selection (when querying) to mediaprovider to get the correct row corresponding to this selection from here you can fetch the file URI as well.
如果您仍然在 DocumentsActivity 类中选择哪个并且需要文件 URI,您应该能够执行以下操作(警告这是 hacky!)查询(使用 contentresolver):content://com.android.providers.media.documents/document/image:62
URI 并从光标读取 _display_name 值。这是有点独特的名称(只是本地文件上的文件名),并在选择(查询时)中使用它到 mediaprovider 以获取与此选择对应的正确行,您也可以从此处获取文件 URI。
The recommended ways of accessing document provider can be found here (get an inputstream or file descriptor to read file/bitmap):
可以在此处找到访问文档提供程序的推荐方法(获取输入流或文件描述符以读取文件/位图):
回答by rahulritesh
Try this:
尝试这个:
//KITKAT
i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, CHOOSE_IMAGE_REQUEST);
Use the following in the onActivityResult:
在 onActivityResult 中使用以下内容:
Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
BitmapFactory.decodeStream(input , null, opts);
回答by Danylo Volokh
Here is an updated version of Paul Burke's answer. In versions below Android 4.4(KitKat) we don't have the DocumentsContractclass.
这是Paul Burke 答案的更新版本。在低于Android 4.4(KitKat) 的版本中,我们没有DocumentsContract类。
In order to work on versions below KitKat create this class:
为了在 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());
}
}
回答by Sachin Gupta
We need to do the following changes/fixes in our earlier onActivityResult()'s gallery picker code to run seamlessly on Android 4.4(KitKat) and on all other earlier versions as well.
我们需要在我们之前的 onActivityResult() 的图库选择器代码中进行以下更改/修复,以便在Android 4.4(KitKat) 和所有其他早期版本上无缝运行。
Uri selectedImgFileUri = data.getData();
if (selectedImgFileUri == null ) {
// The user has not selected any photo
}
try {
InputStream input = mActivity.getContentResolver().openInputStream(selectedImgFileUri);
mSelectedPhotoBmp = BitmapFactory.decodeStream(input);
}
catch (Throwable tr) {
// Show message to try again
}