Как получить путь к файлу с учетом URI дерева на Android 11?

Мы получаем URI дерева только тогда, когда пользователь выбирает папку, используя структуру доступа к хранилищу. В документах упоминается, что начиная с Android 11 мы можем использовать необработанные пути к файлам для доступа к файлам. Мне это нужно, чтобы я мог использовать существующую родную библиотеку. Но я не могу найти никакой документации по получению необработанного пути с учетом URI дерева. Как получить путь к файлу с учетом URI дерева на Android 11?

Обратите внимание, что все существующие ответы на stackoverflow являются хаками, и сейчас они даже не работают. Мне нужен официальный API для этого.


person Ganesh Bhambarkar    schedule 15.04.2020    source источник
comment
У вас нет доступа к файлам, которые вы получаете от Storage Access Framework. Эта ссылка предназначена для файлов мультимедиа (т. е. через MediaStore)   -  person ianhanniballake    schedule 15.04.2020


Ответы (4)


Для API ›= 29 параметры доступа к хранилищу изменились настолько радикально, что даже наличие пути к файлу не будет иметь большого значения из-за ограничений, требуемых разрешений и т. д.

Документация для API 30 (Andorid 11), в которой говорится, что можно использовать более старый File API, ссылается на тот факт, что такой API теперь можно использовать для доступа/работы с файлами, расположенными в папках общего хранилища, поэтому DCIM, Music и т. д. Вне этих мест файловый API не будет работать ни в Android 10.

Обратите внимание, что File API в Android 11 и всех будущих версиях работает намного медленнее со штрафом, потому что он был переписан в оболочку, поэтому больше не является API файловой системы. Внизу теперь делегирует все операции в MediaStore. Если важна производительность, то лучше использовать непосредственно MediaStore API.

Я бы рекомендовал работать только с Uris / файловыми дескрипторами через ContentResolver и отказаться от реальных путей к файлам. Любой обходной путь будет временным, и его будет сложнее поддерживать с каждой новой версией Android.

Следующий пример (немного хакерский) может помочь разрешить путь к файлу в API 29 и 30; не тестировалось ниже 29. Не является официальным, поэтому нет гарантии, что он будет работать во всех случаях использования, и я не рекомендую использовать его в производственных целях. В моих тестах разрешенный путь отлично работает с MediaScannerConnection, но может потребоваться корректировки для удовлетворения других требований.

@Nullable
public static File getFile(@NonNull final Context context, @NonNull final DocumentFile document)
{
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
    {
        return null;
    }

    try
    {
        final List<StorageVolume> volumeList = context
                .getSystemService(StorageManager.class)
                .getStorageVolumes();

        if ((volumeList == null) || volumeList.isEmpty())
        {
            return null;
        }

        // There must be a better way to get the document segment
        final String documentId      = DocumentsContract.getDocumentId(document.getUri());
        final String documentSegment = documentId.substring(documentId.lastIndexOf(':') + 1);

        for (final StorageVolume volume : volumeList)
        {
            final String volumePath;

            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q)
            {
                final Class<?> class_StorageVolume = Class.forName("android.os.storage.StorageVolume");

                @SuppressWarnings("JavaReflectionMemberAccess")
                @SuppressLint("DiscouragedPrivateApi")
                final Method method_getPath = class_StorageVolume.getDeclaredMethod("getPath");

                volumePath = (String)method_getPath.invoke(volume);
            }
            else
            {
                // API 30
                volumePath = volume.getDirectory().getPath();
            }

            final File storageFile = new File(volumePath + File.separator + documentSegment);

            // Should improve with other checks, because there is the
            // remote possibility that a copy could exist in a different
            // volume (SD-card) under a matching path structure and even
            // same file name, (maybe a user's backup in the SD-card).
            // Checking for the volume Uuid could be an option but
            // as per the documentation the Uuid can be empty.

            final boolean isTarget = (storageFile.exists())
                    && (storageFile.lastModified() == document.lastModified())
                    && (storageFile.length() == document.length());

            if (isTarget)
            {
                return storageFile;
            }
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

    return null;
}
person PerracoLabs    schedule 30.12.2020
comment
Что такое и как передать документ: DocumentFile? - person Noor Hossain; 19.02.2021
comment
Что такое файл документа? - person Md. Rejaul Karim; 11.03.2021

Как получить путь к файлу с учетом URI дерева на Android 11?

Извините, но это не вариант.

Мне это нужно, чтобы я мог использовать существующую нативную библиотеку

Пусть собственная библиотека работает с расположением файловой системы, которое ваше приложение может использовать непосредственно на Android 10+ и на более старых устройствах (например, getFilesDir(), getExternalFilesDir()).

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

Да, но:

  • Это не будет работать на Android 10, и
  • Storage Access Framework (например, ACTION_OPEN_DOCUMENT_TREE) не задействован

Вы можете использовать такие API, как getDirectory() на StorageVolume, для работы с томами хранения (внешними и съемными) через «доступ ко всем файлам» Android 11. См. эту запись в блоге. для большего.

person CommonsWare    schedule 15.04.2020
comment
в блоге говорится: Без разрешений ваше приложение может создавать файлы любого типа в каталогах Documents/ и Downloads/. После этого ваше приложение может манипулировать этими файлами — так как я могу получить путь к каталогу документов для Android 11? - person Noor Hossain; 17.02.2021
comment
@NoorHossain: я считаю, что использовал Environment.getExternalStoragePublicDirectory(), несмотря на его устаревание, для своего тестирования. - person CommonsWare; 17.02.2021
comment
Спасибо за быстрый ответ, я также обнаружил это, прежде чем я прокомментировал вас, но я боюсь, есть ли какие-либо побочные эффекты, которые создадут проблемы в будущем для использования кодов устаревания? - если нет, то я могу использовать, потому что одно из моих опубликованных приложений у меня здесь. В этом пункте что делать? - person Noor Hossain; 17.02.2021
comment
Environment.getExternalStoragePublicDirectory() --- в Android 11, когда приложение удаляется и устанавливается снова, приложение не может открыть свой старый файл, созданный самостоятельно. появилось сообщение о недоступности. Есть ли способ, чтобы он знал, что старые файлы создаются самостоятельно даже после удаления и повторной установки (в 11)? - person Noor Hossain; 19.02.2021
comment
@NoorHossain: Есть ли способ, чтобы он знал, что старые файлы создаются самостоятельно даже после удаления и повторной установки (в 11)? -- нет простите. - person CommonsWare; 19.02.2021

В моем приложении, скомпилированном с помощью androidSdkTarget:29, я могу поделиться сгенерированным PDF-файлом на Android 10 и 11, сохранив файл по этому пути:

val workSpaceDir = if (isExternalStorageWritable) {
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){
        File(Globals.application.getExternalFilesDir(null))
    } else {
        File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).path + "/" + Globals.application.getString(R.string.app_name))
    }
} else {
    File(Globals.application.filesDir)
}

На данный момент Environment.getExternalStoragePublicDirectory он устарел, но все еще работает. Это единственный способ, который я нашел.

У меня есть следующие разрешения на мой манифест Android:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Вот как я его использую:

val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(
    Uri.fromFile(workSpaceDir + filename),
    Constants.ContentType.ApplicationPdf
)

intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
try {
    Globals.currentActivity.startActivity(intent)
} catch (e: ActivityNotFoundException) {
    Toast.makeText(
        Globals.currentActivity,
        "No PDF viewer installed.",
        Toast.LENGTH_LONG
    ).show()
}
person jedi    schedule 11.03.2021

С помощью SimpleStorage:

val file = DocumentFileCompat.fromUri(context, treeUri);
file.absolutePath // => e.g. /storage/emulated/0/MyFolder/MyFile.mp4
file.basePath // => e.g. MyFolder/MyFile.mp4
person Anggrayudi H    schedule 18.12.2020