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

До KitKat (или до новой галереи) Intent.ACTION_GET_CONTENT возвращал такой URI

контент: // медиа / внешний / изображения / медиа / 3951.

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

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

content: //com.android.providers.media.documents/document/image: 3951

Как мне с этим справиться?


person Michael Greifeneder    schedule 07.11.2013    source источник
comment
Я бы нашел способы использовать контент, не требующий прямого доступа к файлу. Например, этот Uri должен быть открыт как поток через ContentResolver. Я давно нервничал по поводу приложений, которые предполагают, что content:// Uri, представляющий файл, всегда можно преобразовать в File.   -  person CommonsWare    schedule 07.11.2013
comment
Я думаю, что правильный ответ - перейти на ContentResolver и работать с Uri вместо URL-адресов файлов. Я сделаю это. Это также позволяет лучше обрабатывать Uri, не относящиеся к галерее.   -  person Michael Greifeneder    schedule 08.11.2013
comment
@ CommonsWare, если я хочу сохранить путь к изображению в sqlite db, чтобы открыть его позже, следует ли мне сохранять URI или абсолютный путь к файлу?   -  person Snake    schedule 20.12.2013
comment
@CommonsWare Я согласен с твоей нервозностью. :-) Однако мне нужно иметь возможность передавать имя файла (для изображения) в собственный код. Решение состоит в том, чтобы скопировать данные, полученные с помощью InputStream на ContentResolver, в заранее обозначенное место, чтобы оно имело известное имя файла. Однако для меня это звучит расточительно. Есть другие предложения?   -  person darrenp    schedule 15.01.2014
comment
@darrenp: Эммм ... переписать собственный код для работы с InputStream поверх JNI? К сожалению, для вас не так много вариантов.   -  person CommonsWare    schedule 15.01.2014
comment
Это полезно знать. Спасибо за ваш ответ. С тех пор я обнаружил, что теперь мы передаем изображение в C ++ в памяти, а не через файл, поэтому теперь мы можем использовать InputStream вместо файла (что отлично). Только чтение тегов EXIF ​​немного сложно и требует библиотеки Дрю Ноакса. Большое спасибо за ваши комментарии.   -  person darrenp    schedule 16.01.2014
comment
@Exception в кордове есть ошибка, связанная с этой проблемой, поэтому попробуйте найти ответ, он работает   -  person Spry Techies    schedule 25.09.2014
comment
@SpryTechies Но это работает только для изображений, насчет других типов медиа.   -  person Exception    schedule 25.09.2014
comment
@Exception Какой носитель вам нужен, укажите   -  person Spry Techies    schedule 25.09.2014
comment
@SpryTechies Я имею в виду видео файл.   -  person Exception    schedule 25.09.2014
comment
tagasks.com/   -  person DeltaCap019    schedule 30.05.2015
comment
лучший ответ здесь stackoverflow.com/questions/20067508/ от Пола Берка   -  person surhidamatya    schedule 01.10.2015
comment
MediaStore.Images.Media.DATA теперь не используется, а MediaStore.Images.Media._ID с contentWrapperUri всегда возвращает относительный URI, а не абсолютный URI. Это основная проблема для получения действительного Uri, и этот Uri не возвращает действительный путь к файлу, и возникает исключение FileNotFoundException.   -  person Aslam Hossin    schedule 24.01.2021


Ответы (20)


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

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

person finder    schedule 09.11.2013
comment
Не могли бы вы подробнее рассказать, что делает код KitKat? Требуется ли новое разрешение? Код pre KitKat у меня работает и на KitKat. Так почему я должен использовать другой код для KitKat? Спасибо. - person Michael Greifeneder; 09.11.2013
comment
Мой старый код отказывался работать на KitKat. Мне пришлось прочитать руководство. Хорошо, что новых разрешений не требуется. - person finder; 09.11.2013
comment
похоже, мы не можем получить путь от нового sdks uri. Также жаль, что Google внес такие изменения без надлежащей документации и объявления. - person user65721; 17.11.2013
comment
Не могли бы вы объяснить, как получить URL-адрес файла? Я хотел бы получить реальный путь в sdcard. Например, если это изображение, я хотел бы получить этот путь /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg вместо Uri документа. - person Álvaro; 20.11.2013
comment
На основе документации KitKat (developer.android.com/about/versions/) это может быть не то, что нужно OP, если он не намеревается использовать / редактировать документы, принадлежащие другому приложению (ям). Если OP хочет копию или что-то делать в соответствии со старыми версиями, ответ от @voytez будет более подходящим. - person Colin M.; 28.11.2013
comment
У меня это не работает. Я получаю следующее исключение (в наличии 4.4.2): E / AndroidRuntime (29204): Вызвано: java.lang.SecurityException: Запрошенные флаги 0x1, но разрешены только 0x0 - person Russell Stewart; 11.02.2014
comment
если data.getData () имеет значение null, используйте: bitmap = (Bitmap) data.getExtras (). get (data); - person Skywalker; 06.01.2015
comment
Попал на Самсунг s7, нуга. SecurityException: не найдено постоянных разрешений для UID 10206 и Uri 0 @ content: //com.android.providers.media.documents/document/image: 958 - person murt; 25.10.2017

Это не требует специальных разрешений и работает с Storage Access Framework, а также с неофициальным шаблоном ContentProvider (путь к файлу в поле _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());
}

Актуальную версию этого метода можно найти здесь.

person Paul Burke    schedule 13.12.2013
comment
Это фантастически работало на интерфейсе 4.4 Nexus 5 Documents UI и некоторых других предварительно подготовленных устройствах KitKat с использованием стандартных приложений галереи, спасибо, Пол! - person Josh; 14.12.2013
comment
Спасибо за это, мне потребовалось время, чтобы так далеко продвинуться с sdk 19 !! Моя проблема в том, что мое устройство использует Google Диск в качестве файлового браузера. Если файл находится на пути к образу устройства, все в порядке, но если файл находится на диске, он не открывается. Может, мне просто нужно взглянуть на то, как открывать изображения с диска Google. Дело в том, что мое приложение написано для использования пути к файлу и получения изображения с использованием выборки ... - person RuAware; 18.12.2013
comment
@RuAware Когда вы выбираете файл на Диске, он возвращает Authority: com.google.android.apps.docs.storage и Segments: [document, acc=1;doc=667]. Я не уверен, но предположим, что значение doc - это идентификатор Uri, по которому вы можете запросить. Скорее всего, вам потребуются разрешения, как описано в разделе Авторизация приложения на Android здесь: developers.google.com/drive/integrate-android-ui. Пожалуйста, обновите здесь, если вы в этом разобрались. - person Paul Burke; 18.12.2013
comment
Спасибо, я подумал, что если они решат использовать Диск, он выполнит всю загрузку и т. Д. И просто отдаст мне файл. Поскольку я не хочу использовать его для загрузки. Кажется, что программировать для этого - это странно. Спасибо еще раз - person RuAware; 19.12.2013
comment
Эй, это пока работает очень хорошо, большое спасибо! Решалась ли проблема с Google Диском? - person MBillau; 17.01.2014
comment
Я столкнулся с той же проблемой с использованием плагина File, значение id не является фактическим путем, могу ли я использовать то же решение? и подскажите пожалуйста, как вызвать этот метод из класса файлового плагина, для меня это срочно :( - person Abou-Emish; 19.02.2014
comment
Мне нравится, что ваш код совместим с checkstyle, за исключением отсутствия @return в исходном методе javadoc (и нескольких других незначительных лакомых кусочках). Престижность, сэр! Обратите внимание, что для этого также требовалась версия 4.4, поэтому я пометил ее аннотацией @TargetApi (Build.VERSION_CODES.KITKAT) - о, и, наконец, приятно отметить, что кто-то, кроме меня, активно использует final. :-) - person treejanitor; 24.03.2014
comment
Это решение прекрасно работает! Спасибо за это. @RuAware, мне интересно, была ли решена проблема с Google Диском! Я тоже нуждался в себе :) - person Dennis Anderson; 21.08.2014
comment
это абсолютно ужасно! вам не следует продолжать распространение кода, который использует подобные читы. он поддерживает только исходные приложения, для которых вы знаете шаблон, и весь смысл модели поставщика документов заключается в поддержке произвольных источников. - person j__m; 30.09.2014
comment
Используя if (isKitKat && DocumentsContract.isDocumentUri(context, uri)), предполагаете ли вы, что если isKitKat неверно, то DocumentsContract.isDocumentUri(context, uri) не будет проверяться (разумное мышление)? Если да, то как вы можете быть уверены в этом? если нет, то не следует ли сначала проверить if (isKitKat), а затем if (DocumentsContract.isDocumentUri(context, uri))? @ Пол Берк - person user2162550; 16.04.2015
comment
Обходной путь для нового приложения Google Фото gist.github.com/alexzaitsev/75c9b36dfcffd545c676 - person rocknow; 04.06.2015
comment
@PaulBurke: Как с помощью этого кода получить PDF-файл или текстовый файл с Google Диска? Пожалуйста, помогите мне как можно скорее - person Anand Savjani; 28.08.2015
comment
Для некоторых из моих пользователей я получаю нулевой путь, если uri.getScheme () использует этот код. Есть идеи, как исправить этот @PaulBurke, поскольку это серьезная ошибка в приложении? - person Code; 04.11.2015
comment
@ user2162550 считает if (a() && b()). В этом случае, если a ложно, b не будет проверяться JVM, поскольку нет точки проверки для b - результат уже ложный. - person Yaroslav Mytkalyk; 04.12.2015
comment
Привет, если я использую приведенный выше код вместе со сторонним файловым проводником, в конечном итоге будет возвращено значение null. Мне было интересно, это недостаток приведенного выше кода или неправильная реализация стороннего приложения для просмотра файлов? Спасибо - stackoverflow.com/q/34465905/72437 - person Cheok Yan Cheng; 25.12.2015
comment
Май 2016 и все еще работаю над зефиром с минимальной целью api 15. Отличная работа - person jcromanu; 28.05.2016
comment
›Получите значение столбца данных для этого Uri. Это полезно для MediaStore Uris и других файловых ContentProvider. Какова цель getDataColumn? Что оно делает? - person Sudhir Singh Khanger; 09.09.2016
comment
›// TODO обрабатывает неосновные тома. Работает ли он с внешним хранилищем, например с внешними SD-картами? - person Sudhir Singh Khanger; 10.09.2016
comment
_data не будет работать, если ContentProvider его не поддерживает. Рекомендуется следовать инструкциям @CommonsWare. и больше не использовать полный путь к файлу, потому что это может быть файл в облаке Dropbox, а не реальный файл. - person soshial; 27.12.2016
comment
У меня это было в моем приложении, и теперь оно не работает на устройствах Android 7+ Nougat. - person Marian Klühspies; 12.06.2017
comment
Для тех, кто получает Error Unknown URI: content://downloads/public_downloads stackoverflow.com/a/53021624/5996106 - person karanatwal.github.io; 27.10.2018

У меня была та же проблема, я попробовал решение, указанное выше, но, хотя в целом оно работало, по какой-то причине я получал отказ в разрешении от поставщика контента 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_GET_CONTENT в сочетании с c.getContentResolver().openInputStream(selectedImageURI), который будет работать как на pre-KK, так и на KK. Kitkat будет использовать новое представление документов, и это решение будет работать со всеми приложениями, такими как Фотографии, Галерея, Проводник, Dropbox, Google Диск и т. Д.), Но помните, что при использовании этого решения вы должны создать изображение в вашем onActivityResult() и сохранить его. на SD-карту, например. Воссоздание этого изображения из сохраненного uri при следующем запуске приложения вызовет исключение безопасности на преобразователе контента, даже если вы добавите флаги разрешений, как описано в документации Google API (это то, что произошло, когда я провел некоторое тестирование)

Кроме того, в Руководстве по API для разработчиков Android предлагается:

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

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

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

person voytez    schedule 24.11.2013
comment
Этот ответ содержал правильную информацию для моих целей. Условное использование ACTION_PICK и EXTERNAL_CONTENT_URI в KitKat обеспечивает такую ​​же возможность получения метаданных об изображениях в галерее через ContentResolver, как это возможно в более старых версиях, используя просто ACTION_GET_CONTENT. - person Colin M.; 28.11.2013
comment
@voytez, может ли этот URI, возвращаемый через ваше сообщение, преобразовать в полный реальный путь изображения? - person Snake; 20.12.2013
comment
Я считаю, что это должно работать так же, как и до KitKat, поскольку этот код заставляет открывать галерею изображений вместо просмотра документов KK. Но если вы собираетесь использовать его для создания изображения, тогда это решение лучше, так как преобразование в реальный путь - лишний ненужный шаг. - person voytez; 13.01.2014
comment
У меня тоже работало, а не Intent.ACTION_GET_CONTENT. В любом случае я оставил оболочку Intent.createChooser() на новом Intent, чтобы пользователь мог выбирать приложение для просмотра, и работал так, как ожидалось. Может кто увидит недостатки этого решения? - person lorenzo-s; 15.04.2014
comment
Не могли бы вы уточнить, работает ли вызов намерения ACTION_PICK / EXTERNAL_CONTENT_URI в pre-KitKat, например 4.3 (API 18)? - person weiy; 25.04.2015
comment
что, если мне нужно выбрать аудиофайл? - person Onheiron; 05.08.2015
comment
Для всех, кому интересно, цитата взята с сайта developer.android.com/ гид / темы / провайдеры / - person snark; 04.11.2017

Как упоминалось в 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. Но это уже другая тема.

person Michał Klimczak    schedule 22.11.2013
comment
У меня это не работает с Nexus 4 под управлением Kitkat, но с Galaxy S3 под управлением 4.1.2. - person Dan2552; 10.12.2013
comment
@ Dan2552 какая часть не работает? Есть ли у вас исключение? - person Michał Klimczak; 18.12.2013
comment
Оказалось, что я использовал неправильный вызов галереи. Я использовал тот, который был предназначен для любых документов, но с фильтром расширений файлов. - person Dan2552; 18.12.2013
comment
Какой красиво простой ответ, спасибо! Для других, следующих за этим ответом, «cxt» относится к текущему контексту и обычно будет «this». - person thomasforth; 14.01.2015
comment
Спасибо, отредактирую свой ответ, чтобы было понятно, что такое cxt - person Michał Klimczak; 14.01.2015
comment
Это не работает для меня, я получаю FileNotFoundException, вызывающий openInputStream на LG Nexus 5 с Android 4.4.4 на uri, например: content: //com.android.providers.downloads.documents/document/532 - person Alex Black; 14.10.2015
comment
Вероятно, это означает, что файла просто нет. URI кажется нормальным. - person Michał Klimczak; 14.10.2015
comment
Большое вам спасибо, все работает, вы только что сделали мой день. - person Shruti; 27.08.2016
comment
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getActivity().getContentResolver(), uri). Что в этом плохого? - person Sudhir Singh Khanger; 10.09.2016
comment
Как можно использовать этот вход для записи данных EXIF ​​в версии изображений до Android 7.1. ? В Android 7.1 ExifInterface может использовать inputStream, но в более ранних версиях ExifInterface принимает только String в качестве аргумента. - person Thracian; 27.09.2017

Я считаю, что уже опубликованные ответы должны побудить людей двигаться в правильном направлении. Однако вот то, что я сделал, имело смысл для устаревшего кода, который я обновлял. Унаследованный код использовал 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. Код не публикуется.

person LEO    schedule 29.01.2014
comment
довольно умно. мне тоже нужен был актуальный файл uri - person greaterKing; 16.03.2014
comment
ты мой герой, это именно то, что мне нужно! отлично работает и с файлами Google Диска - person mishkin; 26.05.2015
comment
Думаю нестандартно, правда? : D Этот код работает именно так, как я ожидал. - person WeirdElfB0y; 24.10.2015
comment
опубликуйте код для copyAndClose, ответ не полный - person Bartek Pacia; 25.11.2019

Просто хотел сказать, что этот ответ великолепен, и я уже давно использую его без проблем. Но некоторое время назад я столкнулся с проблемой, что DownloadsProvider возвращает URI в формате content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdf, и, следовательно, приложение вылетает из-за NumberFormatException, так как невозможно анализировать его сегменты uri до тех пор. Но сегмент raw: содержит прямой uri, который можно использовать для получения файла, на который есть ссылка. Итак, я исправил это, заменив содержимое isDownloadsDocument(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) {
    Log.e("FileUtils", "Downloads provider returned unexpected uri " + uri.toString(), e);
    return null;
}
}
person Bringoff    schedule 29.09.2017
comment
Отлично работает! Спасибо - person mikemike396; 29.07.2018
comment
Perfecto, спасибо @Bringoff - person Ravi Vaniya; 30.09.2020
comment
Идеальный рабочий - person jagadishlakkurcom jagadishlakk; 14.06.2021

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

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

}
person Grzegorz Pawełczuk    schedule 05.05.2015
comment
Я столкнулся с проблемой .... uri.getPath () возвращал uri с / external, и он не возвращал путь. Я добавил это еще блок if (content.equalsIgnoreCase (uri.getScheme ())), и теперь он работает хорошо .. вы можете объяснить, что он делает - person FaisalAhmed; 26.04.2017
comment
filePath получает значение null .. В uri: content: //com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png - person Bhavesh Moradiya; 07.12.2018

Вопрос

Как получить фактический путь к файлу из 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 указывает на изображение, мы получим растровое изображение, иначе 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
person Vasanth    schedule 25.08.2016
comment
Спасибо. Использование Uri и ContentResolver таким образом значительно упростило мое приложение при работе с файлами. - person jacksonakj; 20.09.2016

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

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

person Morten Holmgaard    schedule 01.11.2014

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

Это означает: если у вас версия SDK 23 или выше, вам будет предложено разрешение на ЧТЕНИЕ и ЗАПИСЬ, когда вы выбираете файл изображения и хотите узнать его 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.

person Anthonyeef    schedule 24.11.2015

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

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

person Isaac Zais    schedule 17.01.2014
comment
Это не ответ на новое приложение Gallery (строго приложение Media Documents Provider) на KitKat. - person nagoya0; 19.01.2014
comment
Приложение, которое спрашивающий называет Gallery, вероятно, является новым средством выбора файлов на kitkat. К вашему сведению: addictivetips.com/android/ - person nagoya0; 25.01.2014
comment
Я сделал то же самое и получил IndexOutOfBound на Nexus 5X, Android 6 в этой строке: cursor.getString(idx); - person Osify; 26.10.2016

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

Он основан на умном решении от 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

Проверьте разрешение и, если возможно, запуститеImagePick

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

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

person Quentin G.    schedule 16.03.2017

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

var path: String = uri.path // uri = any content Uri
val databaseUri: Uri
val selection: String?
val selectionArgs: Array<String>?
if ("/document/image:" in path || "/document/image%3A" in path) {
    // 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)
}
person 0101100101    schedule 08.03.2017
comment
Мне просто нужен рабочий код котлина. Для меня это работа. Благодарность - person Harvi Sirja; 03.01.2019

Постарайтесь избегать использования метода 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, данные намерения) {

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


    }
person saranya    schedule 04.01.2015
comment
Где вы показываете изображение во втором случае? - person Quantum_VC; 02.04.2015
comment
извините, мне не удалось добавить эту строку кода в else if mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); снова нужно вызвать функцию кадрирования изображений в соответствии с потребностями моего проекта - person saranya; 02.04.2015
comment
Какой тип файла - это photoFile и mImgCropping? - person Philip Belgrave-Herbert; 17.06.2015

Это полный взлом, но вот что я сделал ...

Итак, играя с настройкой DocumentsProvider, я заметил, что образец кода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.
}

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

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

person fattire    schedule 26.11.2013
comment
Чтобы ответить себе еще более сумасшедшей мыслью - если ваше приложение уже обрабатывает file:// намерения из внешнего средства выбора файлов, вы также можете проверить полномочия, как в приведенном выше примере, чтобы убедиться, что оно от вашего пользовательского поставщика, и если да, то вы также можно использовать путь для создания нового file:// намерения, используя извлеченный вами путь, а затем StartActivity() и позволить вашему приложению подобрать его. Я знаю, ужасно. - person fattire; 26.11.2013

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

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());
    }
}
person Rafa    schedule 06.10.2015
comment
забудьте dumpImageMetaData (uri); это не нужно - person Rafa; 06.10.2015

Основываясь на ответе Пола Берка, я столкнулся со многими проблемами при разрешении пути URI внешней SD-карты, поскольку большинство из предлагаемых «встроенных» функции возвращают пути, которые не разрешаются в файлы.

Однако это мой подход его // TODO к работе с неосновными томами.

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/) - e.g. /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;
}

Обратите внимание, что это зависит от иерархии, которая может быть разной для каждого производителя телефона - я не тестировал их все (до сих пор она хорошо работала на Xperia Z3 API 23 и Samsung Galaxy A3 API 23).

Пожалуйста, подтвердите, если он не работает хорошо в другом месте.

person Evusas    schedule 06.06.2017

для этого типа uri content: //com.android.providers.media.documents/document/document%3A19298 или uri.getAuthority()является любым из этих

"com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority());

используйте эту функцию

private static String getDriveFilePath(Uri uri, Context context) {
        Uri returnUri = uri;
        Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);

        int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
        returnCursor.moveToFirst();
        String name = (returnCursor.getString(nameIndex));
        String size = (Long.toString(returnCursor.getLong(sizeIndex)));
        File file = new File(context.getCacheDir(), name);
        try {
            InputStream inputStream = context.getContentResolver().openInputStream(uri);
            FileOutputStream outputStream = new FileOutputStream(file);
            int read = 0;
            int maxBufferSize = 1 * 1024 * 1024;
            int bytesAvailable = inputStream.available();

            //int bufferSize = 1024;
            int bufferSize = Math.min(bytesAvailable, maxBufferSize);

            final byte[] buffers = new byte[bufferSize];
            while ((read = inputStream.read(buffers)) != -1) {
                outputStream.write(buffers, 0, read);
            }
            Log.e("File Size", "Size " + file.length());
            inputStream.close();
            outputStream.close();
            Log.e("File Path", "Path " + file.getPath());
            Log.e("File Size", "Size " + file.length());
        } catch (Exception e) {
            Log.e("Exception", e.getMessage());
        }
        return file.getPath();
    }
person Ilyas Arafath    schedule 19.06.2021

Ответ @paul Burke отлично работает как для изображений камеры, так и для изображений галереи для уровня API 19 и выше, но он не работает, если минимальный SDK вашего проекта Android установлен ниже 19, а некоторые ответы, упомянутые выше, не работают как для галереи, так и для камера. Что ж, я изменил код @paul Burke, который работает для уровня API ниже 19. Ниже приведен код.

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;
}
person parvez rafi    schedule 20.01.2016
comment
Я получаю java.lang.IllegalArgumentException: ни один из запрошенных столбцов не может быть предоставлен при выборе изображения Google Doc - person dirkoneill; 20.02.2016
comment
@dirkoneill Я получаю такое же исключение. Вы нашли исправление? - person Henry; 23.02.2016

Ответ на ваш вопрос: у вас должны быть разрешения. Введите следующий код в свой файл manifest.xml:

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

У меня это сработало ...

person ashwin    schedule 05.02.2014