11

由于Android在文件访问方面不同主要版本之间非常不一致,我感到有些失落。我尝试尽可能简单地描述问题:

My Company 使用商业原生 DRM 来保护我们提供的其他原生库。我们有一个 Licensing App,它调用了一些 Voodoo,最终得到了 /sdcard/companyname/LicenseContainer/ 中的 Licensing 文件。其他受保护的应用程序在本机代码中查看此目录,检查用户是否拥有有效的许可证。

然而,Android 10 更新完全使此工作流程无效,因为它仅提供scoped storage access. 我们可以使用Storage Manager授予访问权限来解决问题,不幸的是,现在也已弃用。

所以我现在的问题是:一个应用程序如何将文件保存到 /sdcard/FOLDER 上的某个位置

  1. 删除应用时未删除
  2. 其他应用程序可以在本机代码中访问

我对所有可能的解决方案(SAF、FileProvider 等)有点不知所措,它们经常调用一个应用程序向另一个应用程序授予权限。但是这些文件应该可以在没有安装的第一个应用程序的情况下访问。

我知道必须有一个解决方案,因为最近的 FileManagers(即Files by Google)可以访问整个/sdcard/目录。

什么是最简单,面向未来的路线,无需调用“黑客”之类的android:requestLegacyExternalStorage="true"

4

2 回答 2

5

您可以要求用户授予您对任何文件或目录的访问权限,包括内部存储或外部 SD 卡的根目录。您可以为您的应用永久设置此访问权限,之后能够使用 Scoped Storage API 在任何地方读取/写入文件,直到应用被卸载或重置。

然后,如果您需要在本机 C/C++ 代码中读取或写入文件,您可以获取文件的 Linux 文件描述符(int 编号)并将其传递给本机代码以与 fdopen() 调用一起使用。

这是一个 Java 代码片段,用于从单个文件 Uri 中获取文件描述符(字符串形式类似于 content://...)

    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r"); // gets FileNotFoundException here, if file we used to have was deleted
    int fd = parcelFileDescriptor.getFd(); // detachFd() if we want to close in native code

如果您有本机库的源代码,或者可以使用 C FILE* 调用它们 - 它会正常工作。唯一的问题是当您没有源代码并且他们需要文件路径/名称时。* 更新 *:仍然可以使用路径/文件名字符串传递给需要文件名的 C/C++ 函数。简单地代替“真实路径/文件名”,为符号链接创建一个名称,如下所示:

// fd is file descriptor obtained in Java code above
char fileName[32];
sprintf(fileName, "/proc/self/fd/%d", fd);
// The above fileName can be passed to C/C++ functions that expect a file name.
// They can read or write to it, depending on permissions to fd given in Java,
// but I guess C/C++ code can not create a new file. Someone correct me,
// if I'm mistaken here.

但是,此时我不确定当您以这种方式在应用程序“沙盒”之外的目录中创建文件时,如果系统在卸载后也会删除该文件......需要编写一个快速测试Android 10 找出来,然后我们仍然不知道谷歌是否会在未来改变这种行为。

于 2019-10-09T12:41:44.003 回答
0

如果您想将文件保存在共享存储中(用户和其他应用程序可以访问它),您需要使用

  • 对于媒体文件(图像、视频、音频、下载),请使用MediaStore
  • 对于文档和其他文件,使用存储访问框架(这只是一个系统文件选择器)

例如,您可以使用以下代码段使用 Storage Access Framework 保存 pdf 文件

const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "invoice.pdf")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}

在用户选择了一个目录后,我们仍然需要在 onActivityResult 方法中处理结果 Uri。

override fun onActivityResult(
        requestCode: Int, resultCode: Int, resultData: Intent?) {
    if (requestCode == CREATE_FILE && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for directory that
        // the user selected.
        resultData?.data?.also { uri ->
            // save your data using the `uri` path
        }
    }
}

您可以在以下博客文章中更详细地了解这一点

https://androidexplained.github.io/android/android11/scoped-storage/2020/09/29/file-saving-android-11.html

于 2020-10-27T18:48:50.063 回答