Доступ к пути к файлу в Android 11 (R)
В соответствии с документами, доступ к пути к файлу предоставляется в Android R:
Начиная с Android 11, приложения с разрешением READ_EXTERNAL_STORAGE могут читать медиафайлы устройства, используя прямые пути к файлам и собственные библиотеки. Эта новая возможность позволяет вашему приложению более плавно работать со сторонними медиа-библиотеками.
Проблема в том, что я не могу получить путь к файлу из MediaStore
, так как же нам читать путь к файлу, к которому мы не можем получить доступ / получить? Есть ли способ, о котором я не знаю, чтобы мы могли получить путь к файлу изMediaStore
?
Кроме того, в документах говорится следующее:
Доступ ко всем файлам
Некоторые приложения имеют основной вариант использования, требующий широкого доступа к файлам, например управление файлами или операции резервного копирования и восстановления. Они могут получить доступ ко всем файлам, выполнив следующие действия:
- Объявите разрешение MANAGE_EXTERNAL_STORAGE.
- Направляйте пользователей на страницу настроек системы, где они могут включить параметр Разрешить доступ для управления всеми файлами для вашего приложения.
Это разрешение дает следующие права:
- Доступ для чтения и записи для всех файлов в общем хранилище.
- Доступ к содержимому таблицы MediaStore.Files.
Но мне не нужен доступ ко всем файлам, я хочу, чтобы пользователь выбрал видео из MediaStore
и передайте путь к файлу FFmpeg
(требуется путь к файлу). Я знаю, что больше не могу использовать_data
столбец для получения пути к файлу.
Пожалуйста, обратите внимание:
- Я знаю
Uri
возвращается изMediaStore
и не указывает на файл. - Я знаю, что могу скопировать файл в каталог своего приложения и передать его в
FFmpeg
, но я мог сделать это до Android R. - Я не могу пройти
FileDescriptor
кFFmpeg
и я не могу использовать/proc/self/fd/
(Я получил/proc/7828/fd/70: Permission denied
при выборе файла с SD-карты) обратите внимание на эту проблему.
Так что же мне делать, я что-то упускаю? Что имелось в виду сcan read a device's media files using direct file paths and native libraries
?
3 ответа
Задав вопрос по программе Issueetracker, я пришел к следующим выводам:
- В Android R
File
ограничения, которые были добавлены в Android Q, сняты. Итак, мы можем снова получить доступFile
объекты. Если вы ориентируетесь на Android 10 > и хотите получить доступ / использовать пути к файлам, вам нужно будет добавить / сохранить в своем манифесте следующее:
android:requestLegacyExternalStorage="true"
Это необходимо для того, чтобы пути к файлам работали на Android 10(Q). В Android R этот атрибут игнорируется.
Не используйте столбец DATA для вставки или обновления в Media Store, используйте
DISPLAY_NAME
а такжеRELATIVE_PATH
, вот пример:ContentValues valuesvideos; valuesvideos = new ContentValues(); valuesvideos.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/" + "YourFolder"); valuesvideos.put(MediaStore.Video.Media.TITLE, "SomeName"); valuesvideos.put(MediaStore.Video.Media.DISPLAY_NAME, "SomeName"); valuesvideos.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); valuesvideos.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000); valuesvideos.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis()); valuesvideos.put(MediaStore.Video.Media.IS_PENDING, 1); ContentResolver resolver = getContentResolver(); Uri collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); Uri uriSavedVideo = resolver.insert(collection, valuesvideos);
Вы больше не можете использовать
ACTION_OPEN_DOCUMENT_TREE
илиACTION_OPEN_DOCUMENT
намеренное действие, чтобы запросить у пользователя выбор отдельных файлов изAndroid/data/
,Android/obb/
и все подкаталоги.- Рекомендуется использовать только
File
объекты, когда вам нужно выполнить "поиск", например, при использованииFFmpeg
, например. - Вы можете использовать столбец данных только для доступа к файлам на диске. Вы должны соответствующим образом обрабатывать исключения ввода-вывода.
Если вы хотите получить доступ к File
или хотите путь к файлу из Uri
что было возвращено из MediaStore
, Я создал библиотеку, которая обрабатывает все возможные исключения. Сюда входят все файлы на диске, внутреннем и съемном диске. При выбореFile
из Dropbox, например, File
будет скопирован в каталог ваших приложений, к которому у вас есть полный доступ, затем будет возвращен путь к скопированному файлу.
Если вы ориентируетесь на API Android 11 , вы не можете напрямую получить доступ к путям файлов, поскольку в API 30(Android R) существует множество ограничений. Поскольку в Android 10(API 29) был представлен API хранилища с ограниченным доступом, хранилище теперь разделено на хранилище с ограниченным объемом (частное хранилище) и общее хранилище (общедоступное хранилище). Хранилище с заданной областью действия - это такой вид, при котором вы можете иметь доступ только к файлам, созданным в каталоге хранилища с заданной областью (например, / Android / data / или / Android / media/). Вы не можете получить доступ к файлам из общего хранилища (например, внутреннего хранилища / внешнего хранилища SD-карты и т. Д.)
Общее хранилище снова делится на мультимедийную и загрузочную коллекцию. В коллекции мультимедиа хранятся файлы изображений, аудио и видео. Коллекция загрузок позаботится о немедиа-файлах.
Чтобы узнать больше об ограниченном хранилище и общем хранилище, этой перейдите поссылке - Ограниченное хранилище в Android 10 и Android 11 .
Если вы имеете дело с мультимедийными файлами (например, изображениями, видео, аудио), вы можете получить путь к файлу с помощью API хранилища мультимедиа , поддерживающего API 30(Android 11). и Если вы имеете дело с файлами, не относящимися к мультимедиа (например, с документами и другими файлами), вы можете получить путь к файлу, используя Uri файла.
Примечание:- Если вы используете файл или классы Uri util (такие как RealPathUtil, FilePathUtils и т. Д.), Чтобы получить путь к файлу, здесь вы можете получить желаемый путь к файлу, но вы не можете прочитать этот файл, так как он вызовет исключение Доступ для чтения (как отказано в разрешении) в Android 11, так как вы не можете читать файлы, созданные другим приложением.
Итак, чтобы реализовать этот сценарий получения пути к файлу в Android 11(API 30), рекомендуется скопировать файл в каталог кеша вашего приложения с помощью File Uri и получить путь доступа к файлу из каталога кеша.
В моем сценарии я использовал оба API для доступа к файлам в Android 11. Чтобы получить путь к файлу мультимедийных файлов (например, изображений, видео, аудио), я использовал API хранилища мультимедиа (см. Эту ссылку: Media Пример Store API - доступ к медиафайлам из общего хранилища ), а чтобы получить путь к файлам, не относящимся к медиафайлам (то есть документам и другим файлам), я использовал fileDescriptor.
Пример дескриптора файла: я создал системное диалоговое окно выбора файла для выбора файла.
private fun openDocumentAction() {
val mimetypes = arrayOf(
"application/*", //"audio/*",
"font/*", //"image/*",
"message/*",
"model/*",
"multipart/*",
"text/*"
)
// you can customize the mime types as per your choice.
// Choose a directory using the system's file picker.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
//type = "application/pdf" //only pdf files
type = "*/*"
putExtra(Intent.EXTRA_MIME_TYPES, mimetypes)
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when it loads.
//putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
}
startActivityForResult(intent, RC_SAF_NON_MEDIA)
}
И обработал результат средства выбора файлов в методе действия onActivityResult . Получите URI файла здесь.
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
RC_SAF_NON_MEDIA -> {
//document selection by SAF(Storage Access Framework) for Android 11
if (resultCode == RESULT_OK) {
// The result data contains a URI for the document or directory that
// the user selected.
data?.data?.also { uri ->
//Permission needed if you want to retain access even after reboot
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Perform operations on the document using its URI.
val path = makeFileCopyInCacheDir(uri)
Log.e(localClassName, "onActivityResult: path ${path.toString()} ")
}
}
}
}
}
Передайте URI файла указанному ниже методу, чтобы получить путь к файлу. Этот метод создаст файловый объект в каталоге кеша вашего приложения, и из этого места вы можете легко получить доступ для чтения к этому файлу.
private fun makeFileCopyInCacheDir(contentUri :Uri) : String? {
try {
val filePathColumn = arrayOf(
//Base File
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.TITLE,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.Files.FileColumns.DISPLAY_NAME,
//Normal File
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.DISPLAY_NAME
)
//val contentUri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.provider", File(mediaUrl))
val returnCursor = contentUri.let { contentResolver.query(it, filePathColumn, null, null, null) }
if (returnCursor!=null) {
returnCursor.moveToFirst()
val nameIndex = returnCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
val name = returnCursor.getString(nameIndex)
val file = File(cacheDir, name)
val inputStream = contentResolver.openInputStream(contentUri)
val outputStream = FileOutputStream(file)
var read = 0
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable = inputStream!!.available()
//int bufferSize = 1024;
val bufferSize = Math.min(bytesAvailable, maxBufferSize)
val buffers = ByteArray(bufferSize)
while (inputStream.read(buffers).also { read = it } != -1) {
outputStream.write(buffers, 0, read)
}
inputStream.close()
outputStream.close()
Log.e("File Path", "Path " + file.path)
Log.e("File Size", "Size " + file.length())
return file.absolutePath
}
} catch (ex: Exception) {
Log.e("Exception", ex.message!!)
}
return contentUri.let { UriPathUtils().getRealPathFromURI(this, it).toString() }
}
Примечание. - Вы можете использовать этот метод, чтобы получить путь к файлу как для файлов мультимедиа (изображения, видео, аудио), так и для файлов, не являющихся мультимедийными (документы и другие файлы). Просто нужно передать файл Uri.
Ваше здоровье...!
Для получения пути я копирую файл с fileDescriptor на новый путь, и я использую этот путь.
Поиск имени файла:
private static String copyFileAndGetPath(Context context, Uri realUri, String id) {
final String selection = "_id=?";
final String[] selectionArgs = new String[]{id};
String path = null;
Cursor cursor = null;
try {
final String[] projection = {"_data", "_display_name"};
cursor = context.getContentResolver().query(realUri, projection, selection, selectionArgs,
null);
cursor.moveToFirst();
final String fileName = cursor.getString(cursor.getColumnIndexOrThrow("_display_name"));
File file = new File(context.getCacheDir(), fileName);
FileUtils.saveAnswerFileFromUri(realUri, file, context);
path = file.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return path;
}
Копировать с файловым дескриптором:
fun saveAnswerFileFromUri(uri: Uri, destFile: File?, context: Context) {
try {
val pfd: ParcelFileDescriptor =
context.contentResolver.openFileDescriptor(uri, "r")!!
if (pfd != null) {
val fd: FileDescriptor = pfd.getFileDescriptor()
val fileInputStream: InputStream = FileInputStream(fd)
val fileOutputStream: OutputStream = FileOutputStream(destFile)
val buffer = ByteArray(1024)
var length: Int
while (fileInputStream.read(buffer).also { length = it } > 0) {
fileOutputStream.write(buffer, 0, length)
}
fileOutputStream.flush()
fileInputStream.close()
fileOutputStream.close()
pfd.close()
}
} catch (e: IOException) {
Timber.w(e)
}
}