使用内容方案时保存文件的正确方法是什么?

Whats the correct way to save a file when using the Content sheme?

我使用以下意图让用户选择文件的保存位置:

// https://developer.android.com/guide/topics/providers/document-provider
var intent = new Intent(Intent.ActionCreateDocument);
intent.AddCategory(Intent.CategoryOpenable);
intent.SetType("image/png");
intent.PutExtra(Intent.ExtraTitle, "myfile");
StartActivityForResult(Intent.CreateChooser(intent, "Select Save Location"), 43);

它创建文件和 returns 文件的 uri:

content://com.android.providers.downloads.documents/document/436

但现在我悬而未决,因为文档的那部分以

结尾

After you create a new document you can get its URI in onActivityResult() so that you can continue to write to it.

而且我不知道该怎么做。由于我的结果使用的是 content 方案,因此我不能像对待常规 Java.IO.File 一样对待它并将其写入磁盘。那么如何将文件保存到我拥有的内容 uri?

当您在 OnActivityResult 中获取内容 Uri 时,您拥有对该 Uri 的临时权限(因此具有写入权限),因此您可以在写入模式下打开基于包裹的文件描述符,创建一个从该包裹的文件描述符输出流并写入您需要的任何内容。

将资产流写入用户选择的文件的示例:

protected async override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
    if (resultCode == Result.Ok && requestCode == 43)
    {
        var buffer = new byte[1024];
        using (var pFD = ContentResolver.OpenFileDescriptor(data.Data, "w"))
        using (var outputSteam = new FileOutputStream(pFD.FileDescriptor))
        using (var inputStream = Assets.Open("somePicture.png"))
        {
            while (inputStream.CanRead && inputStream.IsDataAvailable())
            {
                var readCount = await inputStream.ReadAsync(buffer, 0, buffer.Length);
                await outputSteam.WriteAsync(buffer, 0, readCount);
            }
        }

    }
    base.OnActivityResult(requestCode, resultCode, data);
}

更新(性能):

仅供参考,如果您是 saving/streaming 大文件,请避免在流上使用异步版本的读取和写入,而只需启动一个线程(或通过 [=29= 使用线程池中的线程) ]).

注意:由于所有 async/await 开销,这将 总是 更快,这有点像我平时的方式会这样做(通常根据文件大小快 2x(+))。

if (resultCode == Result.Ok && requestCode == 43)
{
    await Task.Run(() =>
    {
        // Buffer size can be "tuned" to enhance read/write performance
        var buffer = new byte[1024]; 
        using (var pFD = ContentResolver.OpenFileDescriptor(data.Data, "w"))
        using (var outputSteam = new FileOutputStream(pFD.FileDescriptor))
        using (var inputStream = Assets.Open("her.png"))
        {
            while (inputStream.CanRead && inputStream.IsDataAvailable())
            {
                var readCount = inputStream.Read(buffer, 0, buffer.Length);
                outputSteam.Write(buffer, 0, readCount);
            }
        }
    });
}