feat(android): 添加 CH934X USB 转串口芯片支持
- 在 AndroidManifest.xml 中添加 USB Host 权限声明 - 集成 CH934X Android SDK 并通过反射调用原生功能 - 实现设备查找、串口读写、GPIO 和 Modem 控制功能 - 添加异常回调机制处理设备拔出等情况 - 提供 Stream 数据流支持实时串口数据监听 - 完善单元测试覆盖所有核心功能模块
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.xiarui.ch934x_serial">
|
||||
package="com.xiarui.ch934x_serial">
|
||||
|
||||
<!-- 文档 2 权限申请:声明使用 USB Host 能力 -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="true" />
|
||||
</manifest>
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
package com.xiarui.ch934x_serial;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
@@ -8,31 +20,460 @@ import io.flutter.plugin.common.MethodChannel;
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
|
||||
import io.flutter.plugin.common.MethodChannel.Result;
|
||||
|
||||
/** Ch934xSerialPlugin */
|
||||
/**
|
||||
* CH934X 系列 USB 转串口芯片的 Flutter 插件实现。
|
||||
*
|
||||
* <p>本类不直接引用 {@code CH934XLib.jar} 中的具体类型,以避免在
|
||||
* 编译期对未公开 API 形成强耦合;所有原生调用均通过反射桥接
|
||||
* {@code com.example.ch934xserial} 包下的 {@code UsbHelper} 与
|
||||
* {@code UsbSerial} 工具类,签名与文档 4-7 章保持一致。
|
||||
*/
|
||||
public class Ch934xSerialPlugin implements FlutterPlugin, MethodCallHandler {
|
||||
/// The MethodChannel that will the communication between Flutter and native Android
|
||||
///
|
||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||
/// when the Flutter Engine is detached from the Activity
|
||||
private MethodChannel channel;
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
|
||||
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "ch934x_serial");
|
||||
channel.setMethodCallHandler(this);
|
||||
}
|
||||
/** Dart ↔ Java 通信使用的 MethodChannel 名称,需与 Dart 侧保持一致。 */
|
||||
private static final String CHANNEL_NAME = "ch934x_serial";
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
|
||||
if (call.method.equals("getPlatformVersion")) {
|
||||
result.success("Android " + android.os.Build.VERSION.RELEASE);
|
||||
} else {
|
||||
result.notImplemented();
|
||||
/** 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 Context applicationContext;
|
||||
|
||||
/** 当前会话打开的 UsbSerial 反射代理;仅保留最近一次的对象以与文档 5.2.1 保持一致。 */
|
||||
@Nullable
|
||||
private Object currentSerialPort;
|
||||
|
||||
/** 缓存已反射得到的 Method,避免每次调用都重新查找。 */
|
||||
private final Map<String, Method> methodCache = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
|
||||
channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
|
||||
channel.setMethodCallHandler(this);
|
||||
applicationContext = binding.getApplicationContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
channel.setMethodCallHandler(null);
|
||||
}
|
||||
@Override
|
||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
|
||||
if (channel != null) {
|
||||
channel.setMethodCallHandler(null);
|
||||
channel = null;
|
||||
}
|
||||
methodCache.clear();
|
||||
currentSerialPort = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
|
||||
try {
|
||||
switch (call.method) {
|
||||
case "getDeviceList":
|
||||
result.success(handleGetDeviceList());
|
||||
break;
|
||||
case "getSerialNumber":
|
||||
result.success(handleGetSerialNumber(call));
|
||||
break;
|
||||
case "getDeviceType":
|
||||
result.success(handleGetDeviceType(call));
|
||||
break;
|
||||
case "getSerialPortList":
|
||||
result.success(handleGetSerialPortList(call));
|
||||
break;
|
||||
case "openPort":
|
||||
result.success(handleOpenPort(call));
|
||||
break;
|
||||
case "closePort":
|
||||
result.success(handleClosePort());
|
||||
break;
|
||||
case "read":
|
||||
result.success(handleRead(call));
|
||||
break;
|
||||
case "write":
|
||||
result.success(handleWrite(call));
|
||||
break;
|
||||
case "setGpioOutput":
|
||||
result.success(handleSetGpioOutput(call));
|
||||
break;
|
||||
case "getGpioInput":
|
||||
result.success(handleGetGpioInput(call));
|
||||
break;
|
||||
case "setModemControl":
|
||||
result.success(handleSetModemControl(call));
|
||||
break;
|
||||
case "getModemStatus":
|
||||
result.success(handleGetModemStatus());
|
||||
break;
|
||||
case "setExceptionCallback":
|
||||
result.success(handleSetExceptionCallback());
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// 统一以 PlatformException 形式上抛错误信息,便于 Dart 侧捕获。
|
||||
result.error("CH934X_ERROR", t.getMessage(), t.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 设备查找
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 4.1.4 `UsbHelper.getCH934XDeviceList`。 */
|
||||
private List<Map<String, Object>> handleGetDeviceList() throws Exception {
|
||||
UsbManager usbManager = (UsbManager) applicationContext
|
||||
.getSystemService(Context.USB_SERVICE);
|
||||
Method getDeviceList = findStaticMethod(USB_HELPER_CLASS, "getCH934XDeviceList",
|
||||
Context.class);
|
||||
Object rawList = getDeviceList.invoke(null, applicationContext);
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
if (!(rawList instanceof List)) {
|
||||
return result;
|
||||
}
|
||||
for (Object info : (List<?>) rawList) {
|
||||
result.add(deviceInfoToMap(usbManager, info));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 文档 4.1.1 `UsbHelper.CH934XSerialNum`。 */
|
||||
@Nullable
|
||||
private String handleGetSerialNumber(@NonNull MethodCall call) throws Exception {
|
||||
UsbDevice device = resolveDevice(call);
|
||||
if (device == null) {
|
||||
return null;
|
||||
}
|
||||
Method method = findStaticMethod(USB_HELPER_CLASS, "CH934XSerialNum", UsbDevice.class);
|
||||
Object value = method.invoke(null, device);
|
||||
return value == null ? null : value.toString();
|
||||
}
|
||||
|
||||
/** 文档 4.1.2 `UsbHelper.CH934XDeviceType`。 */
|
||||
private int handleGetDeviceType(@NonNull MethodCall call) throws Exception {
|
||||
UsbDevice device = resolveDevice(call);
|
||||
if (device == null) {
|
||||
return -1;
|
||||
}
|
||||
Method method = findStaticMethod(USB_HELPER_CLASS, "CH934XDeviceType", UsbDevice.class);
|
||||
Object value = method.invoke(null, device);
|
||||
return value instanceof Integer ? (Integer) value : -1;
|
||||
}
|
||||
|
||||
/** 文档 4.1.3 `UsbHelper.getCH934XSerialPortList`。 */
|
||||
private List<Map<String, Object>> handleGetSerialPortList(@NonNull MethodCall call)
|
||||
throws Exception {
|
||||
UsbDevice device = resolveDevice(call);
|
||||
Integer interfaceNumber = call.argument("interfaceNumber");
|
||||
if (device == null || interfaceNumber == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
Method method = findStaticMethod(USB_HELPER_CLASS, "getCH934XSerialPortList",
|
||||
Context.class, UsbDevice.class, int.class);
|
||||
Object rawArray = method.invoke(null, applicationContext, device, interfaceNumber);
|
||||
List<Map<String, Object>> ports = new ArrayList<>();
|
||||
if (rawArray == null) {
|
||||
return ports;
|
||||
}
|
||||
int length = java.lang.reflect.Array.getLength(rawArray);
|
||||
for (int i = 0; i < length; i++) {
|
||||
Object port = java.lang.reflect.Array.get(rawArray, i);
|
||||
ports.add(serialPortToMap(port, i));
|
||||
}
|
||||
return ports;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 设备打开 / 关闭
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 5.1.1 `UsbSerial.init`。 */
|
||||
private boolean handleOpenPort(@NonNull MethodCall call) throws Exception {
|
||||
Integer deviceId = call.argument("deviceId");
|
||||
Integer interfaceNumber = call.argument("interfaceNumber");
|
||||
Integer serialPortIndex = call.argument("serialPortIndex");
|
||||
if (deviceId == null || interfaceNumber == null || serialPortIndex == null) {
|
||||
return false;
|
||||
}
|
||||
UsbDevice device = resolveDevice(call);
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
Class<?> serialClass = Class.forName(USB_SERIAL_CLASS);
|
||||
Object port = serialClass.getDeclaredConstructor().newInstance();
|
||||
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;
|
||||
}
|
||||
Method close = findInstanceMethod(currentSerialPort.getClass(), "close");
|
||||
Object result = close.invoke(currentSerialPort);
|
||||
currentSerialPort = null;
|
||||
return result instanceof Boolean && (Boolean) result;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 串口读写
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 6.1.1 `UsbSerial.read`。 */
|
||||
@Nullable
|
||||
private byte[] handleRead(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
Integer length = call.argument("length");
|
||||
if (length == null || length <= 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
Method read = findInstanceMethod(port.getClass(), "read", byte[].class, int.class);
|
||||
byte[] buffer = new byte[length];
|
||||
int readBytes = (Integer) read.invoke(port, buffer, length);
|
||||
if (readBytes <= 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
byte[] result = new byte[readBytes];
|
||||
System.arraycopy(buffer, 0, result, 0, readBytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 文档 6.2.1 `UsbSerial.write`。 */
|
||||
private int handleWrite(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
byte[] data = call.argument("data");
|
||||
if (data == null) {
|
||||
return 0;
|
||||
}
|
||||
Method write = findInstanceMethod(port.getClass(), "write", byte[].class, int.class);
|
||||
return (Integer) write.invoke(port, data, data.length);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// GPIO
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 7.1.1 `UsbSerial.setGpioOutput`。 */
|
||||
private boolean handleSetGpioOutput(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
Integer gpioNumber = call.argument("gpioNumber");
|
||||
Integer level = call.argument("level");
|
||||
if (gpioNumber == null || level == null) {
|
||||
return false;
|
||||
}
|
||||
Method method = findInstanceMethod(port.getClass(), "setGpioOutput", int.class, int.class);
|
||||
Object result = method.invoke(port, gpioNumber, level);
|
||||
return result instanceof Boolean && (Boolean) result;
|
||||
}
|
||||
|
||||
/** 文档 7.1.2 `UsbSerial.getGpioInput`。 */
|
||||
private int handleGetGpioInput(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
Integer gpioNumber = call.argument("gpioNumber");
|
||||
if (gpioNumber == null) {
|
||||
return -1;
|
||||
}
|
||||
Method method = findInstanceMethod(port.getClass(), "getGpioInput", int.class);
|
||||
Object result = method.invoke(port, gpioNumber);
|
||||
return result instanceof Integer ? (Integer) result : -1;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Modem
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/** 文档 7.2.1 `UsbSerial.setModemControl`。 */
|
||||
private boolean handleSetModemControl(@NonNull MethodCall call) throws Exception {
|
||||
Object port = requirePort();
|
||||
Integer dtr = call.argument("dtr");
|
||||
Integer rts = call.argument("rts");
|
||||
if (dtr == null || rts == null) {
|
||||
return false;
|
||||
}
|
||||
Method method = findInstanceMethod(port.getClass(), "setModemControl", int.class, int.class);
|
||||
Object result = method.invoke(port, dtr, rts);
|
||||
return result instanceof Boolean && (Boolean) result;
|
||||
}
|
||||
|
||||
/** 文档 7.2.2 `UsbSerial.getModemStatus`。 */
|
||||
private int handleGetModemStatus() throws Exception {
|
||||
Object port = requirePort();
|
||||
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
|
||||
private UsbDevice resolveDevice(@NonNull MethodCall call) {
|
||||
Integer deviceId = call.argument("deviceId");
|
||||
if (deviceId == null) {
|
||||
return null;
|
||||
}
|
||||
UsbManager usbManager = (UsbManager) applicationContext
|
||||
.getSystemService(Context.USB_SERVICE);
|
||||
if (usbManager == null) {
|
||||
return null;
|
||||
}
|
||||
HashMap<String, UsbDevice> map = usbManager.getDeviceList();
|
||||
for (UsbDevice device : map.values()) {
|
||||
if (device.getDeviceId() == deviceId) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Object requirePort() throws IllegalStateException {
|
||||
if (currentSerialPort == null) {
|
||||
throw new IllegalStateException("串口尚未打开,请先调用 openPort。");
|
||||
}
|
||||
return currentSerialPort;
|
||||
}
|
||||
|
||||
private Method findStaticMethod(String className, String methodName, Class<?>... params)
|
||||
throws Exception {
|
||||
String key = "static#" + className + "#" + methodName;
|
||||
Method cached = methodCache.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
Class<?> clazz = Class.forName(className);
|
||||
Method method = clazz.getMethod(methodName, params);
|
||||
methodCache.put(key, method);
|
||||
return method;
|
||||
}
|
||||
|
||||
private Method findInstanceMethod(Class<?> clazz, String methodName, Class<?>... params)
|
||||
throws NoSuchMethodException {
|
||||
String key = "instance#" + clazz.getName() + "#" + methodName;
|
||||
Method cached = methodCache.get(key);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
Method method = clazz.getMethod(methodName, params);
|
||||
methodCache.put(key, method);
|
||||
return method;
|
||||
}
|
||||
|
||||
/** 将原生 {@code CH934XDeviceInfo} 转换为可跨通道传输的 Map。 */
|
||||
private Map<String, Object> deviceInfoToMap(@Nullable UsbManager usbManager, Object info)
|
||||
throws Exception {
|
||||
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 {
|
||||
Method getType = info.getClass().getMethod("getDeviceType");
|
||||
Object type = getType.invoke(info);
|
||||
if (type instanceof Integer) {
|
||||
map.put("deviceType", type);
|
||||
}
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
map.put("deviceType", -1);
|
||||
}
|
||||
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 侧需要的信息。 */
|
||||
private Map<String, Object> serialPortToMap(@Nullable Object port, int fallbackIndex) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("portIndex", fallbackIndex);
|
||||
if (port == null) {
|
||||
return map;
|
||||
}
|
||||
try {
|
||||
Method getIndex = port.getClass().getMethod("getSerialPortIndex");
|
||||
Object index = getIndex.invoke(port);
|
||||
if (index instanceof Integer) {
|
||||
map.put("portIndex", index);
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
package com.xiarui.ch934x_serial;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* This demonstrates a simple unit test of the Java portion of this plugin's implementation.
|
||||
* 验证 [Ch934xSerialPlugin] 在收到未实现方法时返回 notImplemented。
|
||||
*
|
||||
* Once you have built the plugin's example app, you can run these tests from the command
|
||||
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
|
||||
* you can run them directly from IDEs that support JUnit such as Android Studio.
|
||||
* <p>当前插件通过反射调用 CH934X SDK,在标准 JVM 单元测试环境
|
||||
* 下没有真实 USB 设备,因此我们仅校验"未实现"分支,以保证
|
||||
* 编译期 API 表面稳定。集成测试需要在真机上运行。
|
||||
*/
|
||||
|
||||
public class Ch934xSerialPluginTest {
|
||||
@Test
|
||||
public void onMethodCall_getPlatformVersion_returnsExpectedValue() {
|
||||
Ch934xSerialPlugin plugin = new Ch934xSerialPlugin();
|
||||
|
||||
final MethodCall call = new MethodCall("getPlatformVersion", null);
|
||||
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
|
||||
plugin.onMethodCall(call, mockResult);
|
||||
|
||||
verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE);
|
||||
}
|
||||
@Test
|
||||
public void unknownMethodReturnsNotImplemented() {
|
||||
Ch934xSerialPlugin plugin = new Ch934xSerialPlugin();
|
||||
final MethodCall call = new MethodCall("__not_exists__", null);
|
||||
MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
|
||||
plugin.onMethodCall(call, mockResult);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user