在 Android 11 上编写许多文件

Posted

技术标签:

【中文标题】在 Android 11 上编写许多文件【英文标题】:Writing many files on Android 11 【发布时间】:2021-02-19 12:39:01 【问题描述】:

场景

我正在尝试创建许多文件,作为用户的一项功能。例如,我可能会编写一个应用程序,为他们过去几周听的每首歌曲创建一个文件(例如,带有歌词的文本文件)。我不能让用户为我生成的每个文件选择目录和文件名,这需要几个小时。用户应该可以从其他应用程序访问这些文档。

解决方案(不是真的)

android 11 中,存储访问框架似乎不再有用。我注意到最初有 2 个有趣的选项:

    创建一个新文件(创建启动活动,用户与之交互以仅保存一个文件),描述 here 授予对目录内容的访问权限(允许读取访问权限,但无法写入任何文档),描述为 here。 注意:如果您以 Android 10 为目标并请求 WRITE_EXTERNAL_STORAGE,则已经存在解决方案。不幸的是,此权限在 Android 11 上被拒绝。因此,我无法使用发布在 here 的解决方案。 使用存储访问框架(特别是ACTION_OPEN_DOCUMENT_TREE)访问目录以访问目录,但我无法将文件写入此目录。 (我得到FileNotFoundException,因为这不是一条普通路径,而是一条树路径。)

判决

我想在我的情况下,我需要走“管理存储设备上的所有文件”的路线,如here 所述,其中涉及使用ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION 启动一个额外的活动。我宁愿不请求读取整个驱动器的权限或让用户进入设置应用程序以授予权限。写入文件不需要任何权限...

您将如何为用户保存许多(非媒体)文件?

【问题讨论】:

使用数据库存储您的数据,然后允许用户在需要时将其内容“导出”到单独的文件? 用户真的需要这些文件吗?他是否与这些文件进行交互?创建这些文件的原因是什么? @MarcinOrlowski:如果我想将数据库导出为具有不同信息的 10 个文件怎么办?用户必须使用 SAF 10 次?我认为这是不合理的。 @LenaBru:保存“许多”文件的用例很多,例如,NLP 应用程序可能希望为每张图片创建一个文本文件,描述其内容。音乐应用程序可能想要编写单独的歌词。它对于调试某些类型的应用程序也很有用,我们希望看到文件以良好的格式保存。在 Android 11 上,即使使用 adb shell 或 Android Studio 文件资源管理器,您也无法查看应用特定目录(内部或外部)。用户可以将这些文件/文档用作文件、共享、组织在文件夹中、与其他应用程序一起打开。 不要忘记文件。 it seems like Storage Access Framework is not going to be useful. 是的。您只需让用户选择一个目录一次,就可以在应用的整个生命周期内完成。 【参考方案1】:

有 2 种方法,但首先,我将定义文件和目录名称,稍后将其保存在外部 (sdcard) 下载文件夹中

val outputFilename = "my_file"
val outputDirectory = "my_sub_directory" // The folder within the Downloads folder, because we use `DIRECTORY_DOWNLOADS`

有效但已弃用的方法:

尽管Environment.getExternalStoragePublicDirectory 已被弃用,但它仍然适用于以 Android 11 为目标并在 Android 11 上运行的应用。

file = File(Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_DOCUMENTS),
    "$outputDirectory/$outputFilename"
)
val outputStream = FileOutputStream(file)

MediaStore 方式:

或者,您可以使用 MediaStore 的 Files collection,建议使用 Gaurav Mall here。我不知道 MediaStore files 收藏...

我在 kotlin 中重写了它并修改了它以便在此处写入文件:

val resolver = context.contentResolver
val values = ContentValues()
// save to a folder
values.put(MediaStore.MediaColumns.DISPLAY_NAME, outputFilename)
values.put(MediaStore.MediaColumns.MIME_TYPE, "application/my-custom-type")
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + outputDirectory)
val uri = resolver.insert(MediaStore.Files.getContentUri("external"), values)
// You can use this outputStream to write whatever file you want:
val outputStream = resolver.openOutputStream(uri!!)
// Or alternatively call other methods of ContentResolver, e.g. .openFile

在以下两种情况下,Android 29 及更高版本都不需要WRITE_EXTERNAL_STORAGE

【讨论】:

消化了这个问题后,我认为另一个很好的例子(除了图片)是here。 MediaStore 解决方案非常棒,因为它易于使用、不会令人困惑并且确实有效。在 Android 10+ 上确认。谢谢。【参考方案2】:

在 Android 11 上,您可以使用标准 File 类在公共目录 Documents 中创建一个子目录,就像在下面 10 中一样。如果您知道怎么做,也可以在 10 中创建。然后在该子目录中创建文件。不用大惊小怪。

您也拥有对许多其他公共目录的写入权限。

您不需要为此访问所有文件。

用户可以使用设备上的官方文件应用程序访问您以这种方式创建的文件。

【讨论】:

您是否在针对 Android 11 的应用上执行过此操作? getExternalStoragePublicDirectory 已弃用,他们建议使用 MediaStore、Intent#ACTION_OPEN_DOCUMENTContext#getExternalFilesDir(String)。正如我在问题中解释的那样,这些选项对我来说都不可行。因此,我不使用have write access to a lot of other public directories too.(当然,我仍然拥有 MediaStore 的写入权限,但我正在编写文档,并且文档已声明我需要为此使用 SAF) 有关您的方法为何不起作用的更多解释,请阅读***.com/a/58575031/7365866 及其 cmets 我所说的所有内容都适用于 Android 11 api 30。试试吧,你会看到的。不推荐使用的功能并不意味着它们不起作用。 Context#getExternalFilesDir(String). None of these options are viable for me, as I explained in the question. ???您的帖子中没有任何关于 getExternalFilesDir 的内容,也没有任何关于使用它的解释。 I am trying to many small files, as a feature for the user. 外部文件目录仍然在应用程序内部,并且仅位于外部存储上。因此,它不会作为用户的功能有用。 (用户无法访问)

以上是关于在 Android 11 上编写许多文件的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 上编写 XML

将 Android 11 中的文件保存到外部存储(SDK 30)

如何使用 JavaScript 在服务器上编写文本文件? [关闭]

在 Windows 上编写一个提取 RPM 文件内容的程序

如何使用 Spark/Scala 在 HDFS 上编写/创建 zip 文件?

如何将使用 Pandas 在 Spark 集群上编写的文件移动到 HDFS?