ARTICLE DETAIL

资讯详情

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

uniapp对接东集PDA扫码:广播监听实现与避坑指南

uniapp对接东集PDA扫码:广播监听实现与避坑指南 做仓储物流、门店盘点这类业务PDA是绕不开的设备。这几年uniapp做跨端企业级App的项目越来越多但项目组一旦接到“对接PDA扫码”的需求十有八九会在同一个地方卡住uniapp到底怎么才能拿到PDA激光扫码枪扫出来的条码我最近一个项目就是这个情况。客户用的是东集PDAAndroid系统仓库现场拿它做入库扫码。设备本身自带激光扫码模组按一下侧键激光对着一维码照一下条码内容就被解析出来了。问题在于解析出来的结果怎么送到uniapp页面里东集PDA的扫描服务走的是Android标准机制——广播。扫描完成后系统会把条码内容封装进一个Intent通过sendBroadcast发到系统广播里。你的App只要注册一个BroadcastReceiver就能把数据接住。这就是标题里“广播监听”四个字的来龙去脉。对比另一个常见方案“键盘模拟输入”广播监听最大的好处是稳定、不受输入焦点影响、不用写原生插件一套uniapp的Native.js调用就能搞定。这篇文章我从原理讲到落地先说明白东集PDA扫码的广播机制和输出模式怎么选再给你一套可以直接抄的5分钟实现代码最后重点讲我在实际项目里踩过的坑——广播Action匹配、接收器被GC回收、页面注销、防抖处理每一条都是真金白银的教训。适合手里正好有东集PDA、用uniapp开发、又不想碰原生插件的小团队和个人开发者参考。1. 先从原理说起为什么是广播监听1.1 东集PDA的扫码工作方式PDA这东西说穿了就是“手机加扫描头”的合体。东集PDA的激光扫码模组属于硬件外设系统底层有一个扫描服务在统一管理它。当你在设备上按下侧边的物理扫码键扫描服务会激活激光模组完成一次扫描后再通过算法把条码内容解析出来。系统解析出条码之后得想办法把内容交给你的App。Android下面这条“最后一公里”有多种走法广播输出扫描服务调用sendBroadcast把条码作为Intent的extra发出去。Intent输出扫描服务尝试启动某个Activity把条码通过Intent传过去。键盘模拟输出扫描服务模拟键盘输入把条码一个字符一个字符“敲”到当前焦点控件里。SDK/API输出厂商提供SDKApp通过绑定服务调接口拿数据。对uniapp开发者来说广播输出是唯一一个“不依赖厂商SDK、不写原生插件”的方案。原因下面展开。1.2 三种常用输出模式的选型对比具体怎么选我列了一张表输出模式实现原理uniapp接入成本稳定性典型问题广播输出系统发广播App注册接收器接收低Native.js即可高需要确认Action名和字段名键盘模拟输出扫描服务模拟键盘逐字输入极低不用写代码低抢焦点、干扰输入框、速度不可控SDK/API输出厂商SDK绑定扫描服务高需写原生插件高原生代码工作量、厂商文档不齐这里多说两句键盘模拟的问题。很多第一次写PDA的同学设备默认就是这个模式扫一下发现条码直接进了当前焦点输入框会觉得“挺好的不用写代码”。真上了生产线问题全冒出来了页面任何地方都可能被莫名其妙填充弹窗里的Input框会突然收到一串条码输入法状态、焦点切换、特殊字符处理都可能出问题。我们项目组第一版图快就是用的键盘模拟后来被现场反馈逼着改成了广播方案。广播输出为什么稳因为它和UI完全解耦。扫描服务发广播、App的接收器收数据整个过程不依赖当前焦点、也不依赖哪个页面在前台。哪怕App退到后台广播一样能收前提是进程还活着。这对仓储场景很重要——操作员经常是扫完一箱货切到别的页面核对信息再切回来接着扫广播方案在这种反复切换的节奏下不会掉链子。2. 5分钟实现扫码从注册广播到拿到条码2.1 准备先确认设备上的广播配置拿到东集PDA第一件事不是写代码而是打开系统自带的扫描设置。东集设备上一般有一个叫“扫描设置”或“ScanService”的系统应用打开后找到类似“输出方式”的选项把它从“键盘”切到“广播”。然后关注两个关键配置广播Action和广播数据字段名。这两个值的默认值不同机型、不同固件版本很可能不一样。我见过的就有这么几种Action常见值com.iData.scan.SCAN_RESULT、com.android.scan.ACTION_BARCODE_DECODED、android.intent.action.SCANRESULT数据字段名常见值barcode、scannerdata、SCAN_BARCODE、decoded_data你可以把这几个值都记下来拿到设备上对照一下。如果设备允许自定义Action建议直接改成自己的包名前缀比如com.yourcompany.scanbar调试的时候一眼就能认出来。提示扫描设置改完之后有的机型需要重启App或者重启一下扫描服务才会生效。改完先手动扫描一次确认设备有声音或震动反馈扫描服务正常再去调代码。2.2 页面上注册广播接收器的完整代码uniapp App端底层是HTML5 Runtime暴露了一套叫Native.js的能力可以在JS里直接调用Android原生API。注册广播接收器用的就是这套能力。下面这段代码是完整的页面级实现export default { data() { return { barcode: , scanReceiver: null }; }, onLoad() { this.registerScan(); }, onUnload() { this.unregisterScan(); }, methods: { registerScan() { const mainActivity plus.android.runtimeMainActivity(); const self this; // 1. 创建广播接收器实例 this.scanReceiver plus.android.implements( io.dcloud.android.content.BroadcastReceiver, { onReceive: function(context, intent) { plus.android.importClass(intent); const action intent.getAction(); if (action com.yourcompany.scanbar) { const code intent.getStringExtra(barcode); if (code) { self.barcode code; self.handleScan(code); } } } } ); // 2. 创建IntentFilter并添加Action const IntentFilter plus.android.importClass(android.content.IntentFilter); const filter new IntentFilter(); filter.addAction(com.yourcompany.scanbar); // 3. 注册到主Activity mainActivity.registerReceiver(this.scanReceiver, filter); }, unregisterScan() { if (this.scanReceiver) { const mainActivity plus.android.runtimeMainActivity(); mainActivity.unregisterReceiver(this.scanReceiver); this.scanReceiver null; } }, handleScan(code) { uni.showToast({ title: 扫码成功 code, icon: none }); } } };拆开看每段的作用第一段plus.android.implements创建了一个BroadcastReceiver实例。第一个参数是DCloud封装好的接口类名必须写成io.dcloud.android.content.BroadcastReceiver很多人在这个大、小写和包名上栽过跟头错一个字符都不行。第二个参数是具体实现里面写onReceive回调。第二段onReceive里先调用plus.android.importClass(intent)。这一步很关键intent是Java对象不经过importClass你直接调intent.getAction()会报方法找不到。importClass的作用就是把Java对象的方法映射到JS环境里。第三段实例化IntentFilter通过addAction把你关心的Action加进去。注意顺序IntentFilter必须要在registerReceiver之前addAction顺序反了filter是空的什么都收不到。第四段把接收器注册到主Activity上。registerReceiver的第一个参数是刚才创建的receiver对象第二个参数是filter。2.3 注册了收不到先检查这三个位置写完之后如果发现扫了码没反应别急着改代码按这个顺序排查设备的输出方式是不是还在键盘模式。设备上配置的Action和代码IntentFilter里的Action是否完全一致。设备上配置的字段名和代码里getStringExtra用的字段名是否完全一致。我遇到过大量问题出在第三点——设备上配置的字段名叫scannerdata代码里用barcode去取拿到的一直是null怎么看代码都没错其实就是字段名没对上。字段名还要提醒一句有些设备广播里带的是一个byte数组而不是String这种情况intent.getStringExtra(barcode)会拿到null要改用intent.getByteArrayExtra(data)再转字符串。具体怎么处理后面速查表里我会专门列出来。2.4 页面生命周期管理注册与注销成对出现注册和注销必须成对出现这是我踩过最深的坑。PDA在连续扫件的时候广播是一条接一条的。如果你的页面已经关了接收器还挂在Activity上每次扫码都会触发一次JS回调。回调里如果有setTimeout、uni.showToast轻则控制台报错、页面栈错乱重则整个App卡死。还有一个更隐蔽的问题广播接收器持有页面实例的引用Vue组件无法被垃圾回收时间久了内存占用越来越高App会越来越卡。所以在onUnload里注销接收器是底线。如果扫码功能在很多页面都要用更合理的方案是把接收器提升到App.vue里用uni.$emit和uni.$on做页面通信这样就不用在每个页面里都重复注册注销了。3. 避坑指南广播监听里的深水区3.1 坑一广播Action对不上什么都收不到这是所有坑里出现频率最高的。设备扫描设置里的Action和代码里IntentFilter的Action必须完全一致——包括大小写、分隔符、点号差一个字符都收不到。很多人会漏掉一个细节有些设备的默认Action不是固定的系统升级、或者工厂重置之后扫描设置会被恢复成默认值。你的代码一行没改但设备配置变了广播瞬间就没了。解决办法有两个方向。第一代码里做成可配置的把Action和字段名放到一个配置对象里甚至存到uni.setStorageSync里方便现场调整。第二在设备上把Action改成自定义的固定值比如com.yourcompany.scanbar这样即使系统重置你只要重新配置一次就能恢复。3.2 坑二接收器被GC回收广播莫名消失这个问题比较诡异现象是页面刚加载完能收到广播扫了几次之后突然就收不到了App重进又恢复正常。如果你的接收器对象只是局部变量没有保存到页面data、实例属性或全局变量里JS引擎会在某次垃圾回收时把它回收掉。native层虽然还留着注册但JS回调已经没了广播自然就丢了。这也是为什么我前面代码里一定把receiver赋值给this.scanReceiver。这行的本质就是让JS侧持有一个强引用防止接收器被回收。全局注册的场景就把接收器放到getApp().globalData里道理一样。3.3 坑三重复注册与未注销回调乱套这个坑和坑三其实是两兄弟注册了没注销、重复注册了。有些同学在onShow里注册、onHide里忘了注销页面来回切换几次就注册了好几个接收器。结果扫一次码同一个页面重复触发了两次三次扫码回调重复提交数据单。防重复的写法和注销一样简单注册前先判断this.scanReceiver是否存在存在就先注销再重新注册。我习惯把registerScan写成幂等函数不管调用多少次最终只有一个是活的。3.4 坑四键盘模式没关监听白写这个坑我在2.1里提醒过但实际项目里仍然经常遇到。很多PDA出厂默认是键盘模拟模式也就是条码直接送到当前焦点输入框。你写好了广播监听的代码扫一下发现条码出现在某个输入框里而onReceive根本没触发就容易产生自我怀疑是代码不对还是广播机制不兼容都不是。先打开扫描设置看输出方式。只有输出方式选到了“广播”扫描服务才会发广播。如果你既需要页面显示条码又需要触发业务逻辑千万别依赖键盘模拟去“顺便”填充输入框那是最脆弱的做法换了页面结构就崩。3.5 坑五连续扫描不加防抖数据直接花仓库现场的扫描速度比想象中快得多。操作员双手各拿一件货左右手交替扫描一秒能扫好几次。如果每次扫码直接触发一次网络请求会有一个很现实的问题网络慢的时候上一次请求还没返回下一次扫码就来了数据就乱了。而且有些设备在长按扫描键的模式下会连续发多条广播条码内容可能相同、也可能不同。这种情况下建议在业务层做一个简单的防抖300毫秒内的重复条码直接忽略页面处于loading状态时扫码事件暂不处理。具体阈值根据现场实测来调扫码快就调小一点网络慢就调大一点。4. 常见问题排查与速查表4.1 问题速查对照表现象可能原因解决办法扫码后页面无任何反应设备输出方式不是广播模式打开扫描设置改为广播输出扫码无反应Action名不匹配对照设备配置修正代码里的ActiononReceive触发了但barcode为null字段名不对或数据是byte数组查看设备字段名改用getByteArrayExtra页面关闭后仍然触发回调未注销接收器onUnload里unregisterReceiver扫几次后忽然收不到广播接收器被GC回收用this或globalData持有接收器引用一次扫码触发多次回调重复注册了接收器注册前先注销保持幂等切后台再回来收不到广播部分系统限制后台广播保持App在前台或按厂商建议处理重启App后收不到广播设备配置被恢复默认重新设置Action或改成自定义固定值4.2 调试技巧把广播内容全量打印出来写PDA扫码最大的调试难题是黑盒。你不知道是设备没发广播还是广播发了但App没收到。我的建议是做一个调试用的小页面不写任何业务逻辑只注册一个接收器把收到的广播的Action和所有extra字段全部打印出来。onReceive: function(context, intent) { plus.android.importClass(intent); const bundle intent.getExtras(); let logs Action: intent.getAction() \n; if (bundle) { const keys bundle.keySet(); const it keys.iterator(); while (it.hasNext()) { const key it.next(); logs key bundle.get(key) \n; } } uni.showModal({ title: 广播内容, content: logs }); }这段代码能在一分钟之内帮你定位Action和字段名比翻设备文档快得多。核心原理就是遍历Intent携带的extra把key和value拼成字符串显示出来。如果某些机型上iterator调用报方法找不到可以对keys对象也做一次importClass再遍历不同固件的兼容性略有差异。4.3 场景扩展全局扫码与后台扫码如果你的场景是“在App任意页面都能扫码”就按我前面说的把接收器提升到App.vue里注册用uni.$emit把条码广播给所有监听页面// App.vue onLaunch 中 setupGlobalScan() { const mainActivity plus.android.runtimeMainActivity(); const receiver plus.android.implements( io.dcloud.android.content.BroadcastReceiver, { onReceive: function(context, intent) { plus.android.importClass(intent); const code intent.getStringExtra(barcode); if (code) { uni.$emit(scan-result, code); } } } ); const IntentFilter plus.android.importClass(android.content.IntentFilter); const filter new IntentFilter(); filter.addAction(com.yourcompany.scanbar); mainActivity.registerReceiver(receiver, filter); getApp().globalData.scanReceiver receiver; }页面里用uni.$on(scan-result, callback)监听onUnload里用uni.$off清理。注意清理时机避免页面销毁后回调还在执行。如果你的场景是“黑屏也能扫”或者“App在后台也要处理扫码”动态注册的广播接收器会有限制。东集这类设备的厂商建议做法是用服务包一层或者做成原生插件常驻。对uniapp项目来说建议分两步走先把动态注册的方案跑通验证业务可行性确实有后台需求了再考虑厂商SDK插件化用uni.requireNativePlugin封装。不要一上来就直接做原生插件成本高很多而且很多项目其实用不上后台扫码。我在这个项目里最大的体会是PDA扫码类需求看起来是个“接一下广播”的小功能真正折腾人的全在外围——设备的输出配置、Action的匹配、接收器的生命周期管理。这些坑不会出现在任何一份官方文档里只有拿着真机一条一条试出来。如果你也是第一次接触东集PDA建议先按2.2的代码把基础功能跑通然后务必把第3部分的坑在测试单上过一遍。尤其是注销和防抖这两条上了产线再改代价完全不一样。最后再分享一个小技巧把所有扫码相关的配置项Action、字段名、防抖时间集中放到一个配置文件里现场调试的时候改配置比改代码要省心得多。
返回列表