Доступ к пути к файлу в Android 11 (R)

В соответствии с документами, доступ к пути к файлу предоставляется в Android R:

Начиная с Android 11, приложения с разрешением READ_EXTERNAL_STORAGE могут читать медиафайлы устройства, используя прямые пути к файлам и собственные библиотеки. Эта новая возможность позволяет вашему приложению более плавно работать со сторонними медиа-библиотеками.

Проблема в том, что я не могу получить путь к файлу из MediaStore, так как же нам читать путь к файлу, к которому мы не можем получить доступ / получить? Есть ли способ, о котором я не знаю, чтобы мы могли получить путь к файлу изMediaStore?


Кроме того, в документах говорится следующее:

Доступ ко всем файлам

Некоторые приложения имеют основной вариант использования, требующий широкого доступа к файлам, например управление файлами или операции резервного копирования и восстановления. Они могут получить доступ ко всем файлам, выполнив следующие действия:

  1. Объявите разрешение MANAGE_EXTERNAL_STORAGE.
  2. Направляйте пользователей на страницу настроек системы, где они могут включить параметр Разрешить доступ для управления всеми файлами для вашего приложения.

Это разрешение дает следующие права:

  • Доступ для чтения и записи для всех файлов в общем хранилище.
  • Доступ к содержимому таблицы 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)
    }

}
Другие вопросы по тегам