Android 4.4(KitKat)上的Android图库为Intent.ACTION_GET_CONTENT返回不同的URI
在KitKat之前(或在新的图库之前), Intent.ACTION_GET_CONTENT
返回一个这样的URI
内容://媒体/外部/图像/媒体/ 3951。
使用ContentResolver
并为MediaStore.Images.Media.DATA
返回文件的URL。
在KitKat中,Gallery会返回一个URI(通过“Last”),如下所示:
内容://com.android.providers.media.documents/document/image:3951
我该如何处理?
尝试这个:
if (Build.VERSION.SDK_INT <19){ Intent intent = new Intent(); intent.setType("image/jpeg"); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.select_picture)),GALLERY_INTENT_CALLED); } else { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/jpeg"); startActivityForResult(intent, GALLERY_KITKAT_INTENT_CALLED); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; if (null == data) return; Uri originalUri = null; if (requestCode == GALLERY_INTENT_CALLED) { originalUri = data.getData(); } else if (requestCode == GALLERY_KITKAT_INTENT_CALLED) { originalUri = data.getData(); final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Check for the freshest data. getContentResolver().takePersistableUriPermission(originalUri, takeFlags); } loadSomeStreamAsynkTask(originalUri); }
可能需要
@SuppressLint( “NewApi”)
对于
takePersistableUriPermission
这不需要特别的权限,可以使用Storage Access Framework,也可以使用非官方的ContentProvider
模式( _data
字段中的文件path)。
/** * 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 the remote address if (isGooglePhotosUri(uri)) return uri.getLastPathSegment(); 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 index = cursor.getColumnIndexOrThrow(column); return cursor.getString(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()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is Google Photos. */ public static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); }
在这里查看这个方法的最新版本。
有同样的问题,尝试了上述解决scheme,但虽然它一般工作,出于某种原因,我得到了许多拒绝Uri内容提供商的一些图像,虽然我已经正确添加了android.permission.MANAGE_DOCUMENTS
权限。
无论如何,发现其他解决scheme是强制打开图片库,而不是KITKAT文档视图:
// KITKAT i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(i, CHOOSE_IMAGE_REQUEST);
然后加载图像:
Uri selectedImageURI = data.getData(); input = c.getContentResolver().openInputStream(selectedImageURI); BitmapFactory.decodeStream(input , null, opts);
编辑
ACTION_OPEN_DOCUMENT
可能会要求您保留权限标志等,通常会导致安全例外…
其他解决scheme是使用ACTION_GET_CONTENT
结合c.getContentResolver().openInputStream(selectedImageURI)
,它将在KK和KK之前工作。 Kitkat将使用新的文件视图然后这个解决scheme将适用于所有的应用程序,如照片,图库,文件资源pipe理器,Dropbox的,谷歌驱动器等…)但记住,当使用这个解决scheme,你必须在onActivityResult()
将其存储在SD卡上。 在下次应用程序启动时从保存的uri重新创build此映像,即使在添加Google API文档中描述的权限标记时也会抛出内容parsing程序的安全性exception(这是我进行一些testing时发生的情况)
另外Android开发者API指南build议:
ACTION_OPEN_DOCUMENT不打算替代ACTION_GET_CONTENT。 你应该使用的取决于你的应用程序的需求:
如果您希望应用程序只读取/导入数据,请使用ACTION_GET_CONTENT。 使用这种方法,应用程序会导入数据的副本,如图像文件。
如果您希望应用程序对文档提供者所拥有的文档具有长期的持久访问权限,请使用ACTION_OPEN_DOCUMENT。 一个例子是一个照片编辑应用程序,让用户编辑存储在文档提供程序中的图像。
就像Commonsware提到的,你不应该假设你通过ContentResolver
获得的stream可以转换成文件。
你应该做的是从ContentProvider
打开InputStream
,然后创build一个Bitmap。 它也适用于4.4和更早版本,不需要反思。
//cxt -> current context InputStream input; Bitmap bmp; try { input = cxt.getContentResolver().openInputStream(fileUri); bmp = BitmapFactory.decodeStream(input); } catch (FileNotFoundException e1) { }
当然,如果你处理大的图像,你应该加载适当的inSampleSize
: http : //developer.android.com/training/displaying-bitmaps/load-bitmap.html 。 但这是另一个话题。
我相信已经发表的回应应该让人们朝着正确的方向前进。 然而,这是我做了什么对我正在更新的遗留代码有意义。 遗留代码使用库中的URI来更改并保存图像。
在4.4(和谷歌驱动器)之前,URI将如下所示: content:// media / external / images / media / 41
正如问题所述,他们更多的时候是这样的: content://com.android.providers.media.documents/document/image:3951
由于我需要能够保存图像,而不是打扰已经存在的代码,我只是从库中的URI复制到应用程序的数据文件夹。 然后从数据文件夹中保存的图像文件中产生一个新的URI。
这是这个想法:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); startActivityForResult(intent), CHOOSE_IMAGE_REQUEST); public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); File tempFile = new File(this.getFilesDir().getAbsolutePath(), "temp_image"); //Copy URI contents into temporary file. try { tempFile.createNewFile(); copyAndClose(this.getContentResolver().openInputStream(data.getData()),new FileOutputStream(tempFile)); } catch (IOException e) { //Log Error } //Now fetch the new URI Uri newUri = Uri.fromFile(tempFile); /* Use new URI object just like you used to */ }
注 – copyAndClose()只是执行文件I / O来将InputStream复制到FileOutputStream中。 该代码不张贴。
题
如何从URI获取实际的文件path
回答
据我所知,我们不需要从URI获取文件path,因为在大多数情况下,我们可以直接使用URI来完成我们的工作(比如1.获取位图2.发送文件到服务器等。)
1.发送到服务器
我们可以直接使用URI将文件发送到服务器。
使用URI我们可以得到InputStream,我们可以使用MultiPartEntity直接发送到服务器。
例
/** * Used to form Multi Entity for a URI (URI pointing to some file, which we got from other application). * * @param uri URI. * @param context Context. * @return Multi Part Entity. */ public MultipartEntity formMultiPartEntityForUri(final Uri uri, final Context context) { MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8")); try { InputStream inputStream = mContext.getContentResolver().openInputStream(uri); if (inputStream != null) { ContentBody contentBody = new InputStreamBody(inputStream, getFileNameFromUri(uri, context)); multipartEntity.addPart("[YOUR_KEY]", contentBody); } } catch (Exception exp) { Log.e("TAG", exp.getMessage()); } return multipartEntity; } /** * Used to get a file name from a URI. * * @param uri URI. * @param context Context. * @return File name from URI. */ public String getFileNameFromUri(final Uri uri, final Context context) { String fileName = null; if (uri != null) { // Get file name. // File Scheme. if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { File file = new File(uri.getPath()); fileName = file.getName(); } // Content Scheme. else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null); if (returnCursor != null && returnCursor.moveToFirst()) { int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); fileName = returnCursor.getString(nameIndex); returnCursor.close(); } } } return fileName; }
2.从URI获取一个BitMap
如果URI指向图像,那么我们将得到位图,否则为空:
/** * Used to create bitmap for the given URI. * <p> * 1. Convert the given URI to bitmap. * 2. Calculate ratio (depending on bitmap size) on how much we need to subSample the original bitmap. * 3. Create bitmap bitmap depending on the ration from URI. * 4. Reference - http://stackoverflow.com/questions/3879992/how-to-get-bitmap-from-an-uri * * @param context Context. * @param uri URI to the file. * @param bitmapSize Bitmap size required in PX. * @return Bitmap bitmap created for the given URI. * @throws IOException */ public static Bitmap createBitmapFromUri(final Context context, Uri uri, final int bitmapSize) throws IOException { // 1. Convert the given URI to bitmap. InputStream input = context.getContentResolver().openInputStream(uri); BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options(); onlyBoundsOptions.inJustDecodeBounds = true; onlyBoundsOptions.inDither = true;//optional onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional BitmapFactory.decodeStream(input, null, onlyBoundsOptions); input.close(); if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) { return null; } // 2. Calculate ratio. int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth; double ratio = (originalSize > bitmapSize) ? (originalSize / bitmapSize) : 1.0; // 3. Create bitmap. BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio); bitmapOptions.inDither = true;//optional bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional input = context.getContentResolver().openInputStream(uri); Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions); input.close(); return bitmap; } /** * For Bitmap option inSampleSize - We need to give value in power of two. * * @param ratio Ratio to be rounded of to power of two. * @return Ratio rounded of to nearest power of two. */ private static int getPowerOfTwoForSampleRatio(final double ratio) { int k = Integer.highestOneBit((int) Math.floor(ratio)); if (k == 0) return 1; else return k; }
注释
- Android没有提供任何从URI获取文件path的方法,并且在上面的大部分答案中,我们已经硬编码了一些常量,这些常量可能会在function发布中破解(对不起,我可能是错的)。
- 在直接从URI获取文件path的解决scheme之前,尝试使用URI和Android默认方法解决用例问题。
参考
我已经将多个答案结合到一个工作解决scheme中,并以文件path结果
MIMEtypes与示例目的无关。
Intent intent; if(Build.VERSION.SDK_INT >= 19){ intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); }else{ intent = new Intent(Intent.ACTION_GET_CONTENT); } intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setType("application/octet-stream"); if(isAdded()){ startActivityForResult(intent, RESULT_CODE); }
处理结果
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if( requestCode == RESULT_CODE && resultCode == Activity.RESULT_OK) { Uri uri = data.getData(); if (uri != null && !uri.toString().isEmpty()) { if(Build.VERSION.SDK_INT >= 19){ final int takeFlags = data.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION; //noinspection ResourceType getActivity().getContentResolver() .takePersistableUriPermission(uri, takeFlags); } String filePath = FilePickUtils.getSmartFilePath(getActivity(), uri); // do what you need with it... } } }
FilePickUtils
import android.annotation.SuppressLint; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; public class FilePickUtils { private static String getPathDeprecated(Context ctx, Uri uri) { if( uri == null ) { return null; } String[] projection = { MediaStore.Images.Media.DATA }; Cursor cursor = ctx.getContentResolver().query(uri, projection, null, null, null); if( cursor != null ){ int column_index = cursor .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); return cursor.getString(column_index); } return uri.getPath(); } public static String getSmartFilePath(Context ctx, Uri uri){ if (Build.VERSION.SDK_INT < 19) { return getPathDeprecated(ctx, uri); } return FilePickUtils.getPath(ctx, uri); } @SuppressLint("NewApi") 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()); } }
这个Android库处理KitKat中的大小写更改(包括oldere版本 – 2.1+):
https://github.com/iPaulPro/aFileChooser
使用String path = FileUtils.getPath(context, uri)
将返回的Uri转换为可在所有操作系统版本上使用的pathstring。 在这里看到更多关于它: https : //stackoverflow.com/a/20559175/860488
这就是我所做的:
Uri selectedImageURI = data.getData(); imageFile = new File(getRealPathFromURI(selectedImageURI)); private String getRealPathFromURI(Uri contentURI) { Cursor cursor = getContentResolver().query(contentURI, null, null, null, null); if (cursor == null) { // Source is Dropbox or other similar local file path return contentURI.getPath(); } else { cursor.moveToFirst(); int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); return cursor.getString(idx); } }
注意:
managedQuery()
方法已被弃用,所以我没有使用它。
这个答案是从m3n0R问题android得到真正的path通过Uri.getPath() ,我声称没有功劳。 我只是想,还没有解决这个问题的人可以使用这个。
对于仍在使用Android SDK版本23及以上的@Paul Burke代码的用户,如果您的项目遇到错误,说明您缺lessEXTERNAL_PERMISSION,并且您确定已经在AndroidManifest.xml文件中添加了用户权限。 这是因为您可能会在Android API 23或更高版本中使用Google,并且在执行操作以在运行时访问文件时,需要再次保证权限。
这意味着:如果您的SDK版本是23或以上,当您select图片文件并且想要知道它的URI时,您被请求读写权限。
以下是我的代码,除了保罗·伯克的解决scheme。 我添加这些代码,我的项目开始正常工作。
private static final int REQUEST_EXTERNAL_STORAGE = 1; private static final String[] PERMISSINOS_STORAGE = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; public static void verifyStoragePermissions(Activity activity) { int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); if (permission != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( activity, PERMISSINOS_STORAGE, REQUEST_EXTERNAL_STORAGE ); } }
而在你的活动和片段你要求的URI:
private void pickPhotoFromGallery() { CompatUtils.verifyStoragePermissions(this); Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); // startActivityForResult(intent, REQUEST_PHOTO_LIBRARY); startActivityForResult(Intent.createChooser(intent, "select照片"), REQUEST_PHOTO_LIBRARY); }
在我的情况下,CompatUtils.java是我定义verifyStoragePermissions方法(作为静态types,所以我可以在其他活动中调用它)。
在调用verifyStoragePermissions方法之前,如果首先创build一个if状态以查看当前的SDK版本是否高于23,那么它应该更有意义。
请尽量避免使用takePersistableUriPermission方法,因为它引发了我的运行时exception。 / ** *从图库中select。 * /
public void selectFromGallery() { if (Build.VERSION.SDK_INT < AppConstants.KITKAT_API_VERSION) { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); ((Activity)mCalledContext).startActivityForResult(intent,AppConstants.GALLERY_INTENT_CALLED); } else { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); ((Activity)mCalledContext).startActivityForResult(intent, AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED); } }
用于处理图像数据的OnActivity结果:
@Override保护无效onActivityResult(int requestCode,int resultCode,意图数据){
//gallery intent result handling before kit-kat version if(requestCode==AppConstants.GALLERY_INTENT_CALLED && resultCode == RESULT_OK) { Uri selectedImage = data.getData(); String[] filePathColumn = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query(selectedImage,filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String filePath = cursor.getString(columnIndex); cursor.close(); photoFile = new File(filePath); mImgCropping.startCropImage(photoFile,AppConstants.REQUEST_IMAGE_CROP); } //gallery intent result handling after kit-kat version else if (requestCode == AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED && resultCode == RESULT_OK) { Uri selectedImage = data.getData(); InputStream input = null; OutputStream output = null; try { //converting the input stream into file to crop the //selected image from sd-card. input = getApplicationContext().getContentResolver().openInputStream(selectedImage); try { photoFile = mImgCropping.createImageFile(); } catch (IOException e) { e.printStackTrace(); }catch(Exception e) { e.printStackTrace(); } output = new FileOutputStream(photoFile); int read = 0; byte[] bytes = new byte[1024]; while ((read = input.read(bytes)) != -1) { try { output.write(bytes, 0, read); } catch (IOException e) { e.printStackTrace(); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } }
只是想说这个答案很好,我用了很长时间没有问题。 但前段时间我偶然发现了一个问题,DownloadsProvider以格式content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdf
返回URI,因此应用程序与NumberFormatException
崩溃,因为它是不可能parsing它的uri段长。 但raw:
segment包含直接的uri,可以用来检索引用的文件。 所以我已经解决了它,通过replaceisDownloadsDocument(uri)
if
内容如下:
final String id = DocumentsContract.getDocumentId(uri); if (!TextUtils.isEmpty(id)) { if (id.startsWith("raw:")) { return id.replaceFirst("raw:", ""); } try { final Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(context, contentUri, null, null); } catch (NumberFormatException e) { Timber.e(e, "Downloads provider returned unexpected uri %s, quitting", uri.toString()); return null; } }
这是一个彻头彻尾的黑客,但这是我做的…
所以在设置一个DocumentsProvider的时候 ,我注意到, 示例代码 (在第450行左右的getDocIdForFile
)根据相对于指定的根的文件(唯一)path为选定的文档生成一个唯一的ID(即,你把mBaseDir
设置成96行)。
所以URI最终看起来像这样:
content://com.example.provider/document/root:path/to/the/file
正如文档所说,它假设只有一个根(在我的情况下是Environment.getExternalStorageDirectory()
但是你可以在别的地方使用…然后它从文件path开始,并且使其成为唯一的ID, “ root:
”。所以我可以通过删除uri.getPath()中的"/document/root:
”部分来确定path,通过这样做来创build实际的文件path:
public void onActivityResult(int requestCode, int resultCode, Intent data) { // check resultcodes and such, then... uri = data.getData(); if (uri.getAuthority().equals("com.example.provider")) { String path = Environment.getExternalStorageDirectory(0.toString() .concat("/") .concat(uri.getPath().substring("/document/root:".length()))); doSomethingWithThePath(path); } else { // another provider (maybe a cloud-based service such as GDrive) // created this uri. So handle it, or don't. You can allow specific // local filesystem providers, filter non-filesystem path results, etc. }
我知道。 这是可耻的,但它的工作。 同样,这依赖于您在您的应用程序中使用自己的文档提供程序来生成文档ID。
(另外,还有一个更好的方法来构build不认为“/”是path分隔符的path,但是你明白了。)
如果有人感兴趣,我为ACTION_GET_CONTENT
做了一个Kotlin版本的工作:
var path: String = uri.path // uri = any content Uri val databaseUri: Uri val selection: String? val selectionArgs: Array<String>? if (path.contains("/document/image:")) { // files selected from "Documents" databaseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI selection = "_id=?" selectionArgs = arrayOf(DocumentsContract.getDocumentId(uri).split(":")[1]) } else { // files selected from all other sources, especially on Samsung devices databaseUri = uri selection = null selectionArgs = null } try { val projection = arrayOf(MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID, MediaStore.Images.Media.ORIENTATION, MediaStore.Images.Media.DATE_TAKEN) // some example data you can query val cursor = context.contentResolver.query(databaseUri, projection, selection, selectionArgs, null) if (cursor.moveToFirst()) { // do whatever you like with the data } cursor.close() } catch (e: Exception) { Log.e(TAG, e.message, e) }
这对我来说很好:
else if(requestCode == GALLERY_ACTIVITY_NEW && resultCode == Activity.RESULT_OK) { Uri uri = data.getData(); Log.i(TAG, "old uri = " + uri); dumpImageMetaData(uri); try { ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); Log.i(TAG, "File descriptor " + fileDescriptor.toString()); final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); options.inSampleSize = BitmapHelper.calculateInSampleSize(options, User.PICTURE_MAX_WIDTH_IN_PIXELS, User.PICTURE_MAX_HEIGHT_IN_PIXELS); options.inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); imageViewPic.setImageBitmap(bitmap); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); // get byte array here byte[] picData = stream.toByteArray(); ParseFile picFile = new ParseFile(picData); user.setProfilePicture(picFile); } catch(FileNotFoundException exc) { Log.i(TAG, "File not found: " + exc.toString()); } }
我在这里尝试了几个答案,我想我有一个解决scheme,每次都会工作,并pipe理权限。
它基于LEO的聪明解决scheme。 这篇文章应该包含你所需要的所有代码,它可以在任何手机和Android版本上运行;)
为了能够从SD卡中select文件,您需要在清单中使用这个文件:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
常量:
private static final int PICK_IMAGE = 456; // Whatever number you like public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL = 28528; // Whatever number you like public static final String FILE_TEMP_NAME = "temp_image"; // Whatever file name you like
如果可能,请检查权限和launchImagePick
if (ContextCompat.checkSelfPermission(getThis(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(getThis(), new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXTERNAL); } else { launchImagePick(); }
权限响应
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { if (manageReadExternalPermissionResponse(this, requestCode, grantResults)) { launchImagePick(); } }
pipe理权限响应
public static boolean manageReadExternalPermissionResponse(final Activity activity, int requestCode, int[] grantResults) { if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL) { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission was granted, yay! Do the // contacts-related task you need to do. return true; } else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) { boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.READ_EXTERNAL_STORAGE); if (!showRationale) { // The user also CHECKED "never ask again". // You can either enable some fall back, // disable features of your app // or open another dialog explaining // again the permission and directing to // the app setting. } else { // The user did NOT check "never ask again". // This is a good place to explain the user // why you need the permission and ask if he/she wants // to accept it (the rationale). } } else { // Permission denied, boo! Disable the // functionality that depends on this permission. } } return false; }
Launch image pick
private void launchImagePick() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(intent, PICK_IMAGE); // see onActivityResult }
Manage Image pick response
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == PICK_IMAGE) { if (resultCode == Activity.RESULT_OK) { if (data != null && data.getData() != null) { try { InputStream inputStream = getContentResolver().openInputStream(data.getData()) if (inputStream != null) { // No special persmission needed to store the file like that FileOutputStream fos = openFileOutput(FILE_TEMP_NAME, Context.MODE_PRIVATE); final int BUFFER_SIZE = 1 << 10 << 3; // 8 KiB buffer byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead = -1; while ((bytesRead = inputStream.read(buffer)) > -1) { fos.write(buffer, 0, bytesRead); } inputStream.close(); fos.close(); File tempImageFile = new File(getFilesDir()+"/"+FILE_TEMP_NAME); // Do whatever you want with the File // Delete when not needed anymore deleteFile(FILE_TEMP_NAME); } } catch (Exception e) { e.printStackTrace(); } } else { // Error display } } else { // The user did not select any image } } }
That's all folks; this works for me on all the telephones I have.
Building up on Paul Burke's answer I faced many problems resolving external SD card's URI path as most of the suggested "built-in" functions return paths which do not get resolved to files.
However, this is my approach of his // TODO handle non-primary volumes .
String resolvedPath = ""; File[] possibleExtSdComposites = context.getExternalFilesDirs(null); for (File f : possibleExtSdComposites) { // Reset final path resolvedPath = ""; // Construct list of folders ArrayList<String> extSdSplit = new ArrayList<>(Arrays.asList(f.getPath().split("/"))); // Look for folder "<your_application_id>" int idx = extSdSplit.indexOf(BuildConfig.APPLICATION_ID); // ASSUMPTION: Expected to be found at depth 2 (in this case ExtSdCard's root is /storage/0000-0000/) - eg /storage/0000-0000/Android/data/<your_application_id>/files ArrayList<String> hierarchyList = new ArrayList<>(extSdSplit.subList(0, idx - 2)); // Construct list containing full possible path to the file hierarchyList.add(tail); String possibleFilePath = TextUtils.join("/", hierarchyList); // If file is found --> success if (idx != -1 && new File(possibleFilePath).exists()) { resolvedPath = possibleFilePath; break; } } if (!resolvedPath.equals("")) { return resolvedPath; } else { return null; }
Note it depends on hierarchy which might be different on every phone manufacturer – I have not tested them all (it worked well so far on Xperia Z3 API 23 and Samsung Galaxy A3 API 23).
Please confirm if it does not perform well elsewhere.
@paul burke's answer works fine for both camera and gallery pictures for API level 19 and above, but it doesn't work if your Android project's minimum SDK is set to below 19, and some answers referring above doesn't work for both gallery and camera. Well, I have modified @paul burke's code which works for API level below 19. Below is the code.
public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; Log.i("URI",uri+""); String result = uri+""; // DocumentProvider // if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { if (isKitKat && (result.contains("media.documents"))) { String[] ary = result.split("/"); int length = ary.length; String imgary = ary[length-1]; final String[] dat = imgary.split("%3A"); final String docId = dat[1]; final String type = dat[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { } else if ("audio".equals(type)) { } final String selection = "_id=?"; final String[] selectionArgs = new String[] { dat[1] }; return getDataColumn(context, contentUri, selection, selectionArgs); } else if ("content".equalsIgnoreCase(uri.getScheme())) { return getDataColumn(context, uri, null, null); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } 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; }
The answer to your question is that you need to have permissions. Type the following code in your manifest.xml file:
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission> <uses-permission android:name="android.permission.WRITE_OWNER_DATA"></uses-permission> <uses-permission android:name="android.permission.READ_OWNER_DATA"></uses-permission>`
它为我工作…