Android Gallery на Android 4.4 (KitKat) возвращает другой URI для Intent.ACTION_GET_CONTENT

Перед KitKat (или до новой галереи) Intent.ACTION_GET_CONTENT вернул URI, как это

Содержание: // СМИ / внешние / изображения / СМИ / 3951.

Использование ContentResolver и ContentResolver для MediaStore.Images.Media.DATA вернули URL-адрес файла.

Однако в KitKat Галерея возвращает URI (через «Последний»), например:

Содержание: //com.android.providers.media.documents/document/image: 3951

Как мне это сделать?

Solutions Collecting From Web of "Android Gallery на Android 4.4 (KitKat) возвращает другой URI для Intent.ACTION_GET_CONTENT"

Попробуй это:

 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

Это не требует специальных разрешений и работает с _data данных доступа к хранилищу, а также с неофициальным шаблоном _data (путь к _data поле _data ).

 /** * 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()); } 

См. Последнюю версию этого метода здесь .

Если бы та же проблема, попробовал решение выше, но хотя он работал в целом, по какой-то причине я получал разрешение на предоставление провайдером контента Uri для некоторых изображений, хотя у меня было правильно добавлено разрешение android.permission.MANAGE_DOCUMENTS .

В любом случае найдено другое решение, которое должно заставить открытую галерею изображений вместо просмотра документов 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 может потребовать, чтобы вы сохраняли флаги разрешений и т. Д. И обычно часто ACTION_OPEN_DOCUMENT к исключениям безопасности …

Другое решение – использовать ACTION_GET_CONTENT сочетании с c.getContentResolver().openInputStream(selectedImageURI) который будет работать как на pre-KK, так и на KK. Тогда Kitkat будет использовать новые документы, и это решение будет работать со всеми приложениями, такими как Photos, Gallery, File Explorer, Dropbox, Google Drive и т. Д.), Но помните, что при использовании этого решения вам необходимо создать изображение в вашем onActivityResult() и Например, хранить его на SD-карте. Воспроизведение этого изображения из сохраненного uri при следующем запуске приложения приведет к исключению Security Exception для распознавателя контента, даже если вы добавите флаги разрешений, как описано в документах API Google API (это произошло, когда я провел некоторое тестирование)

Кроме того, рекомендации API Android Developer предлагают:

ACTION_OPEN_DOCUMENT не предназначен для замены ACTION_GET_CONTENT. Тот, который вы должны использовать, зависит от потребностей вашего приложения:

Используйте ACTION_GET_CONTENT, если вы хотите, чтобы ваше приложение просто читало / импортировало данные. При таком подходе приложение импортирует копию данных, таких как файл изображения.

Используйте ACTION_OPEN_DOCUMENT, если вы хотите, чтобы ваше приложение имело долгосрочный постоянный доступ к документам, принадлежащим поставщику документов. Примером может служить приложение для редактирования фотографий, которое позволяет пользователям редактировать изображения, хранящиеся в поставщике документов.

Как упоминалось в Commonsware, вы не должны предполагать, что поток, который вы получаете через ContentResolver можно преобразовать в файл.

Вам действительно нужно открыть InputStream из ContentProvider , а затем создать из него 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 (и google drive) 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 () просто выполняет ввод / вывод файлов для копирования InputStream в FileOutputStream. Код не размещен.

Эта Android-библиотека обрабатывает изменения в приложении KitKat (включая старые версии – 2.1+):
https://github.com/iPaulPro/aFileChooser

Используйте String path = FileUtils.getPath(context, uri) чтобы преобразовать возвращаемый Uri в строку пути, используемую для всех версий ОС. Подробнее об этом можно узнать здесь: https://stackoverflow.com/a/20559175/860488

Я объединять несколько ответов в одно рабочее решение, которое приводит к пути к файлу

Тип Mime не подходит для примера.

  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()); } } 

Вопрос

Как получить фактический путь к файлу из URI

Ответ

Насколько мне известно, нам не нужно получать путь к файлу из URI, потому что для большинства случаев мы можем напрямую использовать 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. Получение BitMap из URI

Если URI указывает на изображение, мы получим bitmap, иначе null:

 /** * 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; } 

Комментарии

  1. Android не предоставляет каких-либо методов для получения пути к файлам из URI, и в большинстве вышеперечисленных ответов мы жестко закодировали некоторые константы, которые могут сломаться в выпуске функций (извините, я могу ошибаться).
  2. Прежде чем перейти непосредственно к решению получения пути к файлу из URI, попробуйте, если вы сможете решить свой вариант использования с помощью методов URI и Android по умолчанию.

Справка

  1. https://developer.android.com/guide/topics/providers/content-provider-basics.html
  2. https://developer.android.com/reference/android/content/ContentResolver.html
  3. https://hc.apache.org/httpcomponents-client-ga/httpmime/apidocs/org/apache/http/entity/mime/content/InputStreamBody.html

Вот что я делаю:

 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 по вопросу андроида получить реальный путь Uri.getPath (), и я не претендую на кредит. Я просто подумал, что люди, которые еще не решили эту проблему, могут использовать это.

Для тех, кто по-прежнему использует код @Paul Burke с Android SDK версии 23 и выше, если ваш проект встретил ошибку, заявив, что вам не хватает EXTERNAL_PERMISSION, и вы очень уверены, что уже добавили пользовательское разрешение в свой файл AndroidManifest.xml. Это связано с тем, что вы можете в Android API 23 или выше, и Google делает необходимым снова гарантировать разрешение, пока вы делаете действие для доступа к файлу во время выполнения.

Это означает: если ваша версия SDK равна 23 или выше, вам предлагается разрешение READ & WRITE, когда вы выбираете файл изображения и хотите узнать его URI.

И следующий мой код, в дополнение к решению Пола Берка. Я добавляю этот код, и мой проект начинает работать нормально.

 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, "选择照片"), REQUEST_PHOTO_LIBRARY); } 

В моем случае, CompatUtils.java – это то, где я определяю метод verifyStoragePermissions (как статический тип, поэтому я могу назвать его в рамках другого действия).

Также должно быть более разумно, если вы сначала определите состояние if, чтобы увидеть, является ли текущая версия SDK выше 23 или нет, прежде чем вы вызовете метод verifyStoragePermissions.

Пожалуйста, старайтесь избегать использования метода takePersistableUriPermission, потому что для него возникло исключение во время выполнения. / ** * Выберите из галереи. * /

 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 protected void onActivityResult (int requestCode, int resultCode, Intent data) {

  //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(); } } 

Это полный хак, но вот что я сделал …

Поэтому, играя с настройкой DocumentProvider , я заметил, что пример кода (в getDocIdForFile , около строки 450) генерирует уникальный идентификатор для выбранного документа на основе пути (уникального) файла относительно указанного вами корня, который вы ему даете (это , Что вы установили mBaseDir в строку 96).

Итак, URI выглядит примерно так:

content://com.example.provider/document/root:path/to/the/file

Как говорят документы, он предполагает только один корень (в моем случае это Environment.getExternalStorageDirectory() но вы можете использовать где-то еще … тогда он берет путь к файлу, начиная с корня, и делает его уникальным идентификатором, добавляя « root: ». Поэтому я могу определить путь, исключив часть "/document/root: » из uri.getPath (), создав фактический путь к файлу, выполнив что-то вроде этого:

 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. } 

Я знаю. Это позорно, но это сработало. Опять же, это зависит от того, используете ли вы свой собственный поставщик документов в своем приложении для создания идентификатора документа.

(Кроме того, есть лучший способ построить путь, который не предполагает, что «/» – разделитель путей и т. Д. Но вы получаете идею.)

Если кто-то заинтересован, я создал рабочую версию Kotlin для ACTION_GET_CONTENT :

 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()); } } 

Я попробовал несколько ответов здесь, и я думаю, что у меня есть решение, которое будет работать каждый раз и управлять разрешениями.

Он основан на умном решении от LEO. Этот пост должен содержать весь код, необходимый для выполнения этой работы, и он должен работать на любом телефоне и версии Android;)

Чтобы иметь возможность выбирать файл с SD-карты, вам понадобится это в вашем манифесте:

 <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 

Проверьте разрешение и нажмите кнопку запуска.

 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(); } } 

Управление разрешением ответа

 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; } 

Запуск выбора изображения

 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 } 

Управление ответом на выбор изображения

 @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 } } } 

Это все люди; Это работает для меня на всех телефонах, которые у меня есть.

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>` 

Это сработало для меня …