Ошибка при записи файлов с Android Storage Access на Lollipop

Задний план

У меня есть несколько приложений, которые сильно используют SD-карту для синхронизации файлов. Сломанный внешний доступ к SD-карте на Kitkat по-прежнему является большой проблемой, но я пытаюсь разрешить это с помощью нового API, доступного на Lollipop для пользователей, у которых это есть.

Я успешно запрашиваю и сохраняю разрешение на SD-карту, и я могу перечислить файлы в корневом Uri, возвращенные из действия разрешения разрешения.

Узнайте больше о том, как это делается здесь: как использовать-new-sd-card-access-api-present-for-lollipop

Затем пользователь может выбрать любую папку / подпапку для синхронизации, и я сохраняю документ папки Uri в виде строки в базе данных.

проблема

Позже, потенциально после перезапуска приложения, синхронизация файлов может начаться. Затем я пытаюсь перечислить файлы в подпапке (помните, что у меня есть правильное разрешение, и настойчивость этого работает, а также предоставляет мне доступ ко всем детям).

Затем я создаю новый экземпляр DocumentFile из сохраненной строки и пытаюсь перечислить файлы:

DocumentFile dir = DocumentFile.fromTreeUri(ctx, Uri.parse(storedUri)); dir.listFiles(); 

Проблема заключается в том, что listFiles всегда возвращает дочерние элементы в корневой папке Uri и никогда не является дочерью фактического Uri, я предоставляю метод DocumentFile.fromTreeUri.

Я изучил исходный код DocumentFile, и кажется, что там есть ошибка, в частности, я не вижу необходимости в дальнейшем модифицировать Uri:

 public static DocumentFile fromTreeUri(Context context, Uri treeUri) { final int version = Build.VERSION.SDK_INT; if (version >= 21) { return new TreeDocumentFile(null, context, DocumentsContractApi21.prepareTreeUri(treeUri)); } else { return null; } 

Если мы посмотрим на источник DocumentsContractApi21.prepareTreeUri, мы увидим, что перестраивает Uri:

  public static Uri prepareTreeUri(Uri treeUri) { return DocumentsContract.buildDocumentUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri)); } 

И методы, которые он называет:

  public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) { return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) .authority(treeUri.getAuthority()).appendPath(PATH_TREE) .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT) .appendPath(documentId).build(); } public static String getTreeDocumentId(Uri documentUri) { final List<String> paths = documentUri.getPathSegments(); if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) { return paths.get(1); } throw new IllegalArgumentException("Invalid URI: " + documentUri); } 

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

Решение

Исправьте метод fromTreeUri, чтобы не всегда использовать корневой документ Uri id.

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

  Class<?> c = Class.forName("android.support.v4.provider.TreeDocumentFile"); Constructor<?> constructor = c.getDeclaredConstructor(DocumentFile.class, Context.class, Uri.class); constructor.setAccessible(true); DocumenFile dir = (DocumentFile) constructor.newInstance(null, mCtx, treeUri); dir.listFiles(); 

Solutions Collecting From Web of "Ошибка при записи файлов с Android Storage Access на Lollipop"