ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

uniapp原生插件实现Android USB串口通信完整方案

uniapp原生插件实现Android USB串口通信完整方案 这套方案我实际跑通过完整代码和踩坑记录都在下面了。这个项目的核心思路是绕开uniapp官方没有串口模块的限制利用它的原生插件机制把串口通信能力封装成一个uni_modules插件。这么做的好处是以后不管是维护还是扩展功能都不用动原生代码在JS层就能搞定对团队里不熟悉Android开发的同学也友好得多。我这次用的开发环境是HBuilderX 3.8.12uniapp用的是Vue3版本Android端的串口库是基于谷歌官方的usb-serial-for-android改造的支持市面上绝大多数的USB转串口芯片。整个项目的调试设备是一块STM32F103C8T6的最小系统板通过CH340模块和手机连接手机端是Android 11的Redmi。1. 项目整体设计思路与方案选型1.1 为什么选用uniapp 原生插件的组合方案先说结论如果你要开发一个需要串口通信的物联网控制Appuniapp配合原生插件是当前性价比最高的方案之一。有人可能会问为什么不用纯原生开发其实原因很简单现在大部分物联网产品的控制端除了串口调试功能之外还得做设备管理、数据上报、远程控制这些业务页面。如果全部用原生开发Android和iOS两套代码量翻倍不说后续的迭代和维护成本也是实实在在的压力。uniapp的优势在于业务层全部用Vue语法来写一份代码两端跑。串口通信这个底层能力通过原生插件的方式封装既绕开了uniapp本身不支持串口模块的短板又保留了跨平台开发的高效。这种方案在田间的实际反馈很不错特别是对于设备端还没完全定型的项目改动业务逻辑的成本非常低。1.2 技术方案的选型对比与理由在正式动手之前我把市面上能用的方案都捋了一遍纯JS模拟串口这个基本不用考虑Web环境里的串口API兼容性太差Android WebView也不支持事倍功半。WebSocket中转如果设备本身有WiFi或以太网能力倒是可以用这个方案但那不叫串口通信了而是网络通信压根不适用于纯串口设备。Web Bluetooth只适合蓝牙设备串口场景完全不相关。uniapp原生插件就是本文的重点。由原生层负责和USB设备通信JS层通过插件暴露的接口控制再把数据通过事件通道回传给JS层。这个方案兼容性最好可用性也最高。我最终选了原生插件方案屙的坑少方案稳。特别是Android端的串口库已经非常成熟底层是JNI调用Linux的termios接口稳定性和兼容性都久经考验。2. 原生插件的完整实现过程2.1 创建uniapp原生插件项目这一步看起来简单但很多人会踩坑。打开Android Studio新建一个项目包名一定要和你要发布的uniapp插件的标识对应起来。我的插件标识是SerialPort-Bridge所以包名用的com.example.serialport。记住uniapp的插件标识就是你在uni.requireNativePlugin里要引用的名字搞反了会一直报插件找不到。创建完项目之后需要把uniapp的SDK依赖加进来。在build.gradle文件里找到dependencies节点加这么一段implementation com.android.support:appcompat-v7:28.0.0 implementation io.dcloud:uniapp-v8-release:3.8.12注意版本号要和你HBuilderX里的uniapp版本对应否则后面打包的时候会出现SDK版本不一致的报错。这个坑我刚开始没注意后面打正式包的时候折腾了半天。2.2 封装串口通信核心模块核心模块我把它拆成了四个部分设备发现、连接管理、数据读写和事件回传。在原生层新建一个SerialPortManager类统一管理这些逻辑。先看设备发现的部分这个是最基础的能力public class SerialPortManager { private static final String TAG SerialPortManager; public ListUsbDevice listDevices() { ListUsbDevice deviceList new ArrayList(); HashMapString, UsbDevice devices mUsbManager.getDeviceList(); for (UsbDevice device : devices.values()) { // 过滤掉非串口设备 if (isSerialPortDevice(device)) { deviceList.add(device); } } return deviceList; } private boolean isSerialPortDevice(UsbDevice device) { // 常见USB转串口芯片的厂商ID判断 int vendorId device.getVendorId(); return vendorId 0x1A86 || // CH340 vendorId 0x10C4 || // CP210x vendorId 0x0403; // FT232 } }这里做了一个很重要的过滤逻辑只找USB转串口芯片的厂商ID。如果不过滤手机会把U盘、摄像头这些USB设备也列出来用户体验会很差。我这边优先支持了CH340、CP210x和FT232三种最常见的芯片基本上覆盖了市面上90%以上的USB转串口模块。2.3 USB权限获取与连接管理Android的USB设备访问权限是这一块最麻烦的问题。从Android 11开始系统对USB设备的访问限制更严格了应用必须动态申请权限才能和USB设备通信。我在连接设备之前先做了一次权限检查public void connect(UsbDevice device, int baudRate, int dataBits, int stopBits, int parity) { if (!mUsbManager.hasPermission(device)) { // 没有权限需要申请 PendingIntent permissionIntent PendingIntent.getBroadcast( mContext, 0, new Intent(ACTION_USB_PERMISSION), 0); mUsbManager.requestPermission(device, permissionIntent); return; // 等权限回调后继续 } // 有权限直接建立连接 mSerialPort new SerialPort(device, baudRate, dataBits, stopBits, parity); mSerialPort.open(); startReadThread(); }权限申请成功之后系统会回调一个广播。我在onReceive里处理回调结果然后继续建立连接。这里需要特别注意PendingIntent的flags在Android 12以上要求必须显式声明否则会被系统拒绝。这个问题我在低版本上测不出来一上高版本手机就翻了车。波特率的设置也很有讲究。默认我用的是115200但是实际使用中要根据设备端的固件设置来。像STM32串口1默认是115200ESP32默认也是115200但有的老设备是9600。我在插件里暴露了参数让业务层可以自己指定这样就不用为了不同设备重新发版了。2.4 数据读写与事件回传机制串口通信的核心就是读写。读取方面我开了一个独立的线程循环从串口读取数据。写入方面因为频繁的IO操作可能在UI线程上产生卡顿所以也统一放到子线程执行。看关键的读线程代码private void startReadThread() { mReadThread new Thread(() - { byte[] buffer new byte[1024]; int size; while (!mIsStop) { try { if (mSerialPort null) break; size mSerialPort.read(buffer); if (size 0) { final byte[] data Arrays.copyOf(buffer, size); // 通过主线程回调给JS层 mUniModuleContext.runOnUiThread(() - { sendEvent(onDataReceived, dataToHexString(data)); }); } } catch (Exception e) { e.printStackTrace(); break; } } }); mReadThread.start(); }这里有一个关键设计是我把读到的字节数组转成了十六进制字符串再通过事件传出去。为什么不用字符串因为串口设备返回的数据往往不是标准文本可能有各种控制字符。用十六进制字符串传给JS层JS那边想怎么解析都行。这个设计在对接不同设备的时候特别重要有的设备返回的是ASCII码文本有的是十六进制指令统一用十六进制串传递灵活性大大增加。2.5 在uniapp插件中暴露JS接口原生层写好了接下来就要把能力暴露给JS层使用。这里要用到uniapp的Module扩展机制。我需要新建一个SerialPortModule类继承UniModulepublic class SerialPortModule extends UniModule { UniJSMethod(uiThread false) public void connect(JSONObject options, UniJSCallback callback) { // 解析参数调用SerialPortManager进行连接 String deviceId options.optString(deviceId); int baudRate options.optInt(baudRate, 115200); boolean success mSerialPortManager.connect(deviceId, baudRate); callback.invoke(success ? createSuccessResult() : createErrorResult(连接失败)); } UniJSMethod(uiThread false) public void write(String hexData, UniJSCallback callback) { // 把十六进制字符串转成byte数组写入 byte[] bytes hexStringToBytes(hexData); boolean success mSerialPortManager.write(bytes); callback.invoke(success ? createSuccessResult() : createErrorResult(写入失败)); } UniJSMethod(uiThread false) public void disconnect(UniJSCallback callback) { mSerialPortManager.disconnect(); callback.invoke(createSuccessResult()); } }注意UniJSMethod这个注解里的uiThread false表示这个方法是异步执行的不会阻塞UI线程。对于串口操作这种可能耗时的任务一定要在子线程里执行。这个方法设计对了后面用起来会很顺手不然在连接设备的时候界面上转圈圈的loading会很僵硬。3. uni_modules插件的配置与打包3.1 构建uni_modules插件包的结构原生代码写完后要把插件打包成uni_modules格式才能在uniapp项目里引用。一个标准的uni_modules原生插件目录结构是这样的SerialPort-Bridge/ ├── package.json ├── android/ │ ├── libs/ │ │ ├── serialport-bridge-release.aar │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── com/example/serialport/ └── ios/ └── (iOS的文件这里先留空后续补)关键是package.json里的配置它决定了插件在uniapp里怎么被识别{ name: SerialPort-Bridge, id: SerialPort-Bridge, version: 1.0.0, description: uniapp串口通信原生插件支持USB转串口设备, android: { package: com.example.serialport, dependencies: [], permissions: [], abis: [armeabi-v7a, arm64-v8a] }, uni_modules: true }这里要特别注意package字段它必须和你原生代码里的包名一致否则插件的类找不到运行时直接崩溃。我这块一开始填错了结果一调用插件就抱ClassNotFoundException排查了很久才定位到。3.2 Android端的ABI和SDK版本配置串口通信涉及到底层的USB驱动所以编译的时候要特别注意ABI的支持情况。现在市面上的Android手机基本都是arm64-v8a了但还有不少老的平板和工控设备是armeabi-v7a所以在配置里要同时支持这两个架构。android { defaultConfig { ndk { abiFilters armeabi-v7a, arm64-v8a } } }还有一个容易出问题的地方是minSdkVersion。串口通信要用到android.hardware.usb.UsbManager这个API它从Android 3.1就有了但是权限模型在Android 6.0以后发生了很大变化。为了不踩动态权限的坑我把minSdkVersion设置为21也就是Android 5.0这样代码上可以少处理很多历史兼容性问题。3.3 离线打包的配置要点如果你是用离线打包的方式也就是不在HBuilderX云打包而是自己用Android Studio打包需要在AndroidManifest.xml里加上USB相关的配置。有一段很关键uses-feature android:nameandroid.hardware.usb.host android:requiredtrue /这个配置是告诉系统这个应用需要使用USB Host模式。如果没有这段配置在某些手机上插上USB设备系统会直接忽略根本不会弹授权框。这个坑我印象太深了当时在同事的平板上死活调不出来后来发现就是少这一句。4. JS层调用代码的完整实现原生插件准备好了现在回到uniapp项目里写业务层的调用代码。这一层是用起来最直观的部分也是大家最关心的。4.1 插件的引入和初始化在uniapp项目的main.js里不用做任何插件相关的操作直接在使用的地方通过uni.requireNativePlugin引入就行。不过我觉得最好把串口相关的操作封装成一个独立的工具模块不然业务页面里会到处散落着插件的调用逻辑。我建了一个utils/serialport.js文件// 串口通信工具模块 const SerialPortPlugin uni.requireNativePlugin(SerialPort-Bridge) class SerialPortManager { constructor() { this.isConnected false this.deviceList [] } // 获取设备列表 getDeviceList() { return new Promise((resolve, reject) { SerialPortPlugin.listDevices({}, (result) { if (result.code 0) { this.deviceList result.devices resolve(result.devices) } else { reject(new Error(result.message)) } }) }) } // 连接设备 connect(deviceId, baudRate 115200) { return new Promise((resolve, reject) { SerialPortPlugin.connect({ deviceId: deviceId, baudRate: baudRate }, (result) { if (result.code 0) { this.isConnected true resolve(result) } else { reject(new Error(result.message)) } }) }) } // 发送数据 send(data) { if (!this.isConnected) { return Promise.reject(new Error(设备未连接)) } return new Promise((resolve, reject) { SerialPortPlugin.write({ data: data }, (result) { if (result.code 0) { resolve(result) } else { reject(new Error(result.message)) } }) }) } } export default new SerialPortManager()这样封装之后业务页面里的代码就能写得非常干净让页面代码专注于处理业务。接口统一是Promise风格用起来非常顺手。4.2 在Vue组件中实现设备扫描与连接在业务页面里我把设备扫描和连接做成了一个独立的功能模块。界面上有一个扫描设备按钮点击后调用getDeviceList把找到的设备列出来用户点选之后再进行连接。核心代码是这样的export default { data() { return { devices: [], connectedDevice: null, isConnecting: false, serialData: , baudRate: 115200 } }, methods: { // 扫描USB设备 async scanDevices() { uni.showLoading({ title: 扫描设备中... }) try { this.devices await serialPortManager.getDeviceList() if (this.devices.length 0) { uni.showToast({ title: 未找到设备, icon: none }) } } catch (e) { console.error(扫描设备失败, e) uni.showToast({ title: 扫描失败, icon: none }) } finally { uni.hideLoading() } }, // 连接选中的设备 async connectDevice(device) { this.isConnecting true uni.showLoading({ title: 连接中... }) try { await serialPortManager.connect(device.deviceId, this.baudRate) this.connectedDevice device this.startListening() uni.showToast({ title: 连接成功, icon: success }) } catch (e) { console.error(连接失败, e) uni.showToast({ title: 连接失败, icon: none }) } finally { this.isConnecting false uni.hideLoading() } } } }4.3 串口数据的接收与解析这一部分是整个串口通信链路里最核心的环节。原生层把收到的数据通过onDataReceived事件传上来JS层的任务就是解析这段数据根据设备的通信协议做出响应。我在页面里注册了事件监听同时做了一层数据缓冲因为串口数据是流式的一次读取可能只收到半包数据必须要做粘包处理。这里我直接用了个简单的缓冲数组methods: { startListening() { // 注册事件监听 this.dataListener (data) { this.handleReceivedData(data) } uni.$on(onDataReceived, this.dataListener) }, handleReceivedData(hexString) { // 十六进制字符串转字节数组 const bytes this.hexToBytes(hexString) // 追加到缓冲区 this.buffer this.buffer.concat(bytes) // 尝试解析完整的数据帧 this.parseBuffer() }, parseBuffer() { // 这里根据设备的通信协议来解析 // 比如帧头是0xAA 0x55帧尾是0x0D 0x0A while (this.buffer.length 6) { // 查找帧头 const frameStart this.buffer.indexOf(0xAA) if (frameStart 0) { this.buffer [] break } // 检查是否包含帧尾 // ... 具体的协议解析逻辑 } } }这里需要特别说明的是缓冲区大小的选择。串口的接收缓冲区如果太小数据就会溢出丢失如果太大内存占用会比较高对低端安卓机不友好。我用的是1024字节经过测试在115200波特率下完全够用每条数据帧最多也就几十个字节。4.4 发送控制指令的封装发送数据是整个控制App的核心功能。在页面上我做了几个按钮分别对应开灯、关灯、查询状态这些操作。每个按钮调用对应的发送函数methods: { // 发送开灯指令 sendTurnOnLight() { const command AA 55 01 00 01 0D 0A this.sendCommand(command) }, // 发送关灯指令 sendTurnOffLight() { const command AA 55 01 00 00 0D 0A this.sendCommand(command) }, // 统一发送入口 async sendCommand(hexString) { try { const result await serialPortManager.send(hexString.replace(/ /g, )) console.log(指令发送成功, result) } catch (e) { console.error(指令发送失败, e) uni.showToast({ title: 发送失败, icon: none }) } } }这里我故意在UI上保留了发送指令的原始十六进制格式方便调试时直接对着设备端的逻辑去验证。如果你开发的设备有现成的协议文档按协议解析发送就可以了代码结构是完全一样的。5. 与STM32设备端的联调实战5.1 设备端串口通信参数的校准串口通信最讲究的就是对齐两个字。手机这边的波特率、数据位、停止位、校验位必须和设备端完全一致否则收到的就是乱码。这次联调的STM32F103C8T6板子USART1的配置我用的标准参数USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_Rx | USART_Mode_Tx;对应到手机端的设置就是波特率115200、8位数据位、1位停止位、无校验、无流控。这是串口通信最常见的一套配置也是默认配置。如果你要对接的设备是不同的参数记得要在手机App的连接界面提供这些选项并且要和设备端严格对齐否则数据就读不出来。5.2 数据回传的协议设计与解析设备端返回的数据格式我设计了一套最简单的自定义协议帧头是固定的0xAA 0x55帧尾是0x0D 0x0A。中间的三个字节分别是设备地址、数据长度和数据内容。举个实际的例子当用户点击查询温度按钮时手机发送AA 55 01 00 01 0D 0ASTM32收到指令后把温度数据打包返回比如温度是25.5度对应的十六进制就是AA 55 01 02 19 FF 0D 0A。JS层解析这段数据的时候先找帧头确认是完整帧之后再提取数据区。如果设备端没有返回或者返回的数据不完整就要做超时处理和丢弃处理。实际联调的时候我发现设备端偶尔会把两条指令连在一起返回这时候如果不做分包处理页面解析到的数据就是错的。所以上面代码里的缓冲区处理不是多余的是实打实踩过坑之后的优化。5.3 实测过程中的问题排查记录联调过程中遇到了几个比较典型的坑先记录一下和串口本身相关的乱码问题。第一次联调时手机收到的数据是乱码。排查发现不是波特率的问题而是STM32那边发送字符串的时候调用了printf函数重定向但重定向没写对。后来改成用USART_SendData逐字节发送乱码问题就消失了。还有一种乱码情况是USB转串口模块本身质量不行。CH340杂牌模块有的走线不好高速率下数据会出错。如果遇到这种问题建议先降到9600波特率试试如果能正常通信多半就是模块质量或者接线长度的问题。数据丢失问题。在高速率传输时偶尔会丢失一两个字节。这跟手机端的USB驱动有关也可能是设备的缓冲区不够。STM32的串口中断处理程序里如果数据处理不及时就会丢数据。我在STM32端加了一个环形缓冲区问题就解决了。收不到数据问题。这个最坑爹查了半天发现自己某次断开连接后忘了释放USB设备权限导致后续无法再次连接。重启手机后一切正常。后来我在代码里对连接失败的情况做了完善的异常处理确保串口资源能正常释放。5.4 串口通信线程安全的注意事项线程安全是原生层代码最容易忽视的地方。串口读写涉及到主线程和子线程的并发访问如果处理不当会出现数据错乱甚至程序崩溃。我在SerialPortManager里用了ReadWriteLock读线程和写线程可以并发但是同时只能有一个线程在写private final ReadWriteLock mReadWriteLock new ReentrantReadWriteLock(); public int write(byte[] data) { mReadWriteLock.writeLock().lock(); try { if (mSerialPort null) return -1; return mSerialPort.write(data); } finally { mReadWriteLock.writeLock().unlock(); } }还有一个细节是串口的关闭操作如果用户在接收数据的过程中关闭连接可能会出现空指针异常。所以关闭连接之前要先停掉读线程再关闭串口最后再释放USB权限。5.5 设备控制界面的交互设计心得界面交互方面我也有一点实际经验可以分享。串口控制App和普通App的区别在于它面对的是真实物理设备所以按钮按下去之后的反馈很重要。我做了三层确认按钮按下时的按压态变化、指令发送成功后的toast提示、设备状态变更后的实时刷新。如果按下按钮后设备没有任何响应用户会非常疑惑。所以我特意在界面上加了日志区域把每一次发送和接收的指令都实时滚动展示出来。这样不管是开发调试还是日常使用出了问题都能一眼看出是设备端的问题还是指令的问题。这块日志功能对开发阶段的帮助特别大也是我第一次做串口App时踩的坑当时只做了发送不显示日志调起协议来全靠猜效率极低。6. 完整代码与打包发布注意事项6.1 构建可运行的uniapp离线打包工程如果你要离线打包在Android Studio里创建一个新工程把uniapp的离线SDK导入进来。然后把上面的SerialPort-Bridge插件放进uni_modules目录。离线打包的build.gradle配置里比较关键的是要有这一段repositories { mavenCentral() google() // uni-app 离线SDK 依赖仓库 maven { url https://mvn.codechooser.cn/repository/maven-public/ } }SDK导入成功后在dcloud_uniplugins.json文件里注册插件{ nativePlugins: [ { name: SerialPort-Bridge, class: com.example.serialport.SerialPortModule } ] }这个文件是告诉uniapp运行时哪些原生类对应哪些插件。如果你漏了这步在JS里requireNativePlugin就找不到插件会直接报错。6.2 云打包配置的快速通道如果你不想折腾离线打包用HBuilderX的云打包也可以。只需要在项目的manifest.json里选择App模块配置找到NativePlugin配置把插件添加进去就行。不过有几个点要提醒一下云打包使用的是DCloud的云端环境你在本地Android Studio里放的依赖云打包时是不会一起打进去的。所以插件的所有依赖都要在插件包里配置好包括aar文件和gradle依赖。云打包的包名和签名跟你的插件包名要一致否则会出现签名冲突安装不了。云打包出来的包体积会比纯uniapp项目大一些因为多了原生代码的引用大约增加2到3MB可以接受。6.3 真机调试的通用流程真机调试一般是这么走的第一步手机开启开发者模式打开USB调试。第二步手机通过OTG线连接USB转串口模块。第三步打开App点扫描设备确认设备能被识别。识别不到就先排查是不是OTG线供电不足换个带供电的OTG线试试。第四步连接设备检查波特率设置。第五步发送一条测试指令看日志里能不能收到设备的响应。这五步看起来简单每一步都可能出问题我把排查顺序写在这儿了照着走会节省很多时间。7. 常见问题排查速查表与避坑指南调试到现在很多问题其实是重复出现的。这里把最典型的坑整理成一份速查表方便以后遇到问题直接对照。症状可能原因解决方案扫描不到设备没有OTG线或OTG线供电不足换带供电的OTG线扫描不到设备USB转串口芯片不在支持列表插上设备后查看厂商ID加入过滤列表扫描到但连接失败没有USB权限检查AndroidManifest的uses-feature配置连接成功但收不到数据波特率不正确和设备的波特率严格核对收发乱码校验位、停止位不匹配改成无校验、1位停止位数据丢字节USB模块质量差降低波特率或用更好的模块偶发崩溃线程安全问题检查串口的并发读写锁断开后无法重连USB资源未释放确认disconnect时有清理资源插件找不到uni_modules配置错误检查package.json的id和class路径离线打包找不到插件dcloud_uniplugins.json没配置手动注册插件避坑指南里我再多说几句权限申请不要省Android 11以上的权限模型有变化如果你只用了老的hasPermission逻辑在Android 12以上的手机上大概率会失败。我用了一个相对稳妥的做法不管有没有权限都走一遍权限申请流程既兼容老版本也适配新版本。日志一定要留。调试串口这种底层通信没有日志几乎等于瞎猜。我在页面里做的日志展示区域实际调试的时候非常关键。条件允许的话原生层的log也尽量打全用adb logcat直接看底层错误。设备端永远先自测。如果联调出了问题先不要怀疑手机端。用电脑的串口助手直接和设备通信如果电脑端也通不了那问题多半在设备端别在手机端空耗时间。8. 后续功能扩展与真实项目经验总结这个串口通信App的基础骨架搭完之后扩展空间是很大的。我说几个比较常见的扩展方向供大家参考。指令模板系统。把常用的控制指令预置到配置中心支持自定义新增、编辑指令。实际用的时候点一下按钮就能发送复杂的指令序列对于设备调试员来说能省不少时间。这算是一个小而实用的功能升级。数据图表化展示。把串口返回的传感器数据用canvas绘制成实时曲线。这个功能需要处理海量数据的缓存和性能优化但是效果很直观设备调试时能看出数据的变化趋势会比一个个数字好用很多。协议的动态配置。这套例子里协议是写死在代码里的实际产品中协议可能一变再变。更好的做法是把协议解析规则做成可配置的JSON结构设备端协议升级时只需要下发新的配置不用重新发版。远程协助模式。在一些工业应用场景现场设备出现问题需要远程指导。如果把串口App的操作界面和数据流同步到远端就相当于做了一台远程调试室。这些扩展里面我实际做过的体验是协议动态配置的收益是最明显的。因为产品迭代过程中设备端的协议变更是常态如果每次变协议都要改App发版过程会非常痛苦。大家做物联网产品设计的时候一定要把协议变更的频率预估高一点。另外再补充一个经验前端集成串口通信除了技术实现还要对物理层面的限制保持敏感。USB线的长度、转接头的质量、供电是否稳定这些都会影响通信的稳定性。编程语言解决不了物理定律的问题出了问题先检查硬件的连接。整个项目做下来我的体会是uniapp原生插件的开发难度没有想象中那么高关键是找准文档和调试路径。串口通信这部分原生层的代码量不大核心就是设备枚举、权限申请、数据读写这三件事。把这三点吃透剩下的事情就和普通的业务开发没有什么区别了。如果大家按照这篇内容走一遍相信也能顺利调通自己的串口控制App。
返回列表