如何使用 Scoped Storage 访问特定文件夹并将文件写入其中

How to gain access to a specific folder and write a file into it using Scoped Storage

我的应用程序具有将 txt 格式的 GPS 数据导出到共享文件夹(现在可以通过 getExternalStorageDirectory 访问)的功能,我必须将其切换到分区存储 (API 30)。
(作为信息,该应用程序是 Android 的开源 GPS Logger。)

以后我会让用户选择用于导出的文件夹,使用:

public void openDirectory(Uri uriToLoad) {
    // Choose a directory using the system's file picker.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    startActivityForResult(intent, your-request-code);
}

public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {
    if (requestCode == your-request-code && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the directory that the user selected.
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            // Perform operations on the document using its URI.
        }
    }
}

这样我的应用程序将获得对所选文件夹的访问权限。

如何在该文件夹中创建文本文件并使用 BufferedWriter 写入数据?

我知道也许我可以使用类似的东西:

...
Uri fileUri = // Something related to the previous uri, but I cannot find the solution
OutputStream outputStream = getContentResolver().openOutputStream(fileUri, "rw");
txtBW = new BufferedWriter(new OutputStreamWriter(outputStream));
...

但我发现没有任何效果。

DocumentFile 是 Google 在文档树中导航的有点笨拙的解决方案。你想要的食谱应该是:

  • 把你为你的树得到的 UriDocumentFile.fromTreeUri()
  • 包裹在 DocumentFile
  • DocumentFile 上调用 createFile(),提供文件名或其他显示名称,这将为您提供代表该文档的 DocumentFile
  • 在文档的 DocumentFile 上调用 getUri() 以获得代表文档的 Uri
  • ContentResolver 上使用 openOutputStream() 将您想要的内容写入 Uri

请注意,如果您需要长期访问此树,请务必call takePersistableUriPermission() on a ContentResolver,为文档树传入Uri。这应该可以让您访问树及其中的所有文档。

1-你可以像这样创建一个目标路径(我的目标是“Android/media”文件夹)

 private const val ROOT_FOLDER = "primary:"
 private const val ANDROID_MEDIA = "${ROOT_FOLDER}Android/media"
 private const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"

2- 创建 URI 和意图对象

val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
val uri =DocumentsContract.buildDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
                ANDROID_MEDIA)
val intent = storageManager.primaryStorageVolume.createOpenDocumentTreeIntent()
                .putExtra(DocumentsContract.EXTRA_INITIAL_URI,uri)

3- 注册 activity 以获得结果

例如

private val openDocumentTreeLauncher =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
        if (it.resultCode == RESULT_OK) {
            val data: Uri = it.data?.data!!
            contentResolver.takePersistableUriPermission(
                data,
                Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
            )}

最终启动 Activity 结果

openDocumentTreeLauncher.launch(intent)