Skip to content

AssetFileDescriptor.CreateInputStream/CreateOutputStream hide FileInputStream/FileOutputStream channel access #11797

Description

@tipa

Android framework version

net10.0-android

Affected platform version

Microsoft.Android.Ref.36 36.1.53 + Microsoft.Android.Runtime.36.android 36.1.53

Description

Android.Content.Res.AssetFileDescriptor.CreateInputStream() and CreateOutputStream() hide the concrete Java stream types.

The Android Java API returns:

public FileInputStream createInputStream() throws IOException
public FileOutputStream createOutputStream() throws IOException

AOSP source:
https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/content/res/AssetFileDescriptor.java

The .NET Android binding invokes the correct Java methods/signatures:

createInputStream.()Ljava/io/FileInputStream;
createOutputStream.()Ljava/io/FileOutputStream;

but exposes both methods as:

System.IO.Stream AssetFileDescriptor.CreateInputStream()
System.IO.Stream AssetFileDescriptor.CreateOutputStream()

The returned stream is wrapped through:

Android.Runtime.InputStreamInvoker.FromJniHandle(...)
Android.Runtime.OutputStreamInvoker.FromJniHandle(...)

This loses access to the concrete Java.IO.FileInputStream / Java.IO.FileOutputStream APIs.

This matters for large file copies, especially SAF/content URI copies, because the concrete Java stream types expose Channel. With the current binding, code using the auto-close AssetFileDescriptor.CreateInputStream() / CreateOutputStream() APIs cannot use:

FileInputStream.Channel.TransferTo(..., FileOutputStream.Channel)

Changing the existing return type would probably be breaking, but would it be possible to expose strongly typed alternatives or otherwise make the underlying Java.IO.FileInputStream / Java.IO.FileOutputStream accessible without reflection?

Steps to Reproduce

  1. Open an AssetFileDescriptor for a content URI:
using var afd = Application.Context.ContentResolver.OpenAssetFileDescriptor(uri, "w");
  1. Call CreateOutputStream():
using var stream = afd.CreateOutputStream();
  1. Observe that the returned type is System.IO.Stream, not Java.IO.FileOutputStream, even though the Android API method returns FileOutputStream.

  2. Try to use FileOutputStream.Channel for a large file copy:

var output = (Java.IO.FileOutputStream)stream; // not possible

The only practical workaround is to bypass CreateOutputStream():

using var output = new Java.IO.FileOutputStream(afd.FileDescriptor);

but then the caller must explicitly close the AssetFileDescriptor.

Did you find any workaround?

Yes. The workaround is to avoid AssetFileDescriptor.CreateInputStream() / CreateOutputStream() and create Java file streams from the raw file descriptor:

using var afd = Application.Context.ContentResolver.OpenAssetFileDescriptor(uri, "w");

try
{
    using var output = new Java.IO.FileOutputStream(afd.FileDescriptor);
    // use output.Channel here
}
finally
{
    afd.Close();
}

This preserves access to FileOutputStream.Channel, but it bypasses the Android auto-close stream APIs and requires explicit AssetFileDescriptor.Close() handling.

Relevant log output

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: Mono.AndroidIssues with the Android API binding (Mono.Android.dll).

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions