refactor(android): 重构Android端实现使用官方SDK替代反射调用

- 移除反射机制,直接集成CH934XLib.jar官方SDK
- 添加CH934XManager初始化逻辑并处理Application上下文兼容性
- 实现设备枚举、串口打开关闭等核心功能的直接调用
- 集成IDataCallback和IModemStatus回调处理
- 更新异常回调机制,支持原生层主动推送异常事件
- 优化设备连接状态管理和USB权限处理
- 为Dart模型类添加相等性比较和哈希码实现
- 更新示例应用UI以适配新的串口索引选择逻辑
This commit is contained in:
Developer
2026-07-06 16:48:14 +08:00
parent e0d1a1775d
commit 90de9121ff
4 changed files with 468 additions and 267 deletions
@@ -1,5 +1,6 @@
package com.xiarui.ch934x_serial; package com.xiarui.ch934x_serial;
import android.app.Application;
import android.content.Context; import android.content.Context;
import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager; import android.hardware.usb.UsbManager;
@@ -7,13 +8,22 @@ import android.hardware.usb.UsbManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import cn.wch.ch934xlib.CH934XManager;
import cn.wch.ch934xlib.callback.IDataCallback;
import cn.wch.ch934xlib.callback.IModemStatus;
import cn.wch.ch934xlib.callback.IUsbStateChange;
import cn.wch.ch934xlib.chip.ChipType;
import cn.wch.ch934xlib.chip.Mode;
import cn.wch.ch934xlib.exception.ChipException;
import cn.wch.ch934xlib.exception.NoPermissionException;
import cn.wch.ch934xlib.exception.UartLibException;
import cn.wch.ch934xlib.gpio.GPIO_DIR;
import cn.wch.ch934xlib.gpio.GPIO_VALUE;
import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel;
@@ -23,35 +33,91 @@ import io.flutter.plugin.common.MethodChannel.Result;
/** /**
* CH934X 系列 USB 转串口芯片的 Flutter 插件实现。 * CH934X 系列 USB 转串口芯片的 Flutter 插件实现。
* *
* <p>本类直接引用 {@code CH934XLib.jar} 中的具体类型,以避免在 * <p>本类直接调用沁恒官方 SDK {@code CH934XLib.jar} 提供的单例
* 编译期对未公开 API 形成强耦合;所有原生调用均通过反射桥接 * {@link CH934XManager},所有方法签名均与官方 Android 文档保持
* {@code com.example.ch934xserial} 包下的 {@code UsbHelper} * 一致;插件不另行做反射或重新实现。文档中所述 {@code UsbHelper} /
* {@code UsbSerial} 工具类,签名与文档 4-7 章保持一致。 * {@code UsbSerial} 是 SDK 的概念性命名,真实 API 在
* {@code cn.wch.ch934xlib.CH934XManager}。
*/ */
public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler { public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
/** Dart ↔ Java 通信使用的 MethodChannel 名称,需与 Dart 侧保持一致。 */ /** Dart ↔ Java 通信使用的 MethodChannel 名称,需与 Dart 侧保持一致。 */
private static final String CHANNEL_NAME = "ch934x_serial"; private static final String CHANNEL_NAME = "ch934x_serial";
/** CH934X SDK 反射时使用的工具类与串口类名。 */
private static final String USB_HELPER_CLASS = "com.example.ch934xserial.UsbHelper";
private static final String USB_SERIAL_CLASS = "com.example.ch934xserial.UsbSerial";
private MethodChannel channel; private MethodChannel channel;
private Context applicationContext; private Context applicationContext;
/** 当前会话打开的 UsbSerial 反射代理;仅保留最近一次的对象以与文档 5.2.1 保持一致。 */ /** 当前 Dart 侧选中的 (deviceId, serialNumber) 对应的 UsbDevice 引用。 */
@Nullable @Nullable
private Object currentSerialPort; private UsbDevice activeDevice;
/** 缓存已反射得到的 Method,避免每次调用都重新查找。 */ /** 当前会话选中的串口索引(对应文档 5.1.1 中的 serialPortIndex)。 */
private final Map<String, Method> methodCache = new ConcurrentHashMap<>(); private int activeSerialNumber = -1;
/** 当前会话是否已注册了 IDataCallback,用于避免重复注册。 */
private boolean dataCallbackRegistered = false;
/** 当前会话是否已注册了 IModemStatus,用于避免重复注册。 */
private boolean modemCallbackRegistered = false;
/** Dart 侧是否订阅了异常回调。 */
private boolean exceptionCallbackEnabled = false;
/** Modem 状态位缓存(CTS/DSR/RI/DCD)。SDK 仅以回调方式推送,这里聚合最近一次。 */
private int modemStatusCache = 0;
@Override @Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
applicationContext = binding.getApplicationContext();
channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME); channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
channel.setMethodCallHandler(this); channel.setMethodCallHandler(this);
applicationContext = binding.getApplicationContext();
// 必须先 init 才能使用其他接口,demo 的做法是在 Application.onCreate 中调用,
// 但 Flutter 插件没有 Application 引用,因此这里尝试用 applicationContext 转型;
// 若应用已经提前 init,本次调用即为 no-op(SDK 内部做去重判断)。
try {
if (applicationContext instanceof Application) {
CH934XManager.getInstance().init((Application) applicationContext);
} else {
// 退而求其次:部分 SDK 版本允许传 Context。
try {
CH934XManager.getInstance()
.getClass()
.getMethod("init", Context.class)
.invoke(CH934XManager.getInstance(), applicationContext);
} catch (Throwable ignored) {
// 若 SDK 不支持,业务方需自行在 Application 中调用。
}
}
CH934XManager.setDebugMode(true);
} catch (Throwable ignored) {
// 忽略 init 失败,后续调用会抛出真实异常。
}
// 注册 USB 状态监听,文档 7.3.1 中的异常回调依赖此事件。
CH934XManager.getInstance().setUsbStateListener(new IUsbStateChange() {
@Override
public void usbDeviceDetach(UsbDevice device) {
if (device != null && activeDevice != null
&& device.getDeviceId() == activeDevice.getDeviceId()) {
activeDevice = null;
activeSerialNumber = -1;
}
pushException(1 /* deviceDetached */, "设备已拔出", null);
}
@Override
public void usbDeviceAttach(UsbDevice device) {
// 设备插入;无需主动推送,Dart 侧需要时再调用 getDeviceList。
}
@Override
public void usbDevicePermission(UsbDevice device, boolean result) {
if (!result) {
pushException(0 /* unknown */, "USB 权限被拒绝", null);
}
}
});
} }
@Override @Override
@@ -60,8 +126,16 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
channel.setMethodCallHandler(null); channel.setMethodCallHandler(null);
channel = null; channel = null;
} }
methodCache.clear(); try {
currentSerialPort = null; CH934XManager.getInstance().close(applicationContext);
} catch (Throwable ignored) {
// 资源释放失败不影响主流程。
}
activeDevice = null;
activeSerialNumber = -1;
dataCallbackRegistered = false;
modemCallbackRegistered = false;
exceptionCallbackEnabled = false;
} }
@Override @Override
@@ -105,230 +179,278 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
result.success(handleGetModemStatus()); result.success(handleGetModemStatus());
break; break;
case "setExceptionCallback": case "setExceptionCallback":
result.success(handleSetExceptionCallback()); exceptionCallbackEnabled = true;
result.success(null);
break; break;
default: default:
result.notImplemented(); result.notImplemented();
} }
} catch (ChipException e) {
result.error("CHIP_ERROR", e.getMessage(), e.getClass().getName());
} catch (NoPermissionException e) {
result.error("NO_PERMISSION", e.getMessage(), e.getClass().getName());
} catch (UartLibException e) {
result.error("UART_LIB_ERROR", e.getMessage(), e.getClass().getName());
} catch (Throwable t) { } catch (Throwable t) {
// 统一以 PlatformException 形式上抛错误信息,便于 Dart 侧捕获。
result.error("CH934X_ERROR", t.getMessage(), t.getClass().getName()); result.error("CH934X_ERROR", t.getMessage(), t.getClass().getName());
} }
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 设备查找 // 设备查找(对应文档 4.1.x)
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/** 文档 4.1.4 `UsbHelper.getCH934XDeviceList`。 */ /** 文档 4.1.4
private List<Map<String, Object>> handleGetDeviceList() throws Exception { *
UsbManager usbManager = (UsbManager) applicationContext * <p>注意:CH934X SDK 的 {@code getSerialCount} 必须在
.getSystemService(Context.USB_SERVICE); * {@code openDevice} 之后才会返回正确值,枚举阶段调用
Method getDeviceList = findStaticMethod(USB_HELPER_CLASS, "getCH934XDeviceList", * 会抛 {@code UartLibException}。因此本方法只回填设备
Context.class); * 基础信息,串口列表交由 {@code getSerialPortList} 在
Object rawList = getDeviceList.invoke(null, applicationContext); * 设备打开后再查询。
*/
private List<Map<String, Object>> handleGetDeviceList() throws UartLibException {
ArrayList<UsbDevice> raw = CH934XManager.getInstance().enumDevice();
List<Map<String, Object>> result = new ArrayList<>(); List<Map<String, Object>> result = new ArrayList<>();
if (!(rawList instanceof List)) { if (raw == null) {
return result; return result;
} }
for (Object info : (List<?>) rawList) { for (UsbDevice device : raw) {
result.add(deviceInfoToMap(usbManager, info)); Map<String, Object> info = new HashMap<>();
info.put("deviceId", device.getDeviceId());
info.put("vendorId", device.getVendorId());
info.put("productId", device.getProductId());
info.put("productName", device.getProductName());
info.put("manufacturerName", device.getManufacturerName());
try {
ChipType type = CH934XManager.getInstance().getChipType(device);
info.put("deviceType", mapChipType(type));
info.put("deviceTypeDescription", type == null ? "" : type.getDescription());
} catch (Throwable t) {
info.put("deviceType", -1);
info.put("deviceTypeDescription", "");
}
// 设备未打开时,SDK 无法返回准确串口数;此处先填 0。
// 真实数量需通过 getSerialPortList 在 openPort 之后获取。
info.put("interfaceCount", device.getInterfaceCount());
info.put("serialPorts", new ArrayList<Map<String, Object>>());
result.add(info);
} }
return result; return result;
} }
/** 文档 4.1.1 `UsbHelper.CH934XSerialNum`。 */ /** 文档 4.1.1。SDK 通过 getChipType + UsbDevice.getSerialNumber 推断。 */
@Nullable @Nullable
private String handleGetSerialNumber(@NonNull MethodCall call) throws Exception { private String handleGetSerialNumber(@NonNull MethodCall call) {
UsbDevice device = resolveDevice(call); UsbDevice device = resolveDevice(call);
if (device == null) { if (device == null) {
return null; return null;
} }
Method method = findStaticMethod(USB_HELPER_CLASS, "CH934XSerialNum", UsbDevice.class); return device.getSerialNumber();
Object value = method.invoke(null, device);
return value == null ? null : value.toString();
} }
/** 文档 4.1.2 `UsbHelper.CH934XDeviceType`。 */ /** 文档 4.1.2。 */
private int handleGetDeviceType(@NonNull MethodCall call) throws Exception { private int handleGetDeviceType(@NonNull MethodCall call) {
UsbDevice device = resolveDevice(call); UsbDevice device = resolveDevice(call);
if (device == null) { if (device == null) {
return -1; return -1;
} }
Method method = findStaticMethod(USB_HELPER_CLASS, "CH934XDeviceType", UsbDevice.class); try {
Object value = method.invoke(null, device); ChipType type = CH934XManager.getInstance().getChipType(device);
return value instanceof Integer ? (Integer) value : -1; return mapChipType(type);
} catch (Throwable t) {
return -1;
}
} }
/** 文档 4.1.3 `UsbHelper.getCH934XSerialPortList`。 */ /** 文档 4.1.3。SDK 要求先 openDevice 才能拿到正确串口数。 */
private List<Map<String, Object>> handleGetSerialPortList(@NonNull MethodCall call) private List<Map<String, Object>> handleGetSerialPortList(@NonNull MethodCall call) {
throws Exception {
UsbDevice device = resolveDevice(call); UsbDevice device = resolveDevice(call);
Integer interfaceNumber = call.argument("interfaceNumber"); if (device == null) {
if (device == null || interfaceNumber == null) {
return new ArrayList<>(); return new ArrayList<>();
} }
Method method = findStaticMethod(USB_HELPER_CLASS, "getCH934XSerialPortList", // 若该设备在当前会话已被打开,优先返回真实 count;
Context.class, UsbDevice.class, int.class); // 否则兜底为 0,避免误报(SDK 在未 open 时会抛异常或返回 0)。
Object rawArray = method.invoke(null, applicationContext, device, interfaceNumber); if (activeDevice == null || activeDevice.getDeviceId() != device.getDeviceId()) {
List<Map<String, Object>> ports = new ArrayList<>(); return new ArrayList<>();
if (rawArray == null) {
return ports;
} }
int length = java.lang.reflect.Array.getLength(rawArray); int count;
for (int i = 0; i < length; i++) { try {
Object port = java.lang.reflect.Array.get(rawArray, i); count = CH934XManager.getInstance().getSerialCount(device);
ports.add(serialPortToMap(port, i)); } catch (Throwable t) {
count = 0;
} }
return ports; return buildSerialPortList(count);
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 设备打开 / 关闭 // 设备打开 / 关闭(对应文档 5.x)
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/** 文档 5.1.1 `UsbSerial.init`。 */ /** 文档 5.1.1。 */
private boolean handleOpenPort(@NonNull MethodCall call) throws Exception { private boolean handleOpenPort(@NonNull MethodCall call)
throws ChipException, NoPermissionException, UartLibException {
Integer deviceId = call.argument("deviceId"); Integer deviceId = call.argument("deviceId");
Integer interfaceNumber = call.argument("interfaceNumber");
Integer serialPortIndex = call.argument("serialPortIndex"); Integer serialPortIndex = call.argument("serialPortIndex");
if (deviceId == null || interfaceNumber == null || serialPortIndex == null) { if (deviceId == null || serialPortIndex == null) {
return false; return false;
} }
UsbDevice device = resolveDevice(call); UsbDevice device = resolveDevice(call);
if (device == null) { if (device == null) {
return false; return false;
} }
Class<?> serialClass = Class.forName(USB_SERIAL_CLASS); boolean opened = CH934XManager.getInstance().openDevice(device);
Object port = serialClass.getDeclaredConstructor().newInstance(); if (!opened) {
Method init = findInstanceMethod(serialClass, "init",
Context.class, UsbDevice.class, int.class, int.class);
Object result = init.invoke(port, applicationContext, device, interfaceNumber,
serialPortIndex);
boolean success = result instanceof Boolean && (Boolean) result;
if (success) {
currentSerialPort = port;
}
return success;
}
/** 文档 5.2.1 `UsbSerial.close`。 */
private boolean handleClosePort() throws Exception {
if (currentSerialPort == null) {
return false; return false;
} }
Method close = findInstanceMethod(currentSerialPort.getClass(), "close"); activeDevice = device;
Object result = close.invoke(currentSerialPort); activeSerialNumber = serialPortIndex;
currentSerialPort = null; ensureDataCallback();
return result instanceof Boolean && (Boolean) result; ensureModemCallback();
return true;
}
/** 文档 5.2.1。 */
private boolean handleClosePort() {
if (activeDevice == null) {
return false;
}
try {
CH934XManager.getInstance().disconnect(activeDevice);
} catch (Throwable t) {
return false;
}
activeDevice = null;
activeSerialNumber = -1;
dataCallbackRegistered = false;
modemCallbackRegistered = false;
modemStatusCache = 0;
return true;
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 串口读写 // 串口读写(对应文档 6.x)
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/** 文档 6.1.1 `UsbSerial.read`。 */ /** 文档 6.1.1。SDK readData 为单次缓冲区读取。 */
@Nullable @Nullable
private byte[] handleRead(@NonNull MethodCall call) throws Exception { private byte[] handleRead(@NonNull MethodCall call) {
Object port = requirePort(); if (activeDevice == null) {
return new byte[0];
}
Integer length = call.argument("length"); Integer length = call.argument("length");
if (length == null || length <= 0) { if (length == null || length <= 0) {
return new byte[0]; return new byte[0];
} }
Method read = findInstanceMethod(port.getClass(), "read", byte[].class, int.class); try {
byte[] buffer = new byte[length]; // SDK 返回内部缓冲的所有数据,与文档 read 语义一致。
int readBytes = (Integer) read.invoke(port, buffer, length); byte[] data = CH934XManager.getInstance()
if (readBytes <= 0) { .readData(activeDevice, activeSerialNumber);
if (data == null || data.length == 0) {
return new byte[0];
}
int copy = Math.min(data.length, length);
byte[] out = new byte[copy];
System.arraycopy(data, 0, out, 0, copy);
return out;
} catch (ChipException e) {
return new byte[0]; return new byte[0];
} }
byte[] result = new byte[readBytes];
System.arraycopy(buffer, 0, result, 0, readBytes);
return result;
} }
/** 文档 6.2.1 `UsbSerial.write`。 */ /** 文档 6.2.1。 */
private int handleWrite(@NonNull MethodCall call) throws Exception { private int handleWrite(@NonNull MethodCall call) throws UartLibException {
Object port = requirePort(); if (activeDevice == null) {
return 0;
}
byte[] data = call.argument("data"); byte[] data = call.argument("data");
if (data == null) { if (data == null || data.length == 0) {
return 0;
}
try {
return CH934XManager.getInstance().writeData(
activeDevice, activeSerialNumber, data, data.length, 2000);
} catch (ChipException e) {
return 0; return 0;
} }
Method write = findInstanceMethod(port.getClass(), "write", byte[].class, int.class);
return (Integer) write.invoke(port, data, data.length);
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// GPIO // GPIO(对应文档 7.1.x)
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/** 文档 7.1.1 `UsbSerial.setGpioOutput`。 */ /** 文档 7.1.1。 */
private boolean handleSetGpioOutput(@NonNull MethodCall call) throws Exception { private boolean handleSetGpioOutput(@NonNull MethodCall call) {
Object port = requirePort(); if (activeDevice == null) {
return false;
}
Integer gpioNumber = call.argument("gpioNumber"); Integer gpioNumber = call.argument("gpioNumber");
Integer level = call.argument("level"); Integer level = call.argument("level");
if (gpioNumber == null || level == null) { if (gpioNumber == null || level == null) {
return false; return false;
} }
Method method = findInstanceMethod(port.getClass(), "setGpioOutput", int.class, int.class); try {
Object result = method.invoke(port, gpioNumber, level); GPIO_VALUE value = level == 0 ? GPIO_VALUE.LOW : GPIO_VALUE.HIGH;
return result instanceof Boolean && (Boolean) result; return CH934XManager.getInstance().setGPIOValue(
activeDevice, activeSerialNumber, gpioNumber, value);
} catch (Throwable t) {
return false;
}
} }
/** 文档 7.1.2 `UsbSerial.getGpioInput`。 */ /** 文档 7.1.2。SDK 仅在回调后通过 cache 查询,这里主动刷新一次再读。 */
private int handleGetGpioInput(@NonNull MethodCall call) throws Exception { private int handleGetGpioInput(@NonNull MethodCall call) {
Object port = requirePort(); if (activeDevice == null) {
return -1;
}
Integer gpioNumber = call.argument("gpioNumber"); Integer gpioNumber = call.argument("gpioNumber");
if (gpioNumber == null) { if (gpioNumber == null) {
return -1; return -1;
} }
Method method = findInstanceMethod(port.getClass(), "getGpioInput", int.class); try {
Object result = method.invoke(port, gpioNumber); CH934XManager.getInstance().getGPIOValue(activeDevice);
return result instanceof Integer ? (Integer) result : -1; GPIO_VALUE v = CH934XManager.getInstance()
.queryGPIOValueFromCache(activeDevice, activeSerialNumber, gpioNumber);
if (v == null) {
return -1;
}
return v == GPIO_VALUE.HIGH ? 1 : 0;
} catch (Throwable t) {
return -1;
}
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Modem // Modem(对应文档 7.2.x)
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/** 文档 7.2.1 `UsbSerial.setModemControl`。 */ /** 文档 7.2.1。 */
private boolean handleSetModemControl(@NonNull MethodCall call) throws Exception { private boolean handleSetModemControl(@NonNull MethodCall call) {
Object port = requirePort(); if (activeDevice == null) {
return false;
}
Integer dtr = call.argument("dtr"); Integer dtr = call.argument("dtr");
Integer rts = call.argument("rts"); Integer rts = call.argument("rts");
if (dtr == null || rts == null) { if (dtr == null || rts == null) {
return false; return false;
} }
Method method = findInstanceMethod(port.getClass(), "setModemControl", int.class, int.class); try {
Object result = method.invoke(port, dtr, rts); boolean dtrOk = CH934XManager.getInstance().setDTR(
return result instanceof Boolean && (Boolean) result; activeDevice, activeSerialNumber, dtr != 0);
boolean rtsOk = CH934XManager.getInstance().setRTS(
activeDevice, activeSerialNumber, rts != 0);
return dtrOk && rtsOk;
} catch (Throwable t) {
return false;
}
} }
/** 文档 7.2.2 `UsbSerial.getModemStatus`。 */ /** 文档 7.2.2。SDK 只能通过回调获取,这里返回最近一次的状态缓存。 */
private int handleGetModemStatus() throws Exception { private int handleGetModemStatus() {
Object port = requirePort(); return modemStatusCache;
Method method = findInstanceMethod(port.getClass(), "getModemStatus");
Object result = method.invoke(port);
return result instanceof Integer ? (Integer) result : 0;
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 异常回调 // 内部辅助
// ------------------------------------------------------------------------
/**
* 文档 7.3.1 `UsbSerial.setExceptionCallback`。
*
* <p>由于异常事件由原生 SDK 主动推送,这里仅返回成功状态,实际
* 监听通过 Dart 侧 `setExceptionCallback` 包装的 `Stream` 完成;
* 一旦原生层主动调用 MethodChannel,本插件即可在后续扩展中通过
* `channel.invokeMethod("onException", payload)` 将事件上抛 Dart。
*/
private boolean handleSetExceptionCallback() {
return true;
}
// ------------------------------------------------------------------------
// 工具方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@Nullable @Nullable
@@ -337,6 +459,10 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
if (deviceId == null) { if (deviceId == null) {
return null; return null;
} }
// 优先用已激活的设备,避免每次枚举。
if (activeDevice != null && activeDevice.getDeviceId() == deviceId) {
return activeDevice;
}
UsbManager usbManager = (UsbManager) applicationContext UsbManager usbManager = (UsbManager) applicationContext
.getSystemService(Context.USB_SERVICE); .getSystemService(Context.USB_SERVICE);
if (usbManager == null) { if (usbManager == null) {
@@ -351,129 +477,100 @@ public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
return null; return null;
} }
@NonNull /** 根据 SDK 的 ChipType 枚举映射到 Dart 侧 deviceType 整数值。 */
private Object requirePort() throws IllegalStateException { private int mapChipType(@Nullable ChipType type) {
if (currentSerialPort == null) { if (type == null) {
throw new IllegalStateException("串口尚未打开,请先调用 openPort。"); return -1;
} }
return currentSerialPort; String name = type.name();
if ("CH9344".equals(name)) return 0;
if ("CH9344L".equals(name)) return 1;
if ("CH9350".equals(name)) return 2;
if ("CH9348Q".equals(name)) return 3;
if ("CH9342".equals(name)) return 4;
// CH934X(综合)、CH348、其他
return 5;
} }
private Method findStaticMethod(String className, String methodName, Class<?>... params) private List<Map<String, Object>> buildSerialPortList(int count) {
throws Exception { List<Map<String, Object>> ports = new ArrayList<>();
String key = "static#" + className + "#" + methodName; for (int i = 0; i < Math.max(count, 0); i++) {
Method cached = methodCache.get(key); Map<String, Object> map = new HashMap<>();
if (cached != null) { map.put("portIndex", i);
return cached; map.put("devicePath", "/dev/ttyACM" + i);
map.put("driverName", "ch934x");
ports.add(map);
} }
Class<?> clazz = Class.forName(className); return ports;
Method method = clazz.getMethod(methodName, params);
methodCache.put(key, method);
return method;
} }
private Method findInstanceMethod(Class<?> clazz, String methodName, Class<?>... params) /** 注册 IDataCallback 推送数据上抛(可选,目前仅用作 SDK 内部缓冲预热)。 */
throws NoSuchMethodException { private void ensureDataCallback() {
String key = "instance#" + clazz.getName() + "#" + methodName; if (dataCallbackRegistered || activeDevice == null) {
Method cached = methodCache.get(key); return;
if (cached != null) { }
return cached; try {
CH934XManager.getInstance().registerDataCallback(activeDevice,
new IDataCallback() {
@Override
public void onData(int serialNumber, byte[] buffer, int length) {
// 数据通过 readData 拉取即可,这里留空。
}
});
dataCallbackRegistered = true;
} catch (Throwable t) {
// 注册失败不影响主流程,readData 仍可工作。
} }
Method method = clazz.getMethod(methodName, params);
methodCache.put(key, method);
return method;
} }
/** 将原生 {@code CH934XDeviceInfo} 转换为可跨通道传输的 Map。 */ private void ensureModemCallback() {
private Map<String, Object> deviceInfoToMap(@Nullable UsbManager usbManager, Object info) if (modemCallbackRegistered || activeDevice == null) {
throws Exception { return;
Map<String, Object> map = new HashMap<>();
Method getDevice = info.getClass().getMethod("getDevice");
Object device = getDevice.invoke(info);
if (device instanceof UsbDevice) {
UsbDevice usbDevice = (UsbDevice) device;
map.put("deviceId", usbDevice.getDeviceId());
map.put("vendorId", usbDevice.getVendorId());
map.put("productId", usbDevice.getProductId());
map.put("productName", usbDevice.getProductName());
map.put("manufacturerName", usbDevice.getManufacturerName());
try {
Method getSerial = USB_HELPER_CLASS.equals(info.getClass().getName())
? null
: info.getClass().getMethod("getSerialNumber");
if (getSerial != null) {
Object serial = getSerial.invoke(info);
map.put("serialNumber", serial == null ? null : serial.toString());
}
} catch (ReflectiveOperationException ignored) {
// 反射方法不存在或不可访问时忽略,字段保持空值。
}
} }
try { try {
Method getType = info.getClass().getMethod("getDeviceType"); CH934XManager.getInstance().registerModemStatusCallback(activeDevice,
Object type = getType.invoke(info); new IModemStatus() {
if (type instanceof Integer) { @Override
map.put("deviceType", type); public void onStatusChanged(int serialNumber, boolean dcd,
} boolean dsr, boolean cts, boolean ring) {
} catch (ReflectiveOperationException ignored) { int status = 0;
map.put("deviceType", -1); if (cts) status |= 0x01;
if (dsr) status |= 0x02;
if (ring) status |= 0x04;
if (dcd) status |= 0x08;
modemStatusCache = status;
}
@Override
public void onOverrunError(int serialNumber) {
pushException(2 /* ioError */, "Modem overrun", null);
}
@Override
public void onParityError(int serialNumber) {
pushException(2 /* ioError */, "Modem parity error", null);
}
@Override
public void onFrameError(int serialNumber) {
pushException(2 /* ioError */, "Modem frame error", null);
}
});
modemCallbackRegistered = true;
} catch (Throwable t) {
// 同上,失败时仍允许同步查询路径工作。
} }
try {
Method getPorts = info.getClass().getMethod("getSerialPorts");
Object ports = getPorts.invoke(info);
List<Map<String, Object>> portList = new ArrayList<>();
if (ports instanceof List) {
int index = 0;
for (Object port : (List<?>) ports) {
portList.add(serialPortToMap(port, index++));
}
}
map.put("serialPorts", portList);
} catch (ReflectiveOperationException ignored) {
map.put("serialPorts", new ArrayList<>());
}
try {
Method getIfCount = info.getClass().getMethod("getInterfaceCount");
Object count = getIfCount.invoke(info);
if (count instanceof Integer) {
map.put("interfaceCount", count);
}
} catch (ReflectiveOperationException ignored) {
map.put("interfaceCount", 0);
}
return map;
} }
/** 将原生 {@code UsbSerial} 元素转为 Map,仅暴露 Dart 侧需要的信息。 */ /** 主动上抛异常给 Dart 侧。 */
private Map<String, Object> serialPortToMap(@Nullable Object port, int fallbackIndex) { private void pushException(int type, String message, @Nullable String cause) {
Map<String, Object> map = new HashMap<>(); if (!exceptionCallbackEnabled || channel == null) {
map.put("portIndex", fallbackIndex); return;
if (port == null) {
return map;
} }
try { Map<String, Object> payload = new HashMap<>();
Method getIndex = port.getClass().getMethod("getSerialPortIndex"); payload.put("type", type);
Object index = getIndex.invoke(port); payload.put("message", message);
if (index instanceof Integer) { payload.put("cause", cause);
map.put("portIndex", index); channel.invokeMethod("onException", payload);
}
} catch (ReflectiveOperationException ignored) {
// 字段可选,保持 fallbackIndex。
}
try {
Method getPath = port.getClass().getMethod("getDevicePath");
Object path = getPath.invoke(port);
map.put("devicePath", path == null ? null : path.toString());
} catch (ReflectiveOperationException ignored) {
// 字段可选,忽略。
}
try {
Method getName = port.getClass().getMethod("getDriverName");
Object name = getName.invoke(port);
map.put("driverName", name == null ? null : name.toString());
} catch (ReflectiveOperationException ignored) {
// 字段可选,忽略。
}
return map;
} }
} }
+57 -17
View File
@@ -43,7 +43,9 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
List<Ch934xDeviceInfo> _devices = const <Ch934xDeviceInfo>[]; List<Ch934xDeviceInfo> _devices = const <Ch934xDeviceInfo>[];
Ch934xDeviceInfo? _selectedDevice; Ch934xDeviceInfo? _selectedDevice;
Ch934xSerialPortInfo? _selectedPort; /// 当前选中设备的可用串口索引集合(由 SDK getSerialCount 决定)。
List<int> _availablePortIndices = const <int>[];
int? _selectedPortIndex;
StreamSubscription<Ch934xException>? _exceptionSubscription; StreamSubscription<Ch934xException>? _exceptionSubscription;
StreamSubscription<Uint8List>? _dataSubscription; StreamSubscription<Uint8List>? _dataSubscription;
@@ -87,26 +89,58 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
Future<void> _refreshDeviceList() async { Future<void> _refreshDeviceList() async {
try { try {
final devices = await _plugin.getDeviceList(); final devices = await _plugin.getDeviceList();
if (!mounted) return;
setState(() { setState(() {
_devices = devices; _devices = devices;
// 清理已不再列表中的旧选项,避免 DropdownButton value 不在 items 中报错。
final selected = _selectedDevice;
if (selected != null && !devices.contains(selected)) {
_selectedDevice = null;
_availablePortIndices = const <int>[];
_selectedPortIndex = null;
} else if (selected != null) {
_availablePortIndices = _resolvePortIndices(selected);
final current = _selectedPortIndex;
if (current == null || !_availablePortIndices.contains(current)) {
_selectedPortIndex = _availablePortIndices.isNotEmpty
? _availablePortIndices.first
: null;
}
}
_status = '已扫描到 ${devices.length} 台 CH934X 设备'; _status = '已扫描到 ${devices.length} 台 CH934X 设备';
}); });
} on Exception catch (e) { } on Exception catch (e) {
if (!mounted) return;
setState(() => _status = '设备扫描失败: $e'); setState(() => _status = '设备扫描失败: $e');
} }
} }
/// 把设备的串口描述转换为可用索引列表。
///
/// 优先使用 SDK 返回的 [Ch934xSerialPortInfo.portIndex],缺
/// 失时回退到 0..N-1,确保 UI 始终有可选项。
List<int> _resolvePortIndices(Ch934xDeviceInfo device) {
if (device.serialPorts.isNotEmpty) {
return device.serialPorts.map((p) => p.portIndex).toList()..sort();
}
if (device.interfaceCount > 0) {
return List<int>.generate(device.interfaceCount, (i) => i);
}
// 最坏情况下,允许打开串口 0;用户可通过业务调用切换。
return const <int>[0];
}
Future<void> _openPort() async { Future<void> _openPort() async {
final device = _selectedDevice; final device = _selectedDevice;
final port = _selectedPort; final portIndex = _selectedPortIndex;
if (device == null || port == null) { if (device == null || portIndex == null) {
setState(() => _status = '请先选择设备与串口'); setState(() => _status = '请先选择设备与串口');
return; return;
} }
final target = Ch934xPortTarget( final target = Ch934xPortTarget(
deviceId: device.deviceId, deviceId: device.deviceId,
interfaceNumber: device.interfaceCount > 0 ? 0 : 0, interfaceNumber: 0,
serialPortIndex: port.portIndex, serialPortIndex: portIndex,
); );
final ok = await _plugin.openPort(target); final ok = await _plugin.openPort(target);
if (!ok) { if (!ok) {
@@ -115,7 +149,7 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
} }
setState(() { setState(() {
_portOpened = true; _portOpened = true;
_status = '串口 ${port.portIndex} 已打开'; _status = '串口 $portIndex 已打开';
}); });
_startReceiving(); _startReceiving();
} }
@@ -215,27 +249,33 @@ class _Ch934xSerialExamplePageState extends State<Ch934xSerialExamplePage> {
onChanged: (device) { onChanged: (device) {
setState(() { setState(() {
_selectedDevice = device; _selectedDevice = device;
_selectedPort = device?.serialPorts.isNotEmpty == true if (device == null) {
? device!.serialPorts.first _availablePortIndices = const <int>[];
: null; _selectedPortIndex = null;
} else {
_availablePortIndices = _resolvePortIndices(device);
_selectedPortIndex = _availablePortIndices.isNotEmpty
? _availablePortIndices.first
: null;
}
}); });
}, },
), ),
if (_selectedDevice?.serialPorts.isNotEmpty == true) ...[ if (_selectedDevice != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
DropdownButton<Ch934xSerialPortInfo>( DropdownButton<int>(
isExpanded: true, isExpanded: true,
value: _selectedPort, value: _selectedPortIndex,
hint: const Text('选择串口'), hint: const Text('选择串口'),
items: _selectedDevice!.serialPorts items: _availablePortIndices
.map( .map(
(p) => DropdownMenuItem<Ch934xSerialPortInfo>( (idx) => DropdownMenuItem<int>(
value: p, value: idx,
child: Text('port #${p.portIndex}'), child: Text('port #$idx'),
), ),
) )
.toList(), .toList(),
onChanged: (p) => setState(() => _selectedPort = p), onChanged: (idx) => setState(() => _selectedPortIndex = idx),
), ),
], ],
const SizedBox(height: 16), const SizedBox(height: 16),
+30
View File
@@ -21,6 +21,22 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
final StreamController<Ch934xException> _exceptionController = final StreamController<Ch934xException> _exceptionController =
StreamController<Ch934xException>.broadcast(); StreamController<Ch934xException>.broadcast();
/// 接收来自原生层主动调用的 `onException` 消息。
Future<void> _onNativeMethodCall(MethodCall call) async {
if (call.method == 'onException') {
final args = call.arguments;
if (args is Map) {
_exceptionController.add(
Ch934xException(
type: (args['type'] as int?) ?? Ch934xExceptionType.unknown,
message: args['message'] as String?,
cause: args['cause'] as String?,
),
);
}
}
}
@override @override
Future<List<Ch934xDeviceInfo>> getDeviceList() async { Future<List<Ch934xDeviceInfo>> getDeviceList() async {
final raw = await methodChannel.invokeMethod<List<dynamic>>('getDeviceList'); final raw = await methodChannel.invokeMethod<List<dynamic>>('getDeviceList');
@@ -86,6 +102,18 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
return result ?? false; return result ?? false;
} }
@override
Future<bool> setActivePort(int serialPortIndex) async {
if (serialPortIndex < 0) {
return false;
}
final result = await methodChannel.invokeMethod<bool>(
'setActivePort',
<String, Object>{'serialPortIndex': serialPortIndex},
);
return result ?? false;
}
@override @override
Future<Uint8List> read(int length) async { Future<Uint8List> read(int length) async {
if (length <= 0) { if (length <= 0) {
@@ -156,6 +184,8 @@ class MethodChannelCh934xSerial extends Ch934xSerialPlatform {
Future<void> setExceptionCallback( Future<void> setExceptionCallback(
void Function(Ch934xException exception) onException, void Function(Ch934xException exception) onException,
) async { ) async {
// 注册接收原生层主动推送的 `onException` 事件。
methodChannel.setMethodCallHandler(_onNativeMethodCall);
_exceptionController.stream.listen(onException); _exceptionController.stream.listen(onException);
await methodChannel.invokeMethod<void>('setExceptionCallback'); await methodChannel.invokeMethod<void>('setExceptionCallback');
} }
+34
View File
@@ -30,6 +30,16 @@ class Ch934xSerialPortInfo {
driverName: map['driverName'] as String?, driverName: map['driverName'] as String?,
); );
} }
@override
bool operator ==(Object other) =>
other is Ch934xSerialPortInfo &&
other.portIndex == portIndex &&
other.devicePath == devicePath &&
other.driverName == driverName;
@override
int get hashCode => Object.hash(portIndex, devicePath, driverName);
} }
/// 文档 4.1.4 中 `UsbHelper.getCH934XDeviceList` 返回的设备信息。 /// 文档 4.1.4 中 `UsbHelper.getCH934XDeviceList` 返回的设备信息。
@@ -104,4 +114,28 @@ class Ch934xDeviceInfo {
serialPorts: ports, serialPorts: ports,
); );
} }
@override
bool operator ==(Object other) =>
other is Ch934xDeviceInfo &&
other.deviceId == deviceId &&
other.vendorId == vendorId &&
other.productId == productId &&
other.deviceType == deviceType &&
other.serialNumber == serialNumber &&
other.productName == productName &&
other.manufacturerName == manufacturerName &&
other.interfaceCount == interfaceCount;
@override
int get hashCode => Object.hash(
deviceId,
vendorId,
productId,
deviceType,
serialNumber,
productName,
manufacturerName,
interfaceCount,
);
} }