使用BCL FileStream读取Android串口报错,Java类却可正常读取
为何Java类能读取串口流,但.NET FileStream却报错?
我在带RFID功能的Android平板上,通过厂商提供的so文件打开串口读取感应卡数据。使用Java的FileInputStream能正常读取,但尝试用.NET的FileStream时,调用Read方法抛出System.UnauthorizedAccessException(内部错误为Bad file descriptor)。
核心原因
- 句柄语义不兼容:厂商so返回的
SafeFileHandle是为JNI/Android ART设计的,而.NET的FileStream期望的是符合POSIX标准的系统原生文件描述符,两者的句柄包装逻辑和语义不同,.NET无法正确识别JNI返回的句柄。 - 权限上下文差异:Java层通过厂商so打开串口时,可能已经处理了Android的设备权限(比如特殊系统权限或SU授权),但.NET的
FileStream在访问时没有继承这个权限上下文,导致权限验证失败。 - 句柄所有权问题: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
相关产品推荐
相关产品推荐

