You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用BCL FileStream读取Android串口报错,Java类却可正常读取

为何Java类能读取串口流,但.NET FileStream却报错?

我在带RFID功能的Android平板上,通过厂商提供的so文件打开串口读取感应卡数据。使用Java的FileInputStream能正常读取,但尝试用.NET的FileStream时,调用Read方法抛出System.UnauthorizedAccessException(内部错误为Bad file descriptor)。


核心原因

  1. 句柄语义不兼容:厂商so返回的SafeFileHandle是为JNI/Android ART设计的,而.NET的FileStream期望的是符合POSIX标准的系统原生文件描述符,两者的句柄包装逻辑和语义不同,.NET无法正确识别JNI返回的句柄。
  2. 权限上下文差异:Java层通过厂商so打开串口时,可能已经处理了Android的设备权限(比如特殊系统权限或SU授权),但.NET的FileStream在访问时没有继承这个权限上下文,导致权限验证失败。
  3. 句柄所有权问题:JNI返回的句柄由ART管理,直接交给.NET的FileStream时,.NET无法确认句柄的所有权和有效性,进而触发"无效文件描述符"错误。

可行解决方案

方案1:将Java InputStream包装为.NET Stream

既然FileInputStream能正常工作,可以封装一个.NET Stream适配器,把Java流的操作映射到.NET接口,这样就能在共享项目中使用:

public class JavaInputStreamWrapper : Stream
{
    private readonly FileInputStream _inputStream;

    public JavaInputStreamWrapper(FileInputStream inputStream)
    {
        _inputStream = inputStream ?? throw new ArgumentNullException(nameof(inputStream));
    }

    public override bool CanRead => true;
    public override bool CanSeek => false;
    public override bool CanWrite => false;
    public override long Length => throw new NotSupportedException();
    public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _inputStream.Read(buffer, offset, count);
    }

    public override void Flush() => _inputStream.Flush();
    public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _inputStream.Close();
            _inputStream.Dispose();
        }
        base.Dispose(disposing);
    }
}

// 使用示例
var device = new Java.IO.File("/dev/ttyS9");
var absPath = new String(device.AbsolutePath);
var open = Open(JNIEnv.Handle, IntPtr.Zero, absPath.Handle, baudRate, 0);
var fd = Object.GetObject<FileDescriptor>(open, JniHandleOwnership.DoNotRegister);
var fileInputStream = new FileInputStream(fd);
var netStream = new JavaInputStreamWrapper(fileInputStream);

// 在共享项目中使用netStream
var data = new byte[64];
var length = netStream.Read(data, 0, data.Length);

方案2:直接使用系统文件描述符创建SafeFileHandle

如果厂商的so返回的是系统原生的文件描述符整数(而非JNI对象指针),可以修改DllImport的返回类型,手动创建SafeFileHandle:

// 修改DllImport的返回类型为int(系统文件描述符的整数形式)
[DllImport("serial_port", EntryPoint = "Java_android_1serialport_1api_SerialPort_open")]
public static extern int Open(IntPtr env, IntPtr thiz, IntPtr path, int baudrate, int flags);

// 使用时
var device = new Java.IO.File("/dev/ttyS9");
var absPath = new String(device.AbsolutePath);
int fd = Open(JNIEnv.Handle, IntPtr.Zero, absPath.Handle, baudRate, 0);
if (fd == -1)
{
    throw new IOException("Failed to open serial port");
}

// 创建SafeFileHandle,设置ownsHandle=true让.NET管理句柄生命周期
using var safeHandle = new SafeFileHandle((IntPtr)fd, ownsHandle: true);
using var fileStream = new FileStream(safeHandle, FileAccess.Read);
var data = new byte[64];
var length = fileStream.Read(data, 0, data.Length);

注意事项

  • 方案2仅适用于so返回原生文件描述符的情况,如果返回的是JNI的FileDescriptor对象指针,必须使用方案1。
  • 确保应用已申请Android串口访问权限(比如android.permission.ACCESS_SERIAL_PORT,具体权限需参考厂商文档)。

内容的提问来源于stack exchange,提问作者Giorgi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 23:49:50