前言
在本篇文章中,可以学习到基于uniapp平台的移动端app应用,通过uts插件调用安卓sdk。
背景
移动端最近有一个需求,需要使用PDA的红外扫码功能来识别一些二维码。识别成功后,再进行一些业务操作。因为公司移动端的技术用的是uniapp,没有找到合适的现成库或工具,于是决定自己造,顺带写了这篇文章。
名词解释
PDA设备
PDA的英文全称叫Personal Digital Assistant,现代意义上的PDA终端手持机是一种集成了数据采集、处理、传输功能的智能终端设备。 如下图就是一个在使用红外扫码功能的PDA手持设备。

UTS插件
uts,全称uni type script,统一、强类型、脚本语言,是uni提供的接入原生api、SDK、插件的DSL。
它会被编译为不同平台的编程语言,如:
web平台,编译为JavaScriptAndroid平台,编译为KotliniOS平台,编译为Swift(HX 3.6.7+ 版本支持)harmonyOS平台,编译为ArkTS(HX 4.22+ 版本支持)在现有架构下,ArkTS和JS在同一环境下执行,不涉及通讯等问题。
uts插件,指利用uts语法,操作原生的API(包括手机os的api或三方sdk),并封装成一个uni_modules插件,供前端调用。
如果有想要详细了解的同学可以看看官网介绍:Uniapp UTS插件。
需求分析
本文最终采用的是PDA厂商SDK方案,使用UTS插件接入SDK,最后封装成业务组件。(不关心方案选型,想直接看PDA厂商SDK接入的同学,可以点击目录【具体实现】跳转)
方案分析
| 方案 | 优点 | 缺点 |
|---|---|---|
| 隐藏input代理 | 1. 通用方案,不需要针对不同的PDA设备编写独立代码。 2. 开发方案熟悉,基本还是基于web开发的思维。 | 1. 移动端input聚焦的时候会唤起键盘,关闭键盘方案不够好的情况下会导致页面闪烁。 2. 需要针对处理不同厂商(甚至是同一厂商不同型号)设备的红外扫码按键监听事件。 |
| PDA系统设置扫码广播 | 1. 用户体验好,效果PDA厂商SDK方案没有差别。 2. 代码量小,开发简单。 | 1. 需要针对每一台PDA手持设备进行特殊设置,客户体量大的情况下配置PDA手持设备极度麻烦。 2. 极度依赖PDA厂商的系统设置。 |
| PDA厂商SDK方案 | 1. 用户体验好,完全静默的红外扫码,依靠消息订阅实现。 2. 不需要引入额外的js库。 | 1. 针对不同的PDA品牌,需要额外特殊处理。 2. 较前两个方案复杂,原生SDK接入不是web开发的舒适区。 |
隐藏input代理方案
如果要使用隐藏input代理方案,有两个必须要面对的问题:
- 第一个是input聚焦时会唤起键盘
- 第二个是需要监听PDA手持设备的红外扫码功能的开始与结束。
开始具体分析这两个问题。
如何避免input聚焦唤起键盘
目前有三种方案可以隐藏唤起键盘或者避免键盘被唤起。隐藏唤起键盘会带来无法避免的页面闪烁问题,所以避免键盘被唤起是最优解。
-
uniapp提供了
hideKeyboard方法用来隐藏键盘,hideKeyboard方法会带来页面闪烁问题。检索到有结合使用setInterval不断隐藏键盘的方案,但是测试结果依旧是会有页面闪烁的情况,不建议使用。如果有同学想了解该方案可以看看uniapp的官网。 -
使用
input组件的属性,来达到聚焦input组件时,不唤起键盘的效果。inputmode属性,可以完全屏蔽唤起键盘,但是会丢失PDA扫码结果。readonly属性,app端测试没有效果,无法屏蔽唤起键盘。(有资料说在小程序端有效果,这个我没有做测试,有兴趣的同学可以自己试试。)
-
通过原生插件或者uniapp的native.js来修改页面级(activity级别)的键盘唤起模式。(这个方案没有深入尝试,因为既然都需要编写原生相关代码。那使用PDA厂商的SDK,明显获得更好的用户体验,)
如何监听PDA手持设备的红外扫码功能
- 监听PDA的键盘事件,需要依赖
uniapp的render.js来获取addEventListener,监听键盘事件。 - 监听PDA手持设备红外扫码功能的广播。(可以询问适配的PDA厂商,有没有开放红外扫码的安卓公共广播。如果没有那就只能针对SDK进行一个二次开发)
隐藏input代理方案是一个不错的通用解决方案。但是隐藏键盘带来的页面闪烁问题,会带给用户较差的体验,所以最后的实现方案没有采用该方案。
PDA系统设置扫码广播解解
不同的PDA手持设备有着不同的设置方式,这里是一个其他同学的方案,想了解该方案的同学可以去看看。
需要针对使用的PDA设备进行独立配置红外扫码功能的系统设置,如果在客户体量大、数量多的情况下,会有大量PDA设置需求。会带来两个问题:
- 第一,客户大概率是不愿意自己进行PDA设置,那么这个设置过程将消耗己方的大量人力。
- 第二,不是谁谁来设置这些PDA手持设备,都没有办法保证每一台PDA都一定设置正确。
这个是无法接受的缺点,所以最终也没有采用该方案。
PDA厂商SDK方案
红外扫码作为PDA的原生能力,那么自然会对有需求的开发者提供相关的SDK。我们只需要调用这些SDK,就可以轻松完成红外扫码识别。
步骤拆解:
- 接入PDA手持设备的官方SDK。
- 在插件中完成,对红外扫码的监听。
- 封装相关业务组件,提供给项目使用。
具体实现
当前实现过程使用的是霍尼韦尔PDA设备,接下来的实现过程将以该品牌提供的SDK作为例子。大家在自己开发、接入SDK时,需要向适配的PDA厂商索要红外扫码SDK的相关文档、示例。
如何获取SDK
获取sdk有多种途径,这个同学们可以询问一下公司的采购,或者向上级反映情况。
- 可以去各个品牌的官网下载。
- 找购买渠道的渠道商要相关型号品牌的sdk。
了解SDK
大致有两个比较重要类需要我们在编写代码前了解一下:
AidcManager这个是扫码模块的管理类。
java
// 红外扫码模块的管理类
public final class AidcManager extends java.lang.Object {
/**
* 这个接口提供了AidcManager创建成功的回调方法。
*/
public static interface AidcManager.CreatedCallback {
/**
* 需要我们未来实现的方法,实现红外扫码功能。
* aidcManager是红外扫码管理实例
*/
void onCreated(AidcManager aidcManager)
}
/*
* 创建红外扫码管理器的静态类,需用通过该方法创建AidcManager的实例。
* context 是安卓引用的上下文对象
* callback 将在这个类里面实现红外扫码的基本功能。
*
* 我们不关心其内部实现
*/
public static void create(Context context, AidcManager.CreatedCallback callback)
/**
* 用于创建红外扫码读取器对象
* 不需要关心其内部实现。
*/
public BarcodeReader createBarcodeReader()
// 关闭红外扫码服务的连接。
public void close()
}
BarcodeReader这个是红外扫码的读取器类。
java
// 红外扫码的读取器类
public final class BarcodeReader extends java.lang.Object {
/**
* 红外扫码事件的接口,包含红外扫码事件的回调事件
* 红外扫码的成功响应回调方法
* 红外扫码的失败响应回调方法
*/
public static interface BarcodeReader.BarcodeListener extends java.util.EventListener {
// 红外扫码的成功响应回调方法
void onBarcodeEvent(BarcodeReadEvent event)
// 红外扫码的失败响应回调方法
void onFailureEvent(BarcodeFailureEvent event)
}
// 红外扫码读取器 绑定响应事件回调
public void addBarcodeListener(BarcodeReader.BarcodeListener listener)
// 红外扫码读取器 移除响应事件回调
public void removeBarcodeListener(BarcodeReader.BarcodeListener listener)
// 红外扫码读取器 开始响应扫码结果的事件
public void claim()
// 红外扫码读取器 停止响应扫码结果的事件
public void release()
// 销毁红外扫码读取器
public void close()
}
创建uts插件
创建uts插件有两种方法:
- 第一种,是手动创建
uts插件的目录结构(不建议,容易有遗漏)。 - 第二种如下图,依赖
HBuilderX。右键uni_modules文件夹,点击新建uni_modules插件。选择第三项uts插件-api插件。

uts插件目录
PDA厂商提供的sdk放置在utssdk/app-android/libs文件夹下就可以,uts插件支持以下三种类型文件:
jaraarso库
大致的目录结构如下
csharp
├─static // 静态资源
├─utssdk // 插件主要功能的代码目录
│ ├─app-android //Android平台目录
│ │ ├─assets //Android原生assets资源目录,可选
│ │ ├─libs //Android原生库目录,可选
│ │ ├─res //Android原生res资源目录,可选
│ │ └─index.uts //需要我们实现的插件功能入口,必须
│ ├─app-ios //iOS平台目录 不在本文的讨论范围内。
│ ├─interface.uts // 声明插件对外暴露的API,必需
│ └─unierror.uts // 定义插件对外暴露的错误信息,可选
├─changelog.md // 说明文件
├─readme.md // 说明文件
└─package.json // 插件清单文件,必需
实现uts插件
在utssdk/app-android/目录下新建barcode.uts文件,用于实现红外扫码的AidcManager.CreatedCallback接口,完成以下功能:
- 完成
aidcManager实例的绑定。 - 创建红外扫码读取器实例。
- 给
红外扫码读取器实例绑定响应事件。 - 开启
红外扫码读取器实例对红外扫码结果的响应 - 销毁红外扫扫码管理实例、读取器实例
typescript
export class BarcodeManager implements CreatedCallback {
/** 扫码模块的管理实例 */
manager: AidcManager | null = null
/** 扫码的读取器实例 */
reader: BarcodeReader | null = null
/** 扫码的监听器实例 */
listener: BarcodeReader.BarcodeListener | null
// 绑定红外扫码读取器的监听器实例
constructor(listener: BarcodeReader.BarcodeListener) {
this.listener = listener
}
/** 扫码模块的初始化完成后,绑定红外扫码监听事件, */
override onCreated(aidcManager: AidcManager) {
// 绑定红外扫码模块实例
this.manager = aidcManager
if (this.manager !== null) {
// 创建读取器
this.reader = this.manager?.createBarcodeReader()
if (this.reader !== null) {
try {
// 扫码读取器的一些配置
this.reader?.setProperty(BarcodeReader.PROPERTY_CODE_128_ENABLED, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_GS1_128_ENABLED, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_QR_CODE_ENABLED, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_CODE_39_ENABLED, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_DATAMATRIX_ENABLED, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_UPC_A_ENABLE, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_EAN_13_ENABLED, false)
this.reader?.setProperty(BarcodeReader.PROPERTY_AZTEC_ENABLED, false)
this.reader?.setProperty(BarcodeReader.PROPERTY_CODABAR_ENABLED, false)
this.reader?.setProperty(BarcodeReader.PROPERTY_INTERLEAVED_25_ENABLED, false)
this.reader?.setProperty(BarcodeReader.PROPERTY_PDF_417_ENABLED, false)
this.reader?.setProperty(BarcodeReader.PROPERTY_CODE_39_MAXIMUM_LENGTH, 10)
this.reader?.setProperty(BarcodeReader.PROPERTY_CENTER_DECODE, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_NOTIFICATION_BAD_READ_ENABLED, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_INTERLEAVED_25_ENABLED, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_INTERLEAVED_25_REDUNDANCY_MODE, 10)
this.reader?.setProperty(BarcodeReader.PROPERTY_LINEAR_VOID_HANDLING, false)
this.reader?.setProperty(BarcodeReader.PROPERTY_DATA_PROCESSOR_LAUNCH_BROWSER, false)
// ocr设置
const comreg: string =
'[{"enabled":true,"key":"chanum","regexValue":"[A-Z a-z]{2}\\d{3}","type":"CUSTOMIZED"},' +
'{"enabled":true,"key":"OCR_CONTENT_REGEX_IP_ADDRESS","type":"EMBEDDED"}]'
this.reader?.setProperty(BarcodeReader.PROPERTY_OCR_ENABLED, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_OCR_EXCLUSIVE, true)
this.reader?.setProperty(BarcodeReader.PROPERTY_OCR_CONTENT_REGEX_SEQUENCE, comreg)
// 按键触发模式
this.reader?.setProperty(
BarcodeReader.PROPERTY_TRIGGER_CONTROL_MODE,
BarcodeReader.TRIGGER_CONTROL_MODE_AUTO_CONTROL
)
} catch (e) {
console.error(`${this} Failed to apply properties`)
}
// 注册响应事件
this.reader?.addBarcodeListener(this.listener)
// 读取器开启红外扫码结果响应
this.reader?.claim();
}
}
}
// 提供红外扫码管理模块的注销方法
destroyManager() {
// 注销读取器实例
if (this.reader != null) {
// 停止读取器的响应
this.reader?.release();
// 注销监听事件
this.reader?.removeBarcodeListener(this.listener)
this.listener = null
// 关闭扫码读取器
this.reader?.close()
this.reader = null
}
// 注销红外扫码管理模块
if (this.manager != null) {
// 关闭红外扫码模块连接
this.manager?.close()
this.manager = null
}
}
}
在utssdk/app-android/目录下新建listener.uts文件,实现BarcodeReader.BarcodeListener接口,给读取器实例绑定不同的响应事件。
typescript
export class Listener implements BarcodeReader.BarcodeListener {
onScanBarcode: (barcode: string) => void
onScanFail: () => void
// 绑定外部的红外扫码结果响应事件
constructor(onScanBarcode: (barcode: string) => void, onScanFail: () => void) {
this.onScanBarcode = onScanBarcode
this.onScanFail = onScanFail
}
// 扫码成功回调事件方法
override onBarcodeEvent(event: BarcodeReadEvent) {
const barcode = event.getBarcodeData()
this.onScanBarcode(barcode)
}
// 扫码失败的回调事件方法
override onFailureEvent(event: BarcodeFailureEvent) {
this.onScanFail()
}
}
最后实现入口文件index.uts,这个文件给uniapp组件、页面提供了可调用的方法:
initSilenceScan:用于初始化红外扫码对象destroySilenceScan:用于销毁红外扫码对象,释放内存
typescript
// 扫码模块实例
let barcodeManager: BarcodeManager | null = null
// 初始化红外扫码相关功能
@UTSJS.keepAlive
export function initSilenceScan ({ onScanBarcode, onScanFail }: InitSilenceScanOpt) {
// 判断红外扫码模块是否存在,如果存在则不重复创建。
if (barcodeManager?.manager == null) {
// 创建红外扫码响应事件实例
const listener = new Listener(onScanBarcode, onScanFail)
// 创建红外扫码功能模块
const barcodeManager = new BarcodeManager(listener)
// 获取安卓app的上下文对象
const context = UTSAndroid.getAppContext()
// 调用AidcManager.create抽象方法,创建红外扫码管理实例
AidcManager.create(context, barcodeManager)
}
}
export function destroySilenceScan() {
// 销毁红外扫码模块
barcodeManager?.destroyManager()
}
封装红外扫码业务组件SilenceScan。后续需要使用红外扫码的页面组件,只需要引入SilenceScan组件,即可丝滑享受红外扫码功能。
typescript
<script setup lang="ts">
import * as SilenceScan from '@/uni_modules/silence-scan'
import { onHide, onShow } from '@dcloudio/uni-app'
import { onBeforeMount, onBeforeUnmount, onUnmounted } from 'vue'
/** 对外抛出的ScanCode事件 */
const emit = defineEmits<{ scanCode: [code: string] }>()
/** 扫码成功事件 */
const onSilenceScanCode = (code: string) => {
emit('scanCode', code)
}
/** 扫码失败事件 */
const onScanFail = () => {
uni.showToast({ icon: 'none', title: '请检查扫描的条码、二维码' })
}
// 组件所在页面显示,初始化扫码对象
onShow(() => {
SilenceScan.initSilenceScan({ onScanBarcode: onSilenceScanCode, onScanFail })
})
// 当前组件所在页面隐藏,销毁扫码。
onHide(() => {
SilenceScan.destroySilenceScan()
})
// 当前组件销毁,销毁扫码对象
onUnmounted(() => {
SilenceScan.destroySilenceScan()
})
</script>
<template>
<view />
</template>
<style scoped></style>
最后
文中如有错误或不严谨的地方,请给予指正,十分感谢。