更新记录
1.1.0(2026-09-11)
1.1.0(2026-09-08)
- 适配普通 uni-app(vue3)项目,与 uni-app x 项目共用一套 API(通过条件编译自动区分按键监听方案)
- 新增
requestPermissionAPI:封装存储权限申请,Android 11+ 自动引导"所有文件访问"授权页 - 新增
unlockBarcodeKey/lockBarcodeKeyAPI:切换把枪按键归属,避免盘点与系统 iScan 扫码同时触发 - 修复:SDK 实例创建移至主线程执行,解决非 Looper 线程初始化抛出
Can't create handler导致初始化失败的问题 - 优化:初始化、盘点各失败分支输出日志(logcat 过滤
UHFNative),便于真机排查
1.0.0(2026-09-07)
- 首次发布
- 支持 UHF 模块初始化 / 释放、功率设置、标签盘点、标签回调、硬件把枪按键监听
- 支持模块型号:UM / SLR / RM / GX / YRM
平台兼容性
uni-app(5.14)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | √ | - | - | - | - | √ | - | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| - | - | - | - | - | - | - | - | - | - | - | - |
uni-app x(5.14)
| Chrome | Safari | Android | iOS | 鸿蒙 | 微信小程序 |
|---|---|---|---|---|---|
| - | - | √ | - | - | - |
uhf-rfid-scanner
iData 手持机 UHF RFID 盘点插件(Android 原生 UTS 插件),基于 iData UHF SDK(UHFJar V1.4.06)封装,一套 API 同时适配 uni-app x 与 uni-app(vue3) 项目。
功能特性
- UHF 模块初始化 / 释放,支持 UM / SLR / RM / GX / YRM 五种模块型号
- 标签盘点(开始 / 停止),通过回调实时返回 EPC、RSSI、DATA、EXTRA 数据
- 发射功率设置
- 硬件把枪按键监听(按键触发盘点,拦截系统 iScan 扫码服务)
- 把枪按键归属切换(盘点期间禁用扫码,退出后自动恢复系统扫码)
- 存储权限申请封装(含 Android 11+ "所有文件访问" 权限引导)
- SDK 实例创建自动调度至主线程(规避 Looper 线程限制),调用方无需关心线程问题
平台兼容性
| 平台 | 支持情况 |
|---|---|
| App(Android) | √(Android 6.0+,仅支持 iData 系列 UHF 手持机) |
| App(iOS) | x |
| App(Harmony) | x |
| Web / 各小程序 | x |
| HBuilderX 最低版本 | 4.25 |
|---|
本插件依赖 iData 设备 UHF 硬件,在其他品牌设备上无法完成初始化。
插件目录结构
uni_modules/uhf-rfid-scanner
├── readme.md 插件文档(本文件)
├── changelog.md 更新日志
├── package.json 插件配置
└── utssdk
├── interface.uts 统一接口定义(API 类型、数据结构、错误码)
├── unierror.uts 错误码定义
└── app-android Android 平台实现
├── index.uts 平台胶水层(双端适配入口)
├── config.json abi(armeabi-v7a / arm64-v8a)、minSdkVersion 23
├── UHFNative.kt 原生混编实现(Kotlin)
└── libs
└── UHFJar_V1.4.06.aar iData UHF SDK
快速开始
1. 配置权限(必须)
本插件依赖存储权限完成 UHF 模块串口初始化,需在项目 manifest.json 源码视图中声明:
uni-app x 项目(app-android 节点):
"app-android": {
"distribute": {
"permissions": [
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"/>"
]
}
}
uni-app 项目(app-plus 节点):
"app-plus": {
"distribute": {
"android": {
"permissions": [
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"/>"
]
}
}
}
2. 制作自定义基座
本插件为 UTS 原生插件,标准基座无法运行,请通过 HBuilderX「运行 → 运行到手机或模拟器 → 制作自定义调试基座」打包。
- 首次打包耗时较长(需编译原生 Kotlin 代码)属正常现象
- 修改 manifest 权限或插件原生代码后,必须重新制作基座;若 HBuilderX 提示"基座已是最新版本"但代码未生效,请修改
manifest.json中的versionCode强制更新
3. 导入插件
// uni-app x(.uvue,注意 UTS 类型)
import { initialize, onTag, startInventory } from "@/uni_modules/uhf-rfid-scanner";
// uni-app(.vue,普通 JS)
import { initialize, onTag, startInventory } from '@/uni_modules/uhf-rfid-scanner';
仅支持导入插件根目录,不支持
@/uni_modules/uhf-rfid-scanner/index.uts这类内部路径。
API 参考
initialize(options) : boolean
初始化 UHF 模块并上电。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| options.moduleType | UHFModuleType | 是 | 模块型号:"UM_MODULE" | "SLR_MODULE" | "RM_MODULE" | "GX_MODULE" | "YRM_MODULE" |
| options.power | number | 否 | 初始化后设置的功率,如 30 |
- 返回
true表示初始化成功;失败时可通过adb logcat -s UHFNative查看具体原因 - 耗时操作:uni-app x 项目建议在 IO 线程调用(见下方完整示例);uni-app 项目可直接调用
- 模块型号选择:SLR_MODULE 为 iData 50/60 等常见把枪设备型号,具体以设备规格为准
const ok = initialize({ moduleType: "SLR_MODULE", power: 30 });
setPower(power) : boolean
设置发射功率,需在 initialize 成功后调用。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| power | number | 是 | 功率值,不同机型上限不同(常见 5~33) |
setPower(30);
startInventory() : boolean
开始盘点。成功后标签数据持续通过 onTag 回调返回。重复调用幂等(已在盘点中返回 true)。
startInventory();
stopInventory() : boolean
停止盘点。
stopInventory();
isInitialized() : boolean
判断模块是否已初始化(且未释放)。
if (isInitialized()) {
startInventory();
}
onTag(callback) / offTag(callback)
注册 / 注销标签数据回调。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| callback | (tag: UHFTag) => void |
是 | 标签回调;注销时传 null 可清除全部回调 |
| tag | UHFTag | - | { epc: string, rssi: string, data: string, extra: string, receivedAt: number } |
注意事项:
- 盘点期间回调触发频率高(每秒可达数十次),请勿在回调中执行同步 IO 或复杂 UI 操作
- 重复注册同一个函数会自动替换旧回调,不会叠加;
onTag回调可能来自非 UI 线程,更新 UI 前请自行切线程(uni-app x 示例见下) receivedAt为标签读取时刻的毫秒时间戳
const tagHandler = function (tag: UHFTag): void {
console.log(`EPC: ${tag.epc}, RSSI: ${tag.rssi}, 时间: ${tag.receivedAt}`);
};
onTag(tagHandler); // 注册
offTag(tagHandler); // 注销指定回调
offTag(null); // 清除全部回调
onHardwareKey(callback) / offHardwareKey()
监听 / 取消监听设备把枪按键。回调参数为 Android 键值(number / Int)。
插件识别的触发键列表:
| 按键 | keyCode |
|---|---|
| KEYCODE_F8 | 134 |
| KEYCODE_F4 | 131 |
| KEYCODE_BUTTON_4 | 310 |
| KEYCODE_BUTTON_3 | 309 |
| KEYCODE_PROG_RED | 286 |
- 长按只触发一次(忽略 repeat 事件)
- 触发键按下时 App 层会拦截事件,但仍需配合
unlockBarcodeKey()释放系统 iScan 服务的按键归属(见下)
onHardwareKey((keyCode) => {
// 典型用法:按键切换盘点状态
});
offHardwareKey(); // 取消监听
unlockBarcodeKey() / lockBarcodeKey()
把枪按键归属切换:
unlockBarcodeKey():发送系统广播android.intent.action.BARCODEUNLOCKSCANKEY,通知系统 iScan 扫码服务释放把枪按键(按键不再触发扫码/亮灯),由 App 接收按键事件。建议在页面onShow/onMounted时调用lockBarcodeKey():发送广播android.intent.action.BARCODELOCKSCANKEY,恢复系统 iScan 扫码服务接管按键。建议在页面onHide/onUnmounted、或调用destroy()前执行
不调用
unlockBarcodeKey()时,把枪按键会导致系统扫码与 RFID 盘点同时触发。
requestPermission(callback) : void
申请插件所需的存储权限(一次性封装,内部已做已授权检查):
- Android 11+:检查"所有文件访问"(MANAGE_EXTERNAL_STORAGE)权限,未授予时自动跳转系统设置页,用户开启后返回需重新调用
initialize - Android 6.0 ~ 10:弹出运行时权限申请(READ/WRITE_EXTERNAL_STORAGE)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| callback | (granted: boolean) => void |
是 | 授权结果,true 表示可继续初始化 |
requestPermission((granted) => {
if (granted) {
initialize({ moduleType: "SLR_MODULE" });
}
});
destroy() : void
释放设备:停止盘点、模块下电、注销标签回调、按键监听并恢复系统扫码。页面卸载或退出前必须调用,否则设备串口将保持占用,其他应用无法使用 UHF。
destroy();
完整示例
典型使用流程
requestPermission(申请权限)
↓ granted === true
initialize(初始化模块,成功后 onHardwareKey / onTag 生效)
↓
unlockBarcodeKey(接管把枪按键,系统扫码不再响应)
↓
onTag 回调持续返回标签 → startInventory / stopInventory 切换盘点
↓ 页面退出
lockBarcodeKey(恢复系统扫码)→ offTag / offHardwareKey / destroy(释放设备)
调用时序要求:
initialize必须在requestPermission授权成功之后调用;startInventory必须在initialize成功之后调用;onTag建议在开始盘点前注册、停止盘点后注销,避免无谓回调;- 页面卸载前必须
destroy(),否则设备串口持续被占用; unlockBarcodeKey/lockBarcodeKey必须成对使用,退出时忘记lockBarcodeKey会导致把枪键一直无法扫码。
示例一:uni-app x 项目(.uvue,组合式 API)
新建 pages/rfid/rfid.uvue,并在 pages.json 中注册:
{
"path": "pages/rfid/rfid",
"style": {
"navigationBarTitleText": "UHF RFID 盘点"
}
}
页面完整代码(可直接复制运行,功能:权限申请 → 初始化 → 盘点 → 把枪键触发 → EPC 去重计数 → 释放):
<template>
<view class="page">
<text class="status">{{ status }}</text>
<button class="btn" :disabled="isIniting" @click="initDevice">
{{ isIniting ? "初始化中..." : "初始化" }}
</button>
<button class="btn" :disabled="!ready" @click="toggleReading">
{{ reading ? "停止盘点" : "开始盘点" }}
</button>
<button class="btn" :disabled="!ready" @click="releaseDevice">释放设备</button>
<text class="summary">共 {{ list.length }} 种标签,读取 {{ total }} 次</text>
<view v-for="(item, index) in list" :key="item.epc" class="row">
<text class="row-index">{{ index + 1 }}</text>
<text class="row-epc">{{ item.epc }}</text>
<text class="row-count">{{ item.count }}</text>
<text class="row-rssi">{{ item.rssi }}</text>
</view>
</view>
</template>
<script setup lang="uts">
import {
requestPermission, initialize, isInitialized,
onTag, offTag, startInventory, stopInventory,
onHardwareKey, offHardwareKey,
unlockBarcodeKey, lockBarcodeKey, destroy, UHFTag
} from "@/uni_modules/uhf-rfid-scanner";
type TagItem = { epc: string, count: number, rssi: string };
const status = ref("未初始化");
const isIniting = ref(false);
const reading = ref(false);
const ready = ref(false);
const list = ref<Array<TagItem>>([]);
const total = ref(0);
function updateTag(tag: UHFTag): void {
// 回调来自非 UI 线程,更新 UI 需切回主线程
UTSAndroid.getDispatcher("main").async(function (_: Any | null): void {
total.value += 1;
const idx = list.value.findIndex((item: TagItem): boolean => item.epc == tag.epc);
if (idx >= 0) {
list.value[idx].count += 1;
list.value[idx].rssi = tag.rssi;
} else {
list.value.push({ epc: tag.epc, count: 1, rssi: tag.rssi } as TagItem);
}
}, null);
}
const tagHandler = function (tag: UHFTag): void { updateTag(tag); };
function toggleReading(): void {
if (reading.value) {
stopInventory();
offTag(tagHandler);
reading.value = false;
} else {
onTag(tagHandler);
reading.value = startInventory();
}
}
function initDevice(): void {
isIniting.value = true;
requestPermission(function (granted: boolean): void {
if (!granted) {
isIniting.value = false;
status.value = "缺少存储权限";
return;
}
UTSAndroid.getDispatcher("io").async(function (_: Any | null): void {
const ok = initialize({ moduleType: "SLR_MODULE" });
UTSAndroid.getDispatcher("main").async(function (_: Any | null): void {
isIniting.value = false;
ready.value = ok;
status.value = ok ? "设备已就绪" : "初始化失败";
}, null);
}, null);
});
}
function handleKey(_keyCode: number): void {
toggleReading();
}
function releaseDevice(): void {
lockBarcodeKey(); // 恢复系统扫码
offHardwareKey();
offTag(tagHandler);
destroy();
reading.value = false;
ready.value = false;
status.value = "设备已释放";
}
onMounted(() => {
onHardwareKey(handleKey);
unlockBarcodeKey(); // 接管按键,禁止按键触发扫码
});
onUnmounted(() => {
lockBarcodeKey(); // 恢复系统扫码
offHardwareKey();
offTag(tagHandler);
destroy();
});
</script>
<style>
.page {
display: flex;
flex-direction: column;
padding: 24px;
background-color: #f4f6f8;
}
.status {
font-size: 16px;
color: #5f6b7a;
margin-bottom: 12px;
}
.btn {
height: 44px;
margin-bottom: 12px;
background-color: #126b70;
color: #ffffff;
}
.summary {
font-size: 13px;
color: #6d7784;
margin: 12px 0;
}
.row {
display: flex;
flex-direction: row;
min-height: 40px;
background-color: #ffffff;
align-items: center;
}
.row-index {
width: 12%;
text-align: center;
font-size: 12px;
}
.row-epc {
width: 50%;
font-size: 12px;
}
.row-count {
width: 18%;
text-align: center;
font-size: 12px;
color: #126b70;
}
.row-rssi {
width: 20%;
text-align: center;
font-size: 12px;
}
</style>
示例二:uni-app 项目(.vue,vue3 选项式 API)
新建 pages/rfid/rfid.vue,pages.json 注册方式与示例一相同。与 uni-app x 的差异:页面为 .vue、无 UTS 类型注解、回调直接更新 data(框架已桥接线程)、生命周期用 onLoad / onUnload、initialize 用 setTimeout 避免阻塞 UI。
<template>
<view class="page">
<text class="status">{{ status }}</text>
<button class="btn" :disabled="isIniting" @click="initDevice">
{{ isIniting ? '初始化中...' : '初始化' }}
</button>
<button class="btn" :disabled="!ready" @click="toggleReading">
{{ isReading ? '停止盘点' : '开始盘点' }}
</button>
<button class="btn" :disabled="!ready" @click="releaseDevice">释放设备</button>
<text class="summary">共 {{ tagList.length }} 种标签,读取 {{ totalReadCount }} 次</text>
<view v-for="(item, index) in tagList" :key="item.epc" class="row">
<text class="row-index">{{ index + 1 }}</text>
<text class="row-epc">{{ item.epc }}</text>
<text class="row-count">{{ item.count }}</text>
<text class="row-rssi">{{ item.rssi }}</text>
</view>
</view>
</template>
<script>
import {
requestPermission, initialize, isInitialized,
onTag, offTag, startInventory, stopInventory,
onHardwareKey, offHardwareKey,
unlockBarcodeKey, lockBarcodeKey, destroy
} from '@/uni_modules/uhf-rfid-scanner';
export default {
data() {
return {
status: '未初始化',
isIniting: false,
isReading: false,
ready: false,
tagList: [],
totalReadCount: 0
};
},
onLoad() {
onTag(this.handleTag); // tag 结构 { epc, rssi, data, extra, receivedAt }
onHardwareKey(this.handleHardwareKey);
unlockBarcodeKey(); // 接管把枪按键,禁止按键触发扫码
},
onUnload() {
lockBarcodeKey(); // 恢复系统扫码
offHardwareKey();
offTag(this.handleTag);
destroy();
},
methods: {
handleTag(tag) {
this.totalReadCount += 1;
const index = this.tagList.findIndex((item) => item.epc === tag.epc);
if (index >= 0) {
this.tagList[index].count += 1;
this.tagList[index].rssi = tag.rssi;
} else {
this.tagList.push({ epc: tag.epc, count: 1, rssi: tag.rssi });
}
},
handleHardwareKey() {
this.toggleReading();
},
initDevice() {
if (this.isIniting) return;
this.isIniting = true;
this.status = '正在检查存储权限...';
requestPermission((granted) => {
if (!granted) {
this.isIniting = false;
this.status = '缺少存储权限,请在系统设置中授权后重新初始化';
return;
}
this.status = '正在初始化设备...';
// initialize 为耗时串口操作,延迟执行避免阻塞 UI
setTimeout(() => {
const ok = initialize({ moduleType: 'SLR_MODULE' });
this.isIniting = false;
this.isReading = false;
this.ready = ok;
this.status = ok ? '设备已就绪' : '初始化失败';
}, 50);
});
},
toggleReading() {
if (!isInitialized()) {
this.status = '请先初始化设备';
return;
}
if (this.isReading) {
stopInventory();
this.isReading = false;
} else {
this.isReading = startInventory();
}
},
releaseDevice() {
lockBarcodeKey();
offHardwareKey();
offTag(this.handleTag);
destroy();
this.isReading = false;
this.ready = false;
this.status = '设备已释放';
}
}
};
</script>
<style>
.page {
padding: 24rpx;
background-color: #f4f6f8;
min-height: 100vh;
box-sizing: border-box;
}
.status {
font-size: 28rpx;
color: #5f6b7a;
margin-bottom: 20rpx;
}
.btn {
margin-bottom: 20rpx;
background-color: #126b70;
color: #ffffff;
}
.summary {
font-size: 24rpx;
color: #6d7784;
margin: 16rpx 0;
}
.row {
display: flex;
flex-direction: row;
min-height: 72rpx;
background-color: #ffffff;
align-items: center;
border-bottom: 1rpx solid #edf0f2;
}
.row-index {
width: 12%;
text-align: center;
font-size: 24rpx;
}
.row-epc {
width: 50%;
font-size: 24rpx;
}
.row-count {
width: 18%;
text-align: center;
font-size: 24rpx;
color: #126b70;
}
.row-rssi {
width: 20%;
text-align: center;
font-size: 24rpx;
}
</style>
错误码
| 错误码 | 说明 |
|---|---|
| 9010101 | UHF 模块未初始化 |
| 9010102 | 不支持的 UHF 模块型号 |
| 9010103 | UHF 模块上电失败 |
| 9010104 | 启动盘点失败 |
常见问题(FAQ)
初始化失败如何排查?
- 确认已配置 manifest 权限并重新打包了自定义基座;
- 确认设备为 iData UHF 手持机、模块型号选择正确;
- 通过 adb 过滤插件日志定位失败环节:
adb logcat -s UHFNative,日志会明确输出是实例创建、上电(powerOn)还是功率设置(powerSet)失败及异常堆栈。
Android 11+ 授权"所有文件访问"后仍初始化失败?
授权页返回 App 后,权限不会自动生效到已结束的初始化流程,需重新点击初始化。
按把枪键同时触发了扫码和盘点?
调用 unlockBarcodeKey() 接管按键;页面关闭或释放设备时调用 lockBarcodeKey() 恢复系统扫码。
标签回调里 UI 没刷新?
标签回调可能来自原生轮询线程:uni-app x 项目更新响应式数据需切回主线程(见完整示例);uni-app 项目框架已做桥接,可直接更新 data。
修改权限 / 原生代码后运行不生效?
必须重新制作自定义基座。HBuilderX 按版本号判断基座是否更新,必要时提升 manifest.json 的 versionCode 后重新运行。
同一 EPC 会重复回调吗?
会。盘点模式下每读取到一次标签即回调一次(含重复标签),业务侧通常按 EPC 去重计数(见完整示例的 count 逻辑)。
运行环境要求
- HBuilderX 4.25 及以上
- Android 6.0+(minSdkVersion 23,abi:armeabi-v7a / arm64-v8a)
- iData 系列 UHF 手持机(SDK:UHFJar V1.4.06)
许可
仅供合法授权的资产管理、仓储盘点等业务场景使用。

收藏人数:
购买普通授权版(
试用
赞赏(0)
下载 40
赞赏 0
下载 12593114
赞赏 1949
赞赏
京公网安备:11010802035340号