ARTICLE DETAIL

资讯详情

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

uniapp项目中PDA激光扫码的广播监听实现与避坑指南

uniapp项目中PDA激光扫码的广播监听实现与避坑指南 做无线盘点、仓储出入库、生产追溯这类项目PDA扫码基本是刚需。如果你拿的是东集的PDA又偏偏用了uniapp这套跨端框架那么激光扫码怎么调这个问题大概率会让你在工位上坐一下午。uniapp官方只给了uni.scanCode但那是调摄像头扫码的PDA上的激光扫描头根本不走摄像头通道它走的是Android系统广播。只有把广播监听搞明白你的扫码功能才算真正落地。这篇内容就是我从踩坑到稳定复用的完整方案照着做5分钟内把东集PDA的激光扫码接到uniapp项目里附带的避坑指南能帮你省掉我当初浪费的那整个下午。1. 需求拆解与整体设计思路1.1 东集PDA激光扫码的工作原理先搞清楚一件事东集PDA上的激光扫码本质上是硬件模组在干活不是App在干活。你按下PDA侧面的物理扫描键激光头打出激光扫描引擎对条码解码然后系统把解码结果以广播Broadcast的形式发出来。你的App只需要做一个坐在收音机旁边听广播的角色监听到包含条码内容的广播之后把内容取出来用即可。这个机制有一个很关键的点扫码结果的广播内容是系统级Intent携带的。也就是说你的App和扫码引擎之间不是直接调用的关系而是订阅-发布的关系。既然走的是广播那你就要处理三件事监听的广播Action相当于电台频道是什么扫码结果存在Intent的哪个Extra字段里相当于广播里的具体内容什么时候注册监听、什么时候注销监听相当于什么时候开收音机、什么时候关东集PDA的广播Action和Extra字段在不同批次、不同固件版本上有过调整。常见的Action有android.intent.action.SCANRESULT也有com.android.scanner.service.RESULT这类自定义ActionExtra键名出现过value、barcode、scancode等。所以第一件事不是写代码而是去设备上确认当前这台机器的真实参数这个我在后面避坑指南里会展开讲。1.2 为什么选广播监听而不是原生插件接触过这个场景的人可能第一反应是去DCloud插件市场找个PDA扫码插件或者找东集厂商要SDK再走离线打包。这类方案不是不行但对很多人来说成本偏高。原生插件方案的问题在于插件市场里的PDA插件良莠不齐很多是面向指定厂商、指定型号适配的换一个型号就要重新找插件而且部分插件要求你走离线打包需要准备Android原生开发环境配置Android Studio、处理离线SDK、改原生工程这一套流程走下来就不是5分钟能搞定的了。而广播监听只需要用到uniapp自带的plus.android模块纯JS代码就能完成注册广播接收器的操作。云打包、离线打包都可以直接用不需要任何原生代码。换句话说工程量小、可维护性强、对前端同学友好。只要理解了订阅-发布这个模型你甚至可以把这个方案平移到其他品牌PDA上往后接新设备只改配置就行。1.3 一个可复用的架构设计我在封装这个扫码功能时没有直接在页面里写一大坨广播注册代码而是单独抽了一个工具模块。这样做的好处是当项目里有多个页面需要扫码时不需要复制粘贴哪里需要用就调哪里将来要适配其他PDA也只需要改工具模块里的Action和ExtraKey配置。模块的设计分三层配置层管理广播Action、扫描结果ExtraKey、扫码结果是否去重等配置项核心层负责注册和注销广播监听解析Intent里的扫码结果向上层抛数据业务层页面里调用拿到扫码结果后做一些业务处理比如查询商品、入库、出库后面给的代码就是按这个思路写的。先把模块搭好再往业务页里接思路会非常清晰。2. 5分钟快速接入核心代码实操2.1 环境准备与基础配置在写代码之前确认一下你的项目基础条件项目是uniapp创建的且运行目标为App端Androidmanifest.json里App模块配置中勾选了Android相关基础模块一般不需要额外勾选plus.android属于基础能力如果用云打包打包时选择Android包名、证书这些正常配置即可需要注意一个前置条件plus.android必须在plusready事件触发之后才能安全调用。所以在工具模块里要做一个初始化容错保证页面onLoad时即使plus还没完全就绪也不会控制台直接报错。另外建议开发阶段用真机调试并且打开USB调试。因为广播接收这类功能在HBuilderX内置浏览器里是跑不起来的只有在App环境里plus才是真实存在的。2.2 广播接收器的封装与注册先看核心代码。我把它整理成了一个可复用的工具模块放在utils/scan.js里// utils/scan.js // 设备广播配置不同型号、固件可能有差异以设备实际广播参数为准 const SCAN_ACTION android.intent.action.SCANRESULT; // 东集常见广播Action const SCAN_EXTRA_KEY value; // 东集常见扫码结果Extra键名 let mainActivity null; // 主Activity实例 let receiver null; // 广播接收器实例 let isRegistered false; // 是否已注册防止重复注册 let currentCallback null; // 当前业务回调 /** * 注册扫码广播监听 * param {Function} callback 扫码回调参数为扫码结果字符串 */ function startScan(callback) { if (typeof callback ! function) { console.error([Scan] callback must be a function); return; } currentCallback callback; // 防止在plus未就绪时调用 if (!window.plus) { console.error([Scan] plus is not ready); return; } if (isRegistered) { console.warn([Scan] already registered); return; } try { mainActivity plus.android.runtimeMainActivity(); const IntentFilter plus.android.importClass(android.content.IntentFilter); const filter new IntentFilter(); filter.addAction(SCAN_ACTION); receiver plus.android.implements(io.dcloud.android.content.BroadcastReceiver, { onReceive: function(context, intent) { try { if (intent intent.getAction() SCAN_ACTION) { const code intent.getStringExtra(SCAN_EXTRA_KEY); if (code) { currentCallback currentCallback(code); } } } catch (e) { console.error([Scan] onReceive error:, e); } } }); mainActivity.registerReceiver(receiver, filter); isRegistered true; console.log([Scan] receiver registered); } catch (e) { console.error([Scan] register error:, e); } } /** * 注销扫码广播监听 */ function stopScan() { if (!isRegistered || !mainActivity || !receiver) { return; } try { mainActivity.unregisterReceiver(receiver); isRegistered false; currentCallback null; receiver null; console.log([Scan] receiver unregistered); } catch (e) { console.error([Scan] unregister error:, e); } } module.exports { startScan, stopScan };这里有几个点要说明plus.android.implements是用来创建一个实现了指定接口的JS对象。这里实现的是io.dcloud.android.content.BroadcastReceiver这个类它是DCloud封装过的广播接收器接口。在onReceive方法里你能拿到context和intent两个对象。intent就是系统发出来的广播意图里面带着Action和数据。判断intent.getAction() SCAN_ACTION是为了过滤防止别的广播也被这个接收器拦截。虽然注册时已经指定了filter.addAction(SCAN_ACTION)但保险起见在回调里再做一次校验双重保险对真实项目来说很有用。获取扫码结果用的是intent.getStringExtra(SCAN_EXTRA_KEY)。这里的SCAN_EXTRA_KEY就是你配置的Extra键名对应设备广播出去时塞数据用的字段。不同设备差异很大比如有的PDA里配置的是barcode那你这里就要改成barcode。2.3 在页面生命周期中正确管理注册与注销封装好了接下来在业务页面里怎么用正确做法是页面onLoad时注册监听页面onUnload时注销监听。// pages/stock/in.vue import { startScan, stopScan } from /utils/scan.js; export default { data() { return { scanCode: }; }, onLoad() { // 注册广播监听扫码成功后自动回填到scanCode startScan((code) { this.scanCode code; this.handleScanResult(code); }); }, onUnload() { // 页面销毁前注销避免内存泄漏和重复回调 stopScan(); }, methods: { handleScanResult(code) { // 业务逻辑比如根据条码查询商品信息 console.log(扫描到条码, code); } } };需要注意一个场景如果页面在onHide时也要暂停扫码比如页面被弹窗或者跳转遮挡了你可以加一个onHide里调stopScan()、onShow里重新调startScan()的逻辑。这个要结合具体业务判断不是所有页面都必须这么做。如果业务是扫码结果必须实时响应页面只要可见就要能扫那onShow和onHide的配对管理就很有必要。2.4 数据解析与业务回调有的朋友可能会问广播里拿到的字符串一定就是纯条码内容吗不一定。部分PDA在广播配置里可以设置前缀、后缀、回车换行等。比如设备默认配置了扫码结果后自动追加一个回车符那么getStringExtra出来的值可能末尾就带着\n或者\r。所以我在工具模块里通常会再加一层清洗function normalizeCode(rawCode) { if (!rawCode) return ; return String(rawCode).replace(/[\r\n\t]/g, ).trim(); }这样在回调里拿到的就是干净的条码内容。后缀带回车对某些场景影响不大但如果你要拿条码去数据库里精确匹配末尾多了一个不可见字符排查起来非常痛苦。这个细节我建议一定提前处理。3. 广播监听避坑指南这一节是全文重点我把踩过的坑和帮别人排查时遇到的典型问题全部列出来。3.1 避坑一广播Action和Extra键名完全对不上这是我遇到最多的坑没有之一。有人拿了一段网上的代码Action写的是东集某个型号的Extra键名写的是另一个型号的结果在真机上怎么都收不到广播。收不到广播的时候第一反应不要是代码写错了而要先确认设备的广播参数。东集PDA会在设备系统设置里提供扫码输出方式的配置入口路径大致是【设置】→【扫描】→【扫描输出】→【广播输出】里面可以查看或修改广播的Action名和Extra键名。不同型号、不同固件可能路径略有出入但大方向都在扫描设置这个功能组里。确认参数的方式有两条路第一在设备设置里仔细翻找到广播输出配置页把Action名和Extra键名抄下来和代码里对一下第二用Intent拦截工具抓一次广播。Android开发者工具里有个叫Intent Logger的APK装在PDA上打开监听然后手动按扫描键扫一个条码它会把当前应用能收到的所有广播Action以及Intent里的所有Extra键值打出来非常直观我个人的建议是两种方式结合。因为光看设置页的配置项有时候系统还会在广播里额外补贴一些字段比如扫描时间、扫码类型等。用Intent Logger抓一次你能一眼看到真实Extra键名比自己猜靠谱得多。3.2 避坑二plusready之前调用plus.android直接报错uniapp页面生命周期里onLoad并不保证plus已经准备好。如果直接在onLoad里调用plus.android.runtimeMainActivity()在冷启动场景下是可能报plus is not defined或者plus.android is undefined的。解决办法有几个用uni.getSystemInfoSync()这类API先触发一次确认运行时环境但这个方法对plus初始化没有直接帮助在onLoad里通过plusready事件等待onLoad() { if (window.plus) { this.initScan(); } else { document.addEventListener(plusready, this.initScan, false); } }我封装工具模块时在startScan里已经做了window.plus的判空。但更稳妥的方式还是在页面里主动等待plusready。这样既不会报错也能确保监听注册成功。不过要注意plusready事件添加后要在合适的时机移除避免重复触发。实际项目里如果只注册一次影响不大但如果页面反复进出建议在onUnload里把监听摘掉。3.3 避坑三广播生命周期管不好页面关了还在收这个坑在真实项目里会导致一个很诡异的bug从A页面进入B页面B页面也监听了扫码结果扫一次码A页面和B页面的回调同时触发更严重的情况是页面已经销毁了回调还在执行导致setData的组件状态异常甚至白屏。原因很简单registerReceiver注册的广播接收器是全局性的你不主动unregisterReceiver它就一直驻留在系统里。uniapp页面销毁的时候并不会自动帮你注销。所以务必在页面销毁或离开时调用stopScan()。另外还要注意如果同一个页面注册了多次比如onShow里调用了一次startScan但上一次没有注销系统里就会挂多个接收器实例扫一次码回调N次。我在工具模块里用isRegistered做了防重但更根本的办法是严格配对startScan和stopScan成对出现别只管注册不管注销。3.4 避坑四高速连扫场景下的丢帧与重复仓储场景里操作员经常是连续扫码一秒钟扫好几下。这种高速场景下有几个现象现象一扫得飞快时偶尔漏掉一两条码现象二某一条码重复回调多次丢帧的本质是广播处理跟不上。理论上广播事件是排队分发的但如果你的onReceive回调里做了耗时操作比如直接发网络请求、操作数据库那么后续广播的事件分发就会变慢极端情况下系统还会丢弃广播。解决办法是把业务逻辑和广播逻辑分离。onReceive里只做最轻量的事把条码内容提取出来通过回调抛给页面页面里用setTimeout或者放到下一个事件循环里处理网络请求。如果业务上允许可以再加一个队列把扫码结果放到数组里批量处理。重复回调则和去重有关。场景上PDA的扫描键如果被长按会进入连扫模式同一时间会解出多帧图像每一帧都可能触发广播或者设备配置文件里开启了连续扫码模式。业务层做去重的话一般用内容时间窗口规定几百毫秒内相同条码只处理一次。这样既能防重复又不影响连续扫不同码的场景。3.5 避坑五广播模式与键盘模拟模式只能二选一在设备设置里东集PDA的扫码输出方式通常有几种广播输出、键盘模拟USB键盘/蓝牙键盘、Intent输出、剪贴板输出等。很多朋友配置的时候会发现设备设置里如果勾选了广播输出那么键盘模拟就同时失效了或者反过来键盘模式下广播收不到。这个不算bug而是设备本身的设计约束。扫码引擎解码后只能把结果投递到一个输出通道。有的设备虽然允许同时输出到多个通道但实际测试下来并不可靠。所以你在调广播监听之前务必确认设备的输出方式已经切到广播输出不要又开键盘输出又开广播输出否则广播时灵时不灵定位问题都很费劲。3.6 避坑六安卓高版本对隐式广播的限制从Android 8.0API 26开始系统对隐式广播做了限制很多隐式广播不再允许在Manifest里静态注册只能动态注册。我们的扫码广播场景里用的是动态注册倒是不受影响。需要注意的是一些国内定制ROM还有其他限制。比如部分机器在锁屏状态下、或者App处于后台被省电策略杀掉时广播接收器会失效。所以如果你的业务要求App在后台也能接收扫码并处理那就得关心一下App是否被系统杀了、前台服务是否保活、是否需要申请电池白名单。这块是PDA行业的老话题了。简单说如果是纯内网、App一直是前台使用的场景这类问题少见如果是需要后台自动扫描、自动上送的场景一定要提前测试锁屏和后台运行的表现。4. 常见问题与排错实录把这些问题汇总成一个排查速查表方便你在现场快速定位症状可能原因排查方向完全收不到广播广播Action配置不对在设备设置/Intent Logger里确认真实Action收到广播但取不到条码值Extra键名不对确认设备广播输出配置中的Extra字段名页面来回切换后扫码回调多次广播重复注册未注销检查onLoad/onShow注册与onUnload/onHide注销是否配对扫速很快时会漏码onReceive里有耗时操作把业务逻辑移出广播回调只保留解析和抛出同一条码回调多次连扫模式或未做去重时间窗口内容去重设置广播模式后键盘没法用输出通道冲突在设备设置中关闭键盘输出保持广播输出App在后台收不到广播进程被杀或省电策略申请白名单、保持前台服务按业务需求决定扫码结果末尾带换行/空格设备后缀配置在工具模块里做字符串清洗4.1 现场排查广播参数的两条实战路子如果你手头没有厂商SDK文档又想知道这台PDA发出来的广播到底是啥可以按下面两种方式来。第一种用设备自带的测试模式。部分东集PDA系统里有一个扫描测试或者工厂测试入口打开后可以直接显示扫描结果但不显示广播参数。这个能帮你确认扫码硬件本身是好的。第二种自己写一个临时调试页面注册广播后把intent.getAction()和所有的intent.getExtras()都打出来。这个思路和Intent Logger类似但好处是不用装额外APK直接在uniapp项目里临时加一个调试页即可。onReceive: function(context, intent) { // 临时调试打印所有Action和数据 console.log(action:, intent.getAction()); const bundle intent.getExtras(); if (bundle) { const keys bundle.keySet(); const it keys.iterator(); while (it.hasNext()) { const key it.next(); const val bundle.get(key); console.log(extra key:, key, , value:, val); } } }注意bundle.keySet()返回的是一个Java集合在uniapp的plus.android桥接环境下遍历方式可能有限。如果打印不完整可以尝试直接用intent.getStringExtra(value)、intent.getStringExtra(barcode)、intent.getStringExtra(scancode)这几个常见键名逐一测试哪个能打出条码就说明哪个是对的。4.2 云打包后广播失效先查这两处有人遇到过这种问题本地基座调试好好的云打包出来装到PDA上就收不到广播了。这种情况通常和云打包配置无关但需要重点排查两个点第一manifest.json里的包名。如果调试基座和正式包的包名不一致而设备端广播配置是针对包名做过滤的那换包名后就收不到了。需要去设备设置里确认广播输出是否有包名限制。第二App权限。虽然广播监听本身不需要特殊权限但部分PDA固件要求App获得后台弹出界面前台服务读取设备信息等权限才能正常运行。如果你的App没有申请这些权限可能导致广播接收被系统屏蔽。先到设置-应用管理里把App的权限全部打开再测试一次。4.3 onReceive里到底该不该做耗时操作说一个我自己的习惯广播回调里绝对不做网络请求。之前有一次我在onReceive里直接调了业务接口理论上很快但扫码频率一高接口响应慢广播回调就积压内存占用飙升最后PDA直接卡死重启。后来我把扫码结果先扔到一个数组里再通过setTimeout分批从数组里取数据发请求问题瞬间解决。如果你用的是Vue页面还要注意onReceive回调里的this指向问题。在plus.android.implements的闭包里this已经不是页面的Vue实例了。所以我通常不直接在onReceive里操作页面数据而是把扫码结果通过工具模块的回调传出去。回调在哪定义this就指向哪这样Vue实例的setData才有意义。5. 扩展与个人经验5.1 一套代码适配多品牌PDA的思路广播监听方案最爽的地方在于可迁移。同样是Android系统不同品牌PDA的扫码广播机制其实大同小异差别主要集中在Action名、Extra键名、扫码模式配置上。只要你在工具模块里把配置独立出来换一台设备的时候改配置就行核心代码完全不用动。我见过有的项目为了适配多个厂商专门搞了一个配置文件const DEVICE_PROFILES { dongji: { scanAction: android.intent.action.SCANRESULT, scanExtraKey: value }, huoNiWeiEr: { scanAction: com.honeywell.decode.intent.action.RESULT, scanExtraKey: barcode_string }, youBoXun: { scanAction: android.intent.action.SCANRESULT, scanExtraKey: data } };然后再根据当前PDA的品牌、型号去选择对应的配置。如果你只用一个品牌那就没必要做这么重但这种思路确实能帮你把一个项目复用到不同业务现场。5.2 扫码体验的细节优化广播监听调通了只是起点。真正的好用还在细节里。第一点是扫码成功震动和提示音。东集PDA通常自带扫描成功提示但如果设备被设置成静音或震动关闭了操作员就感受不到反馈。可以在工具模块里封装一个扫码成功反馈函数用plus.android调系统震动或者播放一段短音频。第二点是连续扫码的节奏控制。仓储场景里操作员不会盯着屏幕看他更关心有没有扫上和扫的是什么。所以扫码后建议把页面上的输入框焦点、扫到的条码内容、关联的商品信息一起展示出来让操作员扫一眼就能确认。如果要连续扫多个建议扫码成功后自动清空输入框进入下一次扫描的待命状态。第三点是和业务系统的交互。扫码本身不产生价值扫码之后的数据流转才有价值。所以封装的时候不要只想着拿到码要想着拿到码之后要做什么。是查询库存是记录入库是比对订单在架构上把扫码和业务解耦你会发现这套代码在不同的业务项目里都能用。5.3 我踩过的最贵的一个坑最后分享一个实实在在被坑过的案例。有一次项目上线现场用的是东集某型号PDA我在开发环境测试时怎么扫都正常结果发到客户现场有一部分机器就是收不到广播。远程排查了半天最后发现是那批PDA的系统设置里扫码输出方式被上一任实施人员改成了Intent输出而不是广播输出。操作员拿到的机器设置五花八门App代码再对也没有用。所以我现在每次去现场部署都会做两件事一是给PDA管理员写一份设备设置清单把扫码输出方式、广播Action、Extra键名、是否需要回车后缀这些配置项全部列清楚二是在App里加一个扫码自检页面进去之后直接显示当前监听的Action和ExtraKey以及最近一次收到的扫码结果。现场签名错误时打开自检页一扫问题出在设备还是出在App一目了然。这大概就是广播监听方案最值得注意的一点代码只占一半另一半是对设备配置的理解和应用现场的应变。把这边的经验沉淀成文档和工具比你临时写多少行代码都管用。
返回列表