基于uniapp的PDA手持设备红外扫码方案

前言

在本篇文章中,可以学习到基于uniapp平台的移动端app应用,通过uts插件调用安卓sdk

背景

移动端最近有一个需求,需要使用PDA的红外扫码功能来识别一些二维码。识别成功后,再进行一些业务操作。因为公司移动端的技术用的是uniapp,没有找到合适的现成库或工具,于是决定自己造,顺带写了这篇文章。

名词解释

PDA设备

PDA的英文全称叫Personal Digital Assistant,现代意义上的PDA终端手持机是一种集成了数据采集、处理、传输功能的智能终端设备。 如下图就是一个在使用红外扫码功能的PDA手持设备。

UTS插件

uts,全称uni type script,统一、强类型、脚本语言,是uni提供的接入原生api、SDK、插件的DSL。

它会被编译为不同平台的编程语言,如:

  • web平台,编译为JavaScript
  • Android平台,编译为Kotlin
  • iOS平台,编译为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聚焦唤起键盘

目前有三种方案可以隐藏唤起键盘或者避免键盘被唤起。隐藏唤起键盘会带来无法避免的页面闪烁问题,所以避免键盘被唤起是最优解。

  1. uniapp提供了hideKeyboard方法用来隐藏键盘,hideKeyboard方法会带来页面闪烁问题。检索到有结合使用setInterval不断隐藏键盘的方案,但是测试结果依旧是会有页面闪烁的情况,不建议使用。如果有同学想了解该方案可以看看uniapp的官网

  2. 使用input组件的属性,来达到聚焦input组件时,不唤起键盘的效果。

    1. inputmode属性,可以完全屏蔽唤起键盘,但是会丢失PDA扫码结果。
    2. readonly属性,app端测试没有效果,无法屏蔽唤起键盘。(有资料说在小程序端有效果,这个我没有做测试,有兴趣的同学可以自己试试。)
  3. 通过原生插件或者uniapp的native.js来修改页面级(activity级别)的键盘唤起模式。(这个方案没有深入尝试,因为既然都需要编写原生相关代码。那使用PDA厂商的SDK,明显获得更好的用户体验,)

如何监听PDA手持设备的红外扫码功能

  1. 监听PDA的键盘事件,需要依赖uniapprender.js来获取addEventListener,监听键盘事件。
  2. 监听PDA手持设备红外扫码功能的广播。(可以询问适配的PDA厂商,有没有开放红外扫码的安卓公共广播。如果没有那就只能针对SDK进行一个二次开发)

隐藏input代理方案是一个不错的通用解决方案。但是隐藏键盘带来的页面闪烁问题,会带给用户较差的体验,所以最后的实现方案没有采用该方案。

PDA系统设置扫码广播解解

不同的PDA手持设备有着不同的设置方式,这里是一个其他同学的方案,想了解该方案的同学可以去看看。

需要针对使用的PDA设备进行独立配置红外扫码功能的系统设置,如果在客户体量大、数量多的情况下,会有大量PDA设置需求。会带来两个问题:

  • 第一,客户大概率是不愿意自己进行PDA设置,那么这个设置过程将消耗己方的大量人力。
  • 第二,不是谁谁来设置这些PDA手持设备,都没有办法保证每一台PDA都一定设置正确。

这个是无法接受的缺点,所以最终也没有采用该方案。

PDA厂商SDK方案

红外扫码作为PDA的原生能力,那么自然会对有需求的开发者提供相关的SDK。我们只需要调用这些SDK,就可以轻松完成红外扫码识别。

步骤拆解:

  1. 接入PDA手持设备的官方SDK。
  2. 在插件中完成,对红外扫码的监听。
  3. 封装相关业务组件,提供给项目使用。

具体实现

当前实现过程使用的是霍尼韦尔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插件支持以下三种类型文件:

  • jar
  • aar
  • so库

大致的目录结构如下

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>

最后

文中如有错误或不严谨的地方,请给予指正,十分感谢。

代码地址

红外扫码uts插件 github

相关推荐
风止何安啊1 小时前
别被 JS 骗了!终极指南:JS 类型转换真相大揭秘
前端·javascript·面试
chaffererdog1 小时前
uniapp开发微信小程序使用vk-uview-ui的uSearch搜索组件,在微信开发者工具中点击输入框会意外触发custom事件
微信小程序·小程序·uni-app
拉不动的猪1 小时前
深入理解 Vue keep-alive:缓存本质、触发条件与生命周期对比
前端·javascript·vue.js
|晴 天|1 小时前
WebAssembly:为前端插上性能的翅膀
前端·wasm
孟祥_成都1 小时前
你可能不知道 react 组件中受控和非受控的秘密!
前端
火车叼位1 小时前
ast-grep:结构化搜索与重构利器
前端
over6971 小时前
深入理解 JavaScript 原型链与继承机制:从 instanceof 到多种继承模式
前端·javascript·面试
烂不烂问厨房1 小时前
前端实现docx与pdf预览
前端·javascript·pdf
GDAL1 小时前
Vue3 Computed 深入讲解(聚焦 Vue3 特性)
前端·javascript·vue.js