Android Gallery на Android 4.4 (KitKat) повертає різні URI для наміру.ACTION_GET_CONTENT


214

Перед KitKat (або перед новою галереєю) Intent.ACTION_GET_CONTENTповертався URI, подібний до цього

зміст: // медіа / зовнішній / образи / медіа / 3951.

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

Однак у KitKat Галерея повертає URI (через "Останній") так:

зміст: //com.android.providers.media.documents/document/image: 3951

Як мені впоратися з цим?


21
З манжети я знайду способи використання вмісту, який не потребує прямого доступу до файлу. Наприклад, це Uriможе бути відкрито як потік через ContentResolver. Я довго нервував програми, які припускають, що файл, content:// Uriякий представляє файл, завжди можна перетворити на File.
CommonsWare

1
@ CommonsWare, Якщо я хочу зберегти шлях зображення у sqlite db, щоб потім його відкрити, чи потрібно зберегти URI або абсолютний шлях до файлу?
Змія

2
@CommonsОбережно, я погоджуюся з вашою нервозністю. :-) Однак мені потрібно мати можливість передавати ім'я файлу (для зображення) до рідного коду. Рішення полягає в копіюванні даних, отриманих за допомогою символу, InputStreamна ContentResolverзаздалегідь відведене місце, щоб вони мали відоме ім'я файлу. Однак це звучить для мене марно. Будь-які інші пропозиції?
darrenp

2
@darrenp: Уммм ..., перепишіть власний код для роботи з InputStreamнад JNI? На жаль, для вас не так багато варіантів.
CommonsWare

1
Це корисно знати. Дякую за Вашу відповідь. З того часу я з'ясував, що зараз ми передаємо зображення C ++ у пам'ять, а не через файл, тому ми тепер можемо використовувати InputStreamзамість файлу (що чудово). Лише читання тегів EXIF ​​є дещо складним і вимагає бібліотеки Дрю Ноакса . Велике спасибі за ваші коментарі.
darrenp

Відповіді:


108

Спробуйте це:

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


1
Ви б не хотіли детальніше розробити, що робить код KitKat? Для цього потрібен новий дозвіл? Код попереднього KitKat працює для мене і в KitKat. То чому я б вирішив використовувати інший код для KitKat? Дякую.
Майкл Грейфенедер

67
здається, ми не можемо отримати шлях від нових sdks uri. Також соромно, що Google вчинив подібні зміни без належної документації та оголошення.
користувач65721

1
Чи можете ви пояснити, як отримати URL-адресу файлу? Я хотів би отримати реальний шлях у sdcard. Наприклад, якщо це зображення, я хотів би отримати цей шлях /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg замість документа Uri.
Альваро

4
На основі документів KitKat ( developer.android.com/about/versions/… ) це може бути не те, що потрібно ОП, якщо він не має наміру використовувати / редагувати документи, які належать іншій програмі. Якщо ОП хоче копіювати або робити речі у відповідність до старих версій, відповідь @voytez буде більш доречною.
Колін М.

8
Це не працює для мене. Я отримую такий виняток (на складі 4.4.2): E / AndroidRuntime (29204): Викликано: java.lang.SecurityException: Запрошені прапори 0x1, але дозволено лише 0x0
Russell Stewart

177

Це не вимагає спеціальних дозволів і працює з рамкою доступу до зберігання, а також з неофіційною 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());
}

Дивіться оновлену версію цього методу тут .


2
Це фантастично спрацювало на користувальницькому інтерфейсі 4.4 Nexus 5 Documents та деяких інших пристроях Pref KitKat, використовуючи стандартні додатки галереї, дякую Пол!
Джош

1
Дякую за це, мені потрібні віки, щоб досягти цього далеко з sdk 19 !! Моя проблема полягає в тому, що мій пристрій використовує диск Google як браузер файлів. Якщо файл перебуває на пристрої, шлях зображення у вас виходить нормальним, але якщо файл знаходиться на диску, він не відкривається. Можливо, мені просто потрібно розібратися з відкриттям зображень з Google Drive. Річ у моєму додатку написано, щоб використовувати шлях до файлу та отримувати зображення за допомогою накреслення ...
RuAware

2
@RuAware При виборі файлу Диска він повертає Authority: com.google.android.apps.docs.storageта Segments: [document, acc=1;doc=667]. Я не впевнений, але припустимо, що docцінність - це Uriідентифікатор, до якого можна запитати. Вам, ймовірно, потрібні дозволи буде налаштовано як детальніше в розділі "Авторизуйте додаток на Android" тут: developers.google.com/drive/integrate-android-ui . Будь ласка, оновіть тут, якщо ви це зрозумієте.
Пол Берк

30
це абсолютно жахливо! не слід продовжувати поширювати код, який "обманює", як це. він підтримує лише вихідні програми, для яких вам відомий зразок, і вся суть моделі постачальника документів полягає в підтримці довільних джерел
j__m

2
Це _dataне працює, коли ContentProvider не підтримує його. Рекомендується дотримуватися інструкцій @CommonsWare і більше не використовувати повний шлях до файлу, оскільки це може бути файл у хмарі Dropbox замість реального файлу.
сошіал

67

З тією ж проблемою я спробував рішення вище, але, хоча воно працювало загалом, я чомусь отримував відмову у дозволі від постачальника вмісту 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);

EDIT

ACTION_OPEN_DOCUMENT може зажадати від вас зберігати прапорці дозволів тощо, і зазвичай це призводить до виключень із безпеки ...

Іншим рішенням є використання ACTION_GET_CONTENTкомбінованого, з c.getContentResolver().openInputStream(selectedImageURI)яким буде працювати як на пре-КК, так і на КК. Kitkat буде використовувати нові подання документів, і це рішення працюватиме з усіма програмами, такими як "Фотографії", "Галерея", "Провідник файлів", "Dropbox", "Google Drive тощо"), але пам’ятайте, що при використанні цього рішення ви повинні створити зображення у вашому onActivityResult()і зберегти його на SD карта, наприклад. Відтворення цього зображення із збереженого урі під час наступного запуску програми призведе до того, що виняток безпеки буде вирішено для вирішення вмісту, навіть коли ви додасте прапор дозволів, як описано в документах Google API (саме це сталося, коли я робив тестування)

Крім того, Рекомендації щодо API для розробників Android пропонують:

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

Використовуйте ACTION_GET_CONTENT, якщо ви хочете, щоб ваш додаток просто читав / імпортував дані. При такому підході програма імпортує копію даних, наприклад файл зображення.

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


1
Ця відповідь містила правильну інформацію для моїх цілей. Умовно використання ACTION_PICK та EXTERNAL_CONTENT_URI на KitKat надали таку ж можливість отримувати метадані про зображення в галереї через ContentResolver, як це можливо на старих версіях, використовуючи просто ACTION_GET_CONTENT.
Колін М.

@voytez, Чи може цей URI повернутися через ваше повідомлення, перетворене на повний реальний шлях зображення?
Змія

Я вважаю, що це має працювати так само, як до KitKat, оскільки цей код змушує відкривати галерею зображень замість перегляду документів KK. Але якщо ви маєте намір використовувати його для створення зображення, тоді це рішення краще, оскільки перехід до реального шляху - зайвий не потрібний крок.
войтез

4
Працював і для мене, замість цього Intent.ACTION_GET_CONTENT. У будь-якому разі я тримав Intent.createChooser()обгортку на новому Intent, щоб користувач міг вибрати додаток для перегляду, і працював, як очікувалося. Чи може хтось побачити недоліки цього рішення?
lorenzo-s

1
Для тих, кому цікаво, цитата походить від developer.android.com/guide/topics/providers/…
snark

38

Як вже згадувалося в Commonsware, ви не повинні вважати, що потік, який ви отримуєте ContentResolver, перетворюється у файл.

Що ви дійсно повинні зробити, це відкрити InputStreamз ContentProvider, а потім створити з нього растрову карту. І він працює також на 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 . Але це вже інша тема.


Це не працює для мене з Nexus 4, що працює Kitkat, але це з Galaxy S3 під керуванням 4.1.2
Dan2552

@ Dan2552 яка частина не працює? Чи отримуєте ви якийсь виняток?
Michał K

Виявилося, я використовував неправильний виклик наміру до галереї. Я використовував той, який був для будь-якого типу документів, але з фільтром розширення файлів.
Dan2552

2
Яка гарно проста відповідь, дякую! Для інших, які відповідають цій відповіді, "cxt" посилається на поточний контекст і зазвичай буде "цей".
тисяча

1
Це, ймовірно, означає, що файлу просто немає. URI здається нормальним.
Michał K

33

Я вважаю, що вже опубліковані відповіді повинні змусити людей рухатись у правильному напрямку. Однак ось що я зробив, це мало сенс для застарілого коду, який я оновлював. Застарілий код використовував URI з галереї для зміни та збереження зображень.

До 4.4 (та диск Google) URI мали б такий вигляд: content: // media / external / images / media / 41

Як зазначено в питанні, вони частіше виглядають так: зміст: //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. Код не розміщується.


досить розумний. Мені теж потрібен був фактичний файл uri
більшийКрін

ти мій герой, це саме те, що мені було потрібно! відмінно працює і для файлів Google Диска
mishkin

Подумайте з коробки, правда? : D Цей код працює точно так, як я очікував.
WeirdElfB0y

2
опублікуйте код для copyAndClose, відповідь не повна
Bartek Pacia

24

Просто хотів це сказати ця відповідь геніальна, і я її довго використовую без проблем. Але деякий час тому я натрапив на проблему, що 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;
}
}

2
Працює ідеально! Дякую
mikemike396

8

Я поєднав кілька відповідей в одне робоче рішення, яке призводить до шляху файлу

Тип міми не має значення для прикладу.

            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.getPath () повертав урі з / зовнішньою, і це шлях не повертався. я додав це ще, якщо ("content" .equalsIgnoreCase (uri.getScheme ())) блок, і це працює добре зараз ... Чи можете ви пояснити, що він робить
FaisalAhmed

filePath стає нульовим .. In uri: content: //com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png
Bhavesh Moradiya

7

Питання

Як отримати фактичний шлях до файлу з 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

Дякую. Використання Uri та ContentResolver таким чином значно спростило моє додаток під час роботи з файлами.
jacksonakj

6

Ця бібліотека Android обробляє зміни корпусу в KitKat (включаючи старіші версії - 2.1+):
https://github.com/iPaulPro/aFileChooser

Використовуйте String path = FileUtils.getPath(context, uri)для перетворення повернутого Uri в рядок шляху, який можна використовувати у всіх версіях ОС. Детальніше про це дивіться тут: https://stackoverflow.com/a/20559175/860488


3

Для тих, хто все ще використовує код @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.


2

Це те, що я роблю:

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 (), і я не претендую на кредит. Я просто думав, що люди, які ще не вирішили цього питання, можуть цим скористатися.


2
Це не відповідь на новий додаток Галерея (суворо додаток "Провайдер медіа-документів") у KitKat.
nagoya0

Додаток, який запитуючий називає "Галерея" - це, мабуть, новий інструмент для вибору файлів на kitkat. FYI: addictivetips.com/android/…
nagoya0

У мене подібне, і я отримав IndexOutOfBound на Nexus 5X, Android 6 в цій лінії:cursor.getString(idx);
Осифікуйте

1

Будь ласка, намагайтеся уникати використання методу 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 захищений недійсним 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();
        }


    }

Де ви відображаєте зображення у другому випадку?
Quantum_VC

вибачте, що не вдалося додати цей рядок коду до іншого, якщо mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); Знову потрібно зателефонувати на функцію "відстежування зображень" відповідно до мого проекту
saranya

Який тип файлу є PhotoFile та mImgCropping?
Філіп БХ

1

Якщо когось цікавить, я зробив працюючу версію Котліна для 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)
}

Я просто хочу робочий код kotlin. Це для мене робота. спасибі
Харві Сірджа

1

Тут я спробував декілька відповідей, і, думаю, у мене є рішення, яке буде працювати щоразу і керувати дозволами.

Він заснований на розумному рішенні від 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
        }
    }
}

Це все, шановні; це працює для мене на всіх телефонах, які у мене є.


0

Це тотальний злом, але ось що я зробив ...

Отож, граючи з налаштуванням 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.
}

Я знаю. Соромно, але це спрацювало. Знову ж таки, це покладається на вас, використовуючи власний постачальника документів у вашому додатку для створення ідентифікатора документа.

(Крім того, є кращий спосіб побудувати шлях, який не передбачає, що "/" є роздільником шляху і т. Д. Але ви розумієте.)


Щоб відповісти на себе ще більш шаленою думкою - якщо ваш додаток уже обробляє file://наміри із зовнішнього засобу вибору файлів, ви також можете перевірити повноваження, як у наведеному вище прикладі, щоб переконатися, що це від вашого користувальницького постачальника, і якщо так, ви можете також використовуйте шлях, щоб "підробити" новий file://намір, використовуючи шлях, який ви видобули, а потім StartActivity()дозвольте вашій програмі підібрати його. Я знаю, жахливо.
жировик

0

Це добре працювало для мене:

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

забути dumpImageMetaData (урі); це зайве
Рафа

0

Надбудова відповіді Пола Берка я зіткнувся з багатьма проблемами вирішення шляху 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).

Будь ласка, підтвердьте, якщо вона не працює в інших місцях.


-1

Відповідь @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;
}

Я отримую java.lang.IllegalArgumentException: жоден із запитуваних стовпців не може бути наданий під час вибору зображення Google Doc
dirkoneill

@dirkoneill Я отримую той же виняток. Ви знайшли виправлення?
Генрі

-4

Відповідь на ваше запитання полягає в тому, що вам потрібно мати дозволи. Введіть наступний код у файл 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>`

Це працювало для мене ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.