【问题标题】:Writing files to publicly accessible documents folder in Android Q - Scoped Storage将文件写入 Android Q 中可公开访问的文档文件夹 - Scoped Storage
【发布时间】:2020-05-27 11:27:13
【问题描述】:

背景

迁移到 Android Q 后,我再也找不到合适的方法来获得对文档文件夹的写入权限 (/storage/emulated/0/Documents)

在任何人提到有关范围存储的许多其他问题之前,我已经阅读了其中的许多问题,并且从我目前所看到的情况来看,所有解决方案都使用特定于应用程序的目录或仅访问媒体目录(不是文档文件夹访问)。

根据我对 Android Q 的理解,我可以选择:

  • 使用只有我的应用(或我授予权限的应用)才能访问的应用特定目录
  • 允许用户选择他们想要存储文件的位置(我认为这可以是公共位置)ACTION_OPEN_DOCUMENT_TREE

问题

我开发的应用程序用于对受试者进行测试,测试的数据自动存储在可公开访问的 Documents 文件夹中,有点像:/storage/emulated/0/Documents/myAppName/subjectName/testData-todaysDate.pdf

当用户想要访问测试数据时,他们将智能手机插入计算机并导航到文档文件夹,剩下的就很明显了。出于这个原因,我必须使用可公开访问的存储。还可以想象他们想通过另一个应用程序打开手机上的测试数据,同样的交易!

我正在寻找的解决方案

所以我正在寻找的解决方案需要能够做到以下几点:

  • 不会多次请求许可(一次就可以)
  • 自动保存测试数据,无需用户提示(因为将进行数百次测试)
  • 保存到公共文档文件夹,例如/storage/emulated/0/Documents/
  • 不涉及用户选择目录所以 NOT ACTION_OPEN_DOCUMENT_TREE
  • 理想情况下,数据是持久的,因此卸载应用不会导致数据丢失

我知道 Android Q 中的这些变化是为了让用户更多地“了解”应用如何访问他们的数据,但是一旦他们了解您的应用如何使用您的数据,就不会有问题了。

【问题讨论】:

  • AFAIK,没有满足您要求的解决方案。如果你从Documents/切换到Downloads/,你可以use MediaStore.Downloads。 “但是一旦他们了解您的应用程序如何使用您的数据,就不应该有问题”——根据这个论点,您不应该对ACTION_OPEN_DOCUMENT_TREE 有问题。用户只需要处理一次,之后您可以将任意数量的文档写入该树中。
  • 有了ACTION_OPEN_DOCUMENT_TREE,他们可以在设置应用程序时选择一个公共位置,然后我们就可以永远重复使用它吗?那可能是最好的解决方案,但仍然很危险,因为用户可以选择一个不好的位置,但总比没有好。
  • When the user wants to access the test data they plug their smartphone into a computer and navigate to the documents folder。 Android Q 设备仍然可以做到这一点吗?请报告,因为我没有这样的设备。
  • 您可以让 MediaStore 将您的文件写入 /storage/emulated/0/Documents 和 /storage/emulated/0/Download 和 /storage/emulated/0/Pictures 和 /storage/emulated/0 /DCIM 无需任何许可。
  • “例如,使用ACTION_OPEN_DOCUMENT_TREE,他们可以在设置应用程序时选择一个公共位置,然后我们就可以永远重复使用它吗?” -- 是的。

标签: android mediastore android-10.0 scoped-storage


【解决方案1】:

尝试将此行放入您的 android Q 清单文件中:

    android:requestLegacyExternalStorage="true"

在应用程序标签中。

【讨论】:

  • 这是一个 hack,不适用于 Q 之后的版本。
【解决方案2】:

仅适用于 Android Q。

String collection = "content://media/1c11-2404/file"; // One can even write to micro SD card
String relative_path = "Documents/MyFolder";

Uri collectionUri = Uri.parse(collection);

ContentValues contentValues = new ContentValues();

contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
contentValues.put(MediaStore.MediaColumns.SIZE, filesize);
contentValues.put(MediaStore.MediaColumns.DATE_MODIFIED, modified );
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relative_path);
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1)

Uri fileUri = context.getContentResolver().insert(collectionUri, contentValues);

拥有一个 uri 可以打开一个输出流来写入文件的内容。

【讨论】:

  • @blackapps:好的,我可以确认这是可行的。对于外部存储,您可以使用content://media/external/file(也可以通过MediaStore.Files.getContentUri("external") 获得)。对于可移动存储,您需要存储 ID(代码 sn-p 中的1c11-2404)。我怀疑这是否可行,所以如果在未来版本的 Android 中这种技术存在问题,我不会感到震惊。但是,这是一个很好的发现——感谢分享!
  • 好吧,我有点困惑,在我使用FileOutputStream 之前,就像outputStream = new FileOutputStream(filePath, false); 这样我会简单地outputStream.write(dataToWrite.getBytes()); 然后.flush().close() 这仅适用于File当然,所以我不确定您如何按照您的建议打开带有 uri 的输出流?
  • OutputStream is = getContentResolver().openOutputStream(fileUri);.
  • 在@blackapps 和@CommonsWare 之间还可以,我能够拼凑到足以让某些东西发挥作用。这绝对是绝妙的,但正如其他人所说,我不确定这是怎么回事。另外值得注意的是,android:requestLegacyExternalStorage="true" 将持续存在(即使被删除),除非您清除应用程序上的数据/缓存。
  • 关于SD卡的UUID,我发现它应该是小写的,与StorageVolumegetUuid()返回的相反
猜你喜欢
  • 1970-01-01
  • 2021-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多