如何解决Android 10的存储混乱



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

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

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

所以我的问题是现在: 一个应用程序如何将文件保存到/sdcard/文件夹中的位置,这些位置是

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

我对所有可能的解决方案(SAF、FileProvider 等(有点不知所措,这些解决方案经常调用一个应用程序向另一个应用程序授予权限。但是这些文件应该可以访问,而无需安装第一个应用程序将其放在那里。

我知道必须有一个解决方案,就像最近的文件管理器(即Files by Google( 访问整个/sdcard/目录。

什么是最简单、面向未来的路线,无需像android:requestLegacyExternalStorage="true"那样调用"黑客"

您可以要求用户授予您访问任何文件或目录的权限,包括内部存储或外部SD卡的根目录。你可以使应用的此访问权限永久化,之后能够使用作用域存储 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上编写一个快速测试才能找到答案,然后我们仍然不知道Google将来是否会改变这种行为。

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

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

例如,可以使用以下代码片段通过存储访问框架保存 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

最新更新