Получить реальный путь от Uri - DATA устарели в Android Q

avatar
ronginat
18 июля 2019 в 11:33
11102
2
17

Я успешно реализую метод получения реального пути к изображению из галереи с помощью Uri, возвращенного из ACTION_PICK. Вот пример:

// getRealPathFromURI(intent.getData());

private String getRealPathFromURI(Uri contentURI) {
    String result;
    Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
    if (cursor == null) { // Source is Dropbox or other similar local file path
        result = contentURI.getPath();
    } else { 
        cursor.moveToFirst(); 
        int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 
        result = cursor.getString(idx);
        cursor.close();
    }
    return result;
}

Как и этот ответ.

Недавно обновлен атрибут compileSdkVersion до 29 и, очевидно, атрибут DATA, используемый всеми, устарел<38976665001933>5091933> В официальных документах они рекомендуют вместо этого использовать FileDescriptor, проблема в том, что я точно не знаю, как это сделать.
Единственное, что я нашел, это этот вопрос. Однако не нашел там правильного ответа.

Пожалуйста, помогите мне преодолеть эту проблему устаревания с помощью решения, используя предложенный способ или любой другой способ.

Спасибо.


Обновление:

Выполнил ответ @CommonsWare и скопировал возвращенный Uri (изображения, которое выбрал пользователь) в локальный каталог, используя context.getContentResolver.openInputStream(Uri).

Даже пытался получить файл с Google Диска — сработало. Единственная проблема заключалась в том, что это заняло много времени (около 20 секунд для файла размером 5 МБ).

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

У меня больше нет внешних путей!

Источник
Stanley Wintergreen
18 июля 2019 в 12:05
1

Попробуйте просто скопировать данные, имеющие объект uri. Как сказал @CommonsWare FileOutputStream(yourFullPath).use { oStream -> contentResolver.openInputStream(uri)?.use { iStream -> oStream.write(iStream.readBytes()) } }

Ответы (2)

avatar
CommonsWare
18 июля 2019 в 11:56
9

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

Этот код может работать не для всех изображений. Не требуется, чтобы DATA указывал на путь к файловой системе, к которому у вас есть доступ.

Как и этот ответ.

FWIW, это был мой ответ на этот вопрос.

Единственное, что я нашел, это этот вопрос. Однако не нашел там подходящего ответа.

Этот метод не был особенно хорош и больше не будет работать, так как Android заблокирован /proc.

В официальной документации вместо этого рекомендуется использовать FileDescriptor, проблема в том, что я не знаю, как именно.

Более общая концепция заключается в том, что вы используете ContentResolver для работы с Uri, независимо от того, получаете ли вы InputStream (openInputStream()), OutputStream (<openInputStream()5>) или 52 <openInputStream()1>. Потребляйте контент, используя эти вещи. Если у вас есть API, которому абсолютно необходим файл, скопируйте содержимое (например, из InputStream) в файл, которым вы управляете (например, в getCacheDir()).

В качестве бонуса теперь ваш код также может использовать Storage Access Framework (например, ACTION_OPEN_DOCUMENT) и Интернет (например, OkHttp), если и когда это будет полезно.

Vince VD
31 марта 2020 в 19:30
0

Для моего приложения мне нужен путь для воспроизведения аудиофайла, который я получил из MediaStore.Audio.Media.DATA, но столбца MediaStore.Audio.Media._ID недостаточно для получения uri? Затем я использую ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id), поэтому он возвращает uri контента, который я затем могу использовать в mediaPlayer.setDataSource? Но по какой-то причине он показывает, что не может разрешить метод setDataSource(android.net.Uri)

CommonsWare
31 марта 2020 в 19:33
1

@VinceVD: Это setDataSource(Context, Uri): developer.android.com/reference/android/media/…

Vince VD
7 апреля 2020 в 01:51
0

У меня есть еще один вопрос по этому поводу, я только что нашел в вашем блоге, что «MediaStore Uri может индексировать носитель на съемном носителе и не имеет прямого доступа к этому файловой системе». Значит ли это, что когда я играю песни с моей SD-карты, это не работает? Я только что попробовал его с медиафайлами на своей SD-карте, и он отлично работает, используя URI контента, который я получаю с помощью song.setData(ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cursor.getLong(cursor.getColumnIndexOrThrow(SONG_ID))) ); (источник: commonsware.com/blog/2016/03/15/how-consume-content-uri.html)

CommonsWare
7 апреля 2020 в 10:46
1

@VinceVD: «Я только что нашел в вашем блоге» - пожалуйста, используйте копирование и вставку для цитат, так как вы не поняли это правильно. Это «Плюс, даже если вы получите Uri MediaStore, который может индексировать носитель на съемном носителе, и у вас нет прямого доступа к этому файловой системе». «Значит ли это, что когда я играю песни с моей SD-карты, это не работает?» -- MediaStore имеет прямой доступ файловой системы к носителю. В вашем приложении нет. Если вы используете MediaStore Uri, то MediaStore — это код, обращающийся к файловой системе, а не к вашему приложению.

Vince VD
7 апреля 2020 в 13:30
0

Итак, если MediaStore Uri имеет прямой доступ к файловой системе, значит ли это, что я могу просто удалить содержимое с помощью contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStore.Audio.Media._ID + "=?", {Long.toString(songId)});?

CommonsWare
7 апреля 2020 в 13:30
0

@VinceVD: К сожалению, нет. Это просто удаляет запись из таблиц MediaStore. Я не знаю, как с помощью MediaStore удалить контент.

Vince VD
7 апреля 2020 в 17:26
0

Значит ли это, что просто невозможно удалить этот файл только с uri содержимого? До Api 29 я полагался на столбец DATA, чтобы получить путь, который теперь устарел, но я также нашел много комментариев о том, что просто плохо полагаться на этот столбец, возвращающий путь, затем я просто использовал file.delete для внутренних файлов и documentFile .delete () (SAF) для внешнего, которые оба работали, но как я могу добиться того же, не используя столбец DATA из MediaStore?

CommonsWare
7 апреля 2020 в 18:05
0

@VinceVD: «Значит ли это, что просто невозможно удалить этот файл только с uri контента?» -- как я уже писал, я понятия не имею, как вы это сделаете.

avatar
dafNou
5 июня 2020 в 13:43
19

Этот вопрос возник и у меня неделю назад.

Мое решение состояло в том, чтобы создать InputStream из URI, а затем на его основе создать OutputStream, скопировав содержимое входного потока.

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

.
@Nullable
public static String createCopyAndReturnRealPath(
       @NonNull Context context, @NonNull Uri uri) {
    final ContentResolver contentResolver = context.getContentResolver();
    if (contentResolver == null)
        return null;

    // Create file path inside app's data dir
    String filePath = context.getApplicationInfo().dataDir + File.separator
            + System.currentTimeMillis();

    File file = new File(filePath);
    try {
        InputStream inputStream = contentResolver.openInputStream(uri);
        if (inputStream == null)
            return null;

        OutputStream outputStream = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int len;
        while ((len = inputStream.read(buf)) > 0)
            outputStream.write(buf, 0, len);

        outputStream.close();
        inputStream.close();
    } catch (IOException ignore) {
        return null;
    }

    return file.getAbsolutePath();
}
Sanush
13 ноября 2020 в 05:50
0

вообще супер решение

Shashi
23 марта 2021 в 13:26
0

Спас мой день... Он работает с выбором всех файлов.. Спасибо...