手持核销车辆系统 — 开发方案
状态:方案已按评审意见细化界面与流程,待最终确认后开发(暂不写代码) 日期:2026-09-25 目标设备:新大陆 NLS-MT95L 手持机(当前 ADB 已连接:MT95LTFFD00631),同时兼容通用安卓手机 识别引擎:百度车牌识别安卓离线 SDK V7.5 包名(已确认):com.ectrip.checkincar 本期策略:先模拟跑通全流程(识别 + 核销),百度 SDK 同步集成但等授权到位后启用 本次细化:第 5 章界面与交互(线框图 + 核销页 5 状态机 + 异常分支 + 硬件键/反馈 + 适配)
1. 需求概述
在手持机上开发一个竖屏安卓 App,供核销员工使用:
- 主界面:两个大按钮 —— 「车辆核销」「系统设置」。
- 车辆核销流程:调起后置摄像头拍摄/对准车牌 → 调用百度离线 SDK 识别车牌 → 展示识别结果并允许人工修正 → 调用后台预约平台接口完成核销 → 展示核销结果(预约信息)。
- 系统设置页:显示/配置
- 本机 IP 地址(自动获取、只读、可刷新)
- 本机 MAC 地址(自动获取、只读)
- 核销员工 ID(可编辑保存)
- 后台核销地址(可编辑保存)
- (附加)设备 SN、百度授权设备指纹、授权状态、识别引擎与模拟开关、版本号
- 今天(第一期)后台与识别均先模拟,界面与流程按真实业务设计,后续只替换引擎与接口实现,不改界面。
2. 实测环境
2.1 目标手持机(ADB 实测)
| 项目 | 实测值 |
|---|---|
| 厂商/型号 | Newland(新大陆)NLS-MT95L |
| 序列号 SN | MT95LTFFD00631 |
| 系统 | Android 11(API 30),固件 MT95L-X_V1.01.002_111006 |
| CPU ABI | arm64-v8a(8 核) |
| 内存/存储 | 2.8GB RAM;/data 20GB(仅用 593MB) |
| 屏幕 | 720 × 1440,density 320(xhdpi),竖屏 |
| 摄像头 | 后摄 + 前摄,支持自动对焦、闪光灯,camera level FULL |
| 网络 | WLAN(wlan0),当前 IP 192.168.50.70/24,SSID HUAWEI-ECTRIP-5G |
| MAC | a4:e8:a3:8d:82:56(/sys/class/net/wlan0/address,权限 -r--r--r--,App 无需权限可直接读取真实 MAC,已实测节点存在且全局可读) |
| 百度设备指纹(实测) | 153DA2A38757D7D7016E76F8EE0AACE367(35 位大写。用官方 demo APK 7.5 在本机激活页实测取得,授权绑的就是它,不是 SN;正式 App 用同一套 jar 预期取值一致,以 App 设置页实显为准) |
2.2 开发机(macOS)
- Android Studio 已安装;Android SDK 平台 19~36 齐全,build-tools 含 34.0.0
- JDK 8 / 11 / 17 / 19 均有(新工程用 JDK 17)
- adb 路径
/opt/homebrew/bin/adb,设备连接正常
2.3 百度 SDK 物料
路径:/Volumes/USERDATA/anroidprj/Baidu_OCR_platenumber_OfflineSDK_Android_7.5-2/7.5车牌sdk/
| 文件 | 用途 |
|---|---|
sdk.zip |
集成物料:3 个 jar + 两种 ABI 的 so + 模型资源 |
ocr-sdk-offline-demo.zip |
官方 demo 工程(Gradle,Java,已分析源码) |
车牌识别安卓离线SDK_V7.5用户接入文档.pdf |
官方接入文档(13 页,已逐页阅读) |
车牌识别SDK_7.5.0.apk |
官方演示 APK |
readme.txt |
模型与授权文件放 assets 的说明 |
3. 百度 SDK V7.5 技术分析(结论)
3.1 包内容(sdk.zip)
Java 依赖(3 个 jar)
libocrgveplate.jar—— 车牌识别引擎(核心类com.baidu.vis.ocrgve.plate.OcrgveEnginePlate)bd_unifylicense.jar—— 统一授权组件(AndroidLicenser/BDLicenseActivator)liantian.jar—— 安全模块
Native 库(两种 ABI 都提供,本设备用 arm64-v8a)
libocrgveplate.so(arm64 约 6.9MB)、libpaddle_light_api_shared_visocr.so(PaddleLite 推理,约 4.4MB)、libbd_unifylicense.so、libliantian.so
模型资源(assets,约 30MB,整目录拷入工程 assets)
- 目录名固定:
assets/ocrgve_resource_plate/ - 含 5 个加密模型(
.dpmodel.encrypted:车牌检测、主分类、单行识别 a/b、车牌颜色)、configure.ini、字典与分类配置等 initFromAssets()内部按固定目录名加载,目录名不可改
3.2 支持范围
- Android 6.0+(文档声明),提供 armeabi-v7a / arm64-v8a;本设备 arm64-v8a 完全匹配
- 纯 Java 接口,离线推理(PaddleLite CPU),识别不依赖网络;网络仅授权时需要
3.3 核心 API(已通过 javap 反编译核对)
// 算法 ID(车牌识别固定值,demo 实测 = 7,以 getAlgorithmId() 返回为准)
int algId = OcrgveEnginePlate.getAlgorithmId();
// ① 授权(必须在子线程调用;成功后才能初始化模型)
// 方式A:本地授权文件(从控制台下载,放 assets)
AndroidLicenser.ErrorCode ret = AndroidLicenser.getInstance()
.authFromFile(context, licenseKey, licenseFileName, true/*isRemote*/, algId);
// 方式B:在线(单台/批量)序列号激活,返回 0 成功
int ret = BDLicenseActivator.initLicenseOnLine(context, serialKey, "", algId);
// 设备指纹(控制台绑定授权用)
String deviceId = AndroidLicenser.getDeviceId(context);
// 授权信息(有效期等)
BDLicenseAuthInfo info = AndroidLicenser.getInstance().authGetAuthInfo(context, algId); // expireTime
// ② 初始化模型(子线程;授权成功后调用;全局只初始化一次,重复初始化返回 MULTI_INIT=6)
OcrgveEnginePlate engine = new OcrgveEnginePlate();
OcrgveEnginePlate.setPreFiltering(false); // 纯背景前置过滤,可选
int code = engine.initFromAssets(context); // 0=成功
// ③ 识别(子线程)
ArrayList<OcrgvesdkResponsePlate> results =
engine.processOcrgvesdk(rgbaBytes, 10/*PIX_RGBA*/, width, height, 0/*orientation*/);
// ④ 释放(App 退出时)
engine.uninit();
识别结果 OcrgvesdkResponsePlate 字段
| 字段 | 含义 |
|---|---|
name |
类型,取 "plate_number" 为车牌结果 |
mystring |
车牌号字符串(支持新能源 8 位等) |
color |
颜色:blue/yellow/green/white/black(蓝/黄/绿/白/黑) |
detScore |
检测置信度 |
recgProb[] |
每个字符的识别置信度(可用最小值做整体过滤,demo 默认阈值 0.8) |
coords[8] |
车牌四角坐标 |
初始化错误码:0 成功、1 未知、2 非法参数、3 内部错误、4 鉴权错误、5 未初始化、6 重复初始化、7 图片处理错误、9 模型文件未找到。
授权错误码(高频):7 设备 ID 校验错误、8 包名(applicationId)校验错误、9 过期时间不正确、11 授权已过期、12 本地文件读取失败、13 远程数据拉取失败。
3.4 授权机制(关键约束,影响工程决策)
重要澄清:百度授权绑定的是「设备指纹 Device-id」,不是设备硬件 SN。
标识 是什么 怎么取 用途 百度设备指纹 Device-id 百度安全模块(liantian)综合多因子硬件信息生成的一串大写 ID(带 fingerVersion指纹版本)代码 AndroidLicenser.getDeviceId(context);或 demo logcat 搜BDLicenseLocalInfo(文档 2.5 节原文:「设备指纹即 deviceid」)百度离线授权的唯一设备标识,控制台必须绑它 设备硬件 SN 厂商出厂序列号 ro.serialno(本机 =MT95LTFFD00631,与 ADB 设备号一致)Build.SERIAL/ 属性读取 / 设置页可手填兜底仅我方资产管理、核销上报告备用,百度授权不用它 注:demo 激活页把 Device-id 标注为「序列号」是口语化叫法,二者不是一回事。
指纹构成(已反编译 liantian.jar 字节码核实):
com.baidu.liantian.ac.LH.getId()采集ANDROID_ID、IMEI/IMSI/SIM 序列号、直接读取/sys/class/net/wlan0/address与eth0/address的真实 MAC(键名 rpmacadd)、WiFi BSSID、Build 硬件信息(MODEL/PRODUCT/BOARD/HARDWARE/MANUFACTURER/DEVICE/DISPLAY、CPU_ABI)等综合生成。稳定性(文档 6.9):刷机、更换硬件会导致指纹变化;安卓系统升级、App 卸载重装、恢复出厂值不会。
本机实测(2026-09-25,官方 demo 7.5 激活页):设备指纹 =
153DA2A38757D7D7016E76F8EE0AACE367,页面原文要求「点击复制下方本机硬件指纹 → 百度云控制台:离线 SDK 管理-单台设备授权-离线激活」。同时 logcat 显示 Android 11 已拒绝第三方读取 IMEI(TelephonyPermissions ... getDeviceId:-1),即本设备指纹实际由 ANDROID_ID + 真实 MAC + Build 硬件信息等因子生成,无电话权限也能稳定取到,App 无需额外申请 READ_PHONE_STATE。
- 官方支持 4 种授权(V7.5 接入文档 2.1 节原文核实,jar 签名
javap复核一致),SN 和设备指纹都涉及,区别在激活方式:
| 方式 | 控制台操作 | 设备端调用(algId = OcrgveEnginePlate.getAlgorithmId() = 7) | 要不要手工提指纹 |
|---|---|---|---|
| ① 控制台离线授权文件 | 申请序列号 → 单台设备授权选「车牌识别」→ 点「离线激活」绑定设备指纹 → 下载 .ini 授权文件 | 文件放 assets,AndroidLicenser.getInstance().authFromFile(ctx, licenseKey, licenseFile, true, 7),文档示例 key/file = AISEE_OCRGVE_7_ANDROID / AISEE_OCRGVE_7_ANDROID.ini(key/file 同名时可直接照搬 demo) | 要,先取指纹再去控制台绑定 |
| ② 按 App 授权文件 | 申请按 app 授权的文件(绑包名+签名 MD5),licenseKey 随文件提供 | 同上 authFromFile,licenseKey/licenseName 填授权方给的值 | 否(绑包名+签名) |
| ③ 在线单台设备序列号 | 控制台申请单台序列号(SN) | 设备联网调 BDLicenseActivator.initLicenseOnLine(ctx, 序列号, "", 7),返回 0 成功;首次联网激活时 SDK 自动把本机指纹绑定到该 SN | 否(自动绑定,最省事) |
| ④ 在线批量序列号 | 控制台申请批量序列号(SN) | 同③,同一个 SN 可激活多台设备 | 否 |
指纹获取 API(文档 2.5 节「获取本地鉴权设备指纹信息(离线激活需要)」,原文「设备指纹即 deviceid」):
- 直接取:
AndroidLicenser.getDeviceId(context)(本 App 设置页采用,实测153DA2A3…) - 完整本地信息:
AndroidLicenser.getInstance().authGetLocalInfo(ctx, 7)返回BDLicenseLocalInfo,字段含deviceId / packageName / md5(签名MD5) / licenseKey / fingerVersion;demo 也可在 logcat 搜BDLicenseLocalInfo拿 Device-id。- 授权同时绑定「设备指纹 Device-id + 应用包名(applicationId)」,一机一证;换设备、刷机、改包名均会失效;系统 OTA / 卸载重装不改变指纹。
- 当前拿到的 SDK 包内没有任何授权文件(assets 只有模型;demo 工程也没有 license),官方 demo 必须输入有效序列号/授权文件才能识别。
- 因此:应用包名必须先定下来再去申请授权,中途改包名授权作废(包名已确认,见第 14 章;申请流程见第 11 章)。
- 第一期 App 的设置页会同时展示「设备 SN(备查)」与「百度设备指纹(点按复制,授权必用)」,未购授权也能先取指纹去控制台申请。
- 推荐落地路径:手持机能联网时优先走方式③(设置页输入控制台申请的单台 SN,
initLicenseOnLine一键激活,无需手工收集指纹);设备现场无网络时走方式①(设置页读指纹 → 控制台离线激活下载.ini→ 随 App 打包进 assets,LICENSE_KEY/LICENSE_FILE见 LicenseManager.java 常量)。 - 已实测坑:未授权状态下
getAuthStatus(7)在该 SDK 内会抛ArrayIndexOutOfBoundsException: length=18; index=1000(其内部用状态码索引 ErrorCode 枚举),必须 try/catch;authGetAuthInfo()未授权时直接 native SIGSEGV 崩溃,必须先确认 SUCCESS 再调。
3.5 demo 流程分析(可直接借鉴)
StartActivity(检查授权)→ ActivitionActivity(三种激活方式 + 展示设备指纹)→ HomeActivity(子线程 initFromAssets)→ ScaleActivity(Camera1 连续取流 640×480 NV21 → 旋转为正向 Bitmap → RGBA → 子线程识别 → 置信度过滤 → 出结果)。
demo 相机部分较老旧(Camera1 + 离屏 SurfaceTexture + ImageView 显示画面),新工程不照搬,改用 CameraX;授权、初始化、识别的调用时序与线程模型严格按 demo/文档实现。
4. 总体设计
4.1 业务流程(详细设计见第 5 章)
┌─────────┐ 车辆核销 ┌──────────────┐ 对准/拍照 ┌──────────────┐
│ 主界面 │ ─────────▶ │ 相机识别页 │ ──────────▶ │ 结果确认卡片 │
│ Main │ ①点击 │ VerifyActivity│ ②自动/拍照 │ (可人工改牌) │
└─────────┘ └──────────────┘ └──────┬───────┘
│ 系统设置 ▲ 重拍/继续识别 │ ③确认核销
▼ └─────────────────────┤
┌─────────┐ ▼
│ 设置页 │ ┌──────────────────┐
│ Settings│ │ 调用后台核销接口 │
└─────────┘ │ (本期 Mock) │
└──────┬───────────┘
▼
┌──────────────────┐
│ ④核销结果(成功/失败)│
│ 成功→2s 自动回取景 │
│ (连刷下一辆) │
└──────────────────┘
设计目标:每辆车常规操作只需 2 次点击 —— 主屏点「车辆核销」→ 对准车牌(自动识别)→ 点「确认核销」→ 成功后自动回到取景位继续下一辆,无需返回主屏。
识别策略(已确认:自动 + 拍照):连续自动识别为主(对准即出结果),手动拍照按钮为辅;取流帧节流约 400ms、单线程串行识别,命中且最小字符置信度 ≥ 阈值(默认 0.80,可设)后停止取流进入确认;支持「重拍」恢复。附闪光灯开关;预留音量下键/手持机实体扫描键作为拍照快捷键。
4.2 技术栈
| 项 | 选型 | 理由 |
|---|---|---|
| 语言 | Java 1.8 | 百度 SDK 与 demo 均为 Java,可最大量直接复用经过验证的调用代码 |
| IDE/构建 | Android Studio + Gradle 8.x + AGP 8.x + JDK 17 | 本机已具备;compileSdk/targetSdk 34 |
| minSdk | 23(Android 6.0) | 对齐百度声明的支持下限,兼容通用安卓手机 |
| ABI | arm64-v8a + armeabi-v7a | 覆盖手持机与主流手机 |
| 相机 | CameraX 1.3.x(camera-camera2 + lifecycle + view) | 生命周期安全、自动处理预览方向/分辨率;MT95L 为标准 Camera2 FULL 设备 |
| UI | AndroidX AppCompat + Material + ViewBinding,传统 View/XML | 竖屏固定,720×1440 dp 适配(基准宽 360dp) |
| 网络 | OkHttp 4 + org.json | 轻量;核销接口只有一个 |
| 存储 | SharedPreferences(设置)+ 本地 JSON(核销记录,最近 100 条) | 不引入数据库,保持简单 |
| 方向 | 全应用 screenOrientation="portrait" |
需求明确竖屏 |
若 CameraX 在该工业设备上出现兼容性问题(概率低),回退方案:移植 demo 的 Camera1 取流代码(已验证可在百度引擎上跑通),识别/业务层不变。
4.3 工程结构(拟新建于当前目录)
checkInCar/
├── 手持核销车辆系统.md ← 本文档
├── settings.gradle / build.gradle / gradle.properties / gradlew
└── app/
├── build.gradle
└── src/main/
├── AndroidManifest.xml
├── assets/
│ └── ocrgve_resource_plate/ ← 从 sdk.zip 整目录拷贝(约30MB)
│ (百度授权 *.ini 待用户提供后放这里)
├── jniLibs/
│ ├── arm64-v8a/ ← 4 个 so(手持机用)
│ └── armeabi-v7a/ ← 4 个 so(通用手机兼容)
├── libs/ ← libocrgveplate.jar / bd_unifylicense.jar / liantian.jar
├── res/ ← layout/drawable/values/mipmap(竖屏 UI)
└── java/com/ectrip/checkincar/
├── App.java ← Application,引擎单例初始化
├── MainActivity.java ← 主界面(2 个按钮 + 今日核销数)
├── ui/settings/SettingsActivity.java
├── ui/verify/VerifyActivity.java ← 相机 + 识别 + 结果确认 + 核销
├── core/prefs/AppPrefs.java ← 全部设置项的读写
├── core/device/DeviceInfo.java ← IP / MAC / SN 获取(多策略)
├── plate/
│ ├── PlateInfo.java ← 车牌号/颜色/置信度/位图
│ ├── PlateRecognizer.java ← 识别引擎接口(可切换)
│ ├── MockPlateRecognizer.java ← 模拟识别(本期默认)
│ └── BaiduPlateRecognizer.java ← 百度 SDK 封装(授权+init+识别)
├── license/LicenseManager.java ← 百度授权状态/在线激活/设备指纹
├── net/
│ ├── VerifyApi.java ← 核销接口(真实 HTTP)
│ └── MockVerifyApi.java ← 模拟核销(本期默认)
├── model/VerifyRequest.java / VerifyResult.java
└── history/VerifyHistory.java ← 本地核销记录(JSON)
包名
com.ectrip.checkincar(结合目录名 checkInCar 与现场 WiFi ECTRIP)已评审确认采用,App 显示名「车辆核销」;该包名将用于百度授权申请,之后不再变更。
5. 界面与交互详细设计(本章为本次重点细化内容)
5.0 设计原则
- 竖屏单手操作:全部页面锁竖屏;核心按钮放在拇指热区(屏幕下半部),高度 ≥56dp。
- 名称四字对齐:主按钮统一 4 个字 —— 「车辆核销」「系统设置」「确认核销」「重拍」。
- 步骤最少:常规每车仅 2 次点击(进核销 → 确认核销),识别与连刷全部自动化。
- 紧凑两列:设置页信息项采用「左标签 : 右值」左右两列、分组卡片排列,减少纵向行数,把屏幕空间留给状态/记录区域。
- 状态可见:每个耗时动作都有明确提示(识别中、核销中),结果用大字 + 颜色 + 振动/音效三重反馈,户外嘈杂环境也能用。
5.1 页面清单与跳转关系
| 页面 | Activity | 进入方式 | 返回行为 |
|---|---|---|---|
| 主界面 | MainActivity | 启动页 | — |
| 系统设置 | SettingsActivity | 主界面「系统设置」 | 返回即回主界面,配置自动保存 |
| 车辆核销 | VerifyActivity | 主界面「车辆核销」 | 返回主界面;核销成功不关闭本页,自动回取景态 |
页面间不产生堆叠(核销页只有一个实例),应用内不做登录页(员工 ID 设置页手填)。
5.2 主界面 MainActivity(线框,基准 360×720dp)
2026-09-25 更新:主界面按「鼎游 ECTRIP 科技风」改版(品牌 Logo + 模式状态 + 大按钮),最新设计以第 15.2 节为准,本节线框保留作历史对照。
┌──────────────────────────────┐
│ 车辆核销 v1.0.0 │ ← 标题栏 48dp,右侧版本号
│ │
│ │
│ ┌────────────────┐ │
│ │ │ │
│ │ 🚗 车辆核销 │ │ ← 主按钮:高 110dp,主色实心,
│ │ │ │ 28sp 粗体,点击进核销页
│ └────────────────┘ │
│ │
│ ┌────────────────┐ │
│ │ ⚙ 系统设置 │ │ ← 次按钮:高 96dp,浅色描边
│ └────────────────┘ │
│ │
│ │
│ 引擎:模拟 网络:WLAN ✔ │ ← 状态行 14sp
│ 今日已核销 12 台 │
└──────────────────────────────┘
- 两按钮纵向居中、宽度占屏 80%;「车辆核销」视觉权重最高。
- 底部状态行实时显示:识别引擎(模拟/百度)、网络是否在线、今日成功核销数(从本地记录统计,进页面 onResume 刷新)。
- 连续点击「车辆核销」防抖;若选百度引擎但未授权/模型未就绪,按钮仍可点,进核销页时弹提示并引导去设置(不闪退)。
5.3 车辆核销页 VerifyActivity —— 单页 5 个状态(核心)
该页只有一个 Activity,通过覆盖层切换 5 个状态,避免页面跳转打断节奏:
S1 取景待识别 ──自动帧/拍照──▶ S2 识别中 ──命中且置信度达标──▶ S3 结果确认
▲ │ │
│ 未命中/低分 │ 确认核销
│ └──────────────▶ 回 S1 ▼
│ S4 核销提交中
│◀──────────── 成功:2s 后自动返回(连刷)──────────────────┤
│ │
└────────────────── 失败:停留,手动「重试/重拍」◀── S5 核销结果(成功/失败)
S1 取景待识别
┌──────────────────────────────┐
│ ✕ 车辆核销 💡 开 │ ← 顶栏:左=返回主界面,右=闪光灯开关
│ │
│ 后摄全屏预览 │
│ ┌────────────────────┐ │
│ │ ╲ ╱ │ │ ← 车牌对齐框(横向矩形,四角 L 形
│ │ │ │ 高亮线 + 半透明遮罩)
│ │ │ │
│ │ ╱ ╲ │ │
│ └────────────────────┘ │
│ │
│ 将车牌移入框内,自动识别 │ ← 提示文字随状态变化
│ │
│ ⬤ │ ← 底部居中大快门(高 72dp),
│ [拍照识别] │ 手动拍照;音量下键/扫描键等效
└──────────────────────────────┘
- CameraX 全屏预览,画面按竖屏自动旋转校正;对齐框宽占屏 86%、高约宽的 1/3。
- 自动识别在后台持续进行(用户无感);快门是主动兜底。
- 连续 3 秒无任何检测时,提示文字轮播:「请靠近车牌 / 保持光线充足 / 点击下方按钮拍照」。
S2 识别中
- 命中帧后立即停止取流,画面定格;对齐框四角变为加载色,提示「识别中…」(正常 200~600ms,不做全屏遮罩,避免闪烁)。
- 结果不达标(无车牌或最低字符置信度
S3 结果确认(底部抽屉式卡片,不挡全图)
┌──────────────────────────────┐
│ (定格的车牌画面) │
│ │
├──────────────────────────────┤
│ 车牌号 蓝牌 │ ← 颜色标签:蓝/黄/绿/白/黑
│ ┌──────────────────────────┐│
│ │ 京 A8D6F8 ✎ ││ ← 36sp 超大号;点 ✎ 弹键盘手改
│ └──────────────────────────┘│
│ 置信度 0.96 员工 10086 │ ← 次要信息 13sp 一行
│ │
│ ┌──────────┐ ┌────────────┐ │
│ │ 重拍 │ │ 确认核销 │ │ ← 左次右主,等高 56dp
│ └──────────┘ └────────────┘ │
└──────────────────────────────┘
- 车牌可人工修正:点击号码区域弹数字+省份简称键盘(系统键盘即可,输入自动去空格/转大写);手改后置信度置灰显示「人工输入」。
- 员工 ID 取自设置页;若为空,点「确认核销」时提示「请先在设置中填写核销员工 ID」并给「去设置」入口。
- 后台地址为空或非法同理拦截,不发无效请求。
S4 核销提交中
- 卡片切换为 Loading:旋转指示 +「核销中,请勿移开…」;按钮全部禁用防重复提交;HTTP 超时 10s。
S5 核销结果
成功(绿底) 失败(红底)
┌──────────────────────┐ ┌──────────────────────┐
│ ✔ │ │ ✕ │
│ 核销成功 │ │ 核销失败 │
│ 京A8D6F8 │ │ 京A8D6F8 │
│ ─────────────────── │ │ 原因:该车牌无预约记录 │ ← 后台 msg 原文
│ 预约人:张三 │ │ │
│ 场 地:P1预约车位 │ │ │
│ 时 段:今日09-18点 │ │ │
│ 2 秒后自动继续… │ │ [重拍] [手改车牌重试] │
│ [立即继续] │ │ [返回] │
└──────────────────────┘ └──────────────────────┘
- 成功:提示音 + 长振动 1 次,展示后台返回的预约信息(无字段则只显车牌),2 秒倒计时后自动回 S1 连刷;点「立即继续」不等计时。
- 失败:短促两声振动 + 低音,原因直接显示后台
msg(如:无预约 / 已核销 / 不在有效期 / 网络异常),不自动跳转,由员工选择重拍、手改车牌重试或返回。 - 无论成败都写本地记录(时间、车牌、颜色、员工、结果、原因),成功计入今日数。
5.4 系统设置 SettingsActivity(紧凑两列分组卡片)
┌──────────────────────────────┐
│ ‹ 系统设置 │
│ ── 设备信息(自动获取)─────────│
│ 本机 IP 192.168.50.70 ⟳ │ ← 左标签120dp 右值,⟳=刷新
│ MAC 地址 a4:e8:a3:8d:82:56 │ 值可点复制;IP/MAC 整行左右排
│ 设备 SN MT95LTFFD00631 📋 │ 硬件序列号,仅备查/上报
│ 授权指纹 86F3A2…D9C1 📋 │ ← 百度Device-id,授权必绑(详见3.4)
│ 指纹版本 v3 / 算法ID 7 │
│ 授权状态 未授权 │
│ ── 核销配置 ─────────────────│
│ 员工 ID [ 10086 ] │ ← 标签左、输入框右,同一行
│ 核销地址 [ http://192.16… ] │ ← 一行,保存时校验 URL
│ ── 识别配置 ─────────────────│
│ 识别引擎 (●模拟 ○百度离线) │ ← 单选 segmented
│ 置信阈值 [────●────] 0.80 │ ← 滑块 0.50~0.99
│ ── 模拟配置(模拟引擎时显示)──│
│ 模拟车牌 [ 留空=随机 ] │
│ 识别耗时 [ 300 ] ms │
│ 核销结果 (●成功 ○失败 ○随机) │
│ ── 授权(百度引擎时显示)──────│
│ 序列号 [ ] │
│ [ 激活 ]│
│ │
│ 版本 v1.0.0 今日核销 12 台 │
└──────────────────────────────┘
交互规则:
- 所有配置改完即存(失焦/切换即写 SharedPreferences),不做「保存」按钮,减少一次点击。
- 「模拟配置」与「授权」分组按当前引擎联动展开/收起,不堆无关项。
- IP、MAC、授权指纹、SN 点击整行复制到剪贴板并 Toast「已复制」(申请授权、报备设备时高频使用);「授权指纹」行用浅色小字注明「百度授权绑定此 ID,非设备 SN」,防止混淆(见 3.4)。
- 授权状态:未授权 / 已授权(至 yyyy-MM-dd)/ 授权过期(红色)。
- 核销地址做格式校验(http(s):// 前缀 + host),不合法时行尾红字提示且不保存。
- 整页可滚动,但上述内容在 720×1440 一屏内基本可见,不做冗余留白。
5.5 异常分支与处理方式一览
| 场景 | 触发时机 | 界面处理 |
|---|---|---|
| 相机权限未授予 | 进核销页 | 内嵌说明卡片 +「去授权」按钮跳系统设置;返回后自动重试 |
| 设备无后摄/打开失败 | 绑定相机 | 结果位显示「相机不可用」+ 返回按钮(模拟模式下可用纯手动输牌继续演示) |
| 百度未授权/授权过期 | 引擎=百度且校验失败 | 弹窗显示设备指纹与错误码中文说明 →「去设置激活」;可切回模拟模式 |
| 模型初始化失败 | 进核销页前 | 提示错误码(4 鉴权/9 模型缺失等),禁止进入 S1,回设置处理 |
| 一直识别不到车牌 | S1/S2 | 不打断,文字引导;员工可手动拍照 |
| 识别到但置信度低 | S2 | Toast「识别不确定,请靠近或手改」后回 S1;若手改则直接进 S3 |
| 员工 ID/核销地址为空 | S3 点确认 | 输入框抖动提示 +「去设置」,不发请求 |
| 网络不通/超时(10s) | S4 | S5 失败态:原因「网络异常,请检查 WiFi 与核销地址」,可原样重试不重拍 |
| 后台返回 code≠0 | S4→S5 | 失败态显示 msg(无预约/已核销/不在有效期…),支持改牌重试 |
| HTTP 非 200/返回非法 JSON | S4 | 失败态「服务异常(HTTP 500)」并记录日志 |
| 连续重复提交 | S4 | 按钮禁用,二次点击忽略 |
| 识别中按返回 | S1~S3 | 直接回主界面,释放相机与分析器,引擎不销毁 |
| 来电话/切后台 | 生命周期 onPause | 停取流停对焦;回前台自动恢复 S1 |
5.6 硬件键与反馈
- 物理键映射(手持机友好):音量下键 = 拍照(KeyEvent 监听);如后续确认 MT95L 实体扫描键可用,将其键值映射为同一动作(设置里加开关,默认开)。
- 音效:成功/失败两种短音(assets 内置或系统提示音),跟随系统媒体音量;设置页暂不做静音开关(可后加)。
- 振动:成功长振 1 次、失败短振 2 次,使用 VibrationEffect(API 26+),低版本降级。
- 闪光灯:每次进页默认关闭,状态不记忆(避免白天误开)。
5.7 屏幕适配
- 基准宽 360dp(720px@320dpi),用 ConstraintLayout + LinearLayout 组合;文字用 sp,触控目标 ≥48dp。
- 长屏手机(≥800dp 高)拉伸按钮间距与预览区,设置页两列比例固定;小屏(320dp 宽老机)允许设置页滚动,核销页对齐框按比例缩放。
- 颜色仅取功能语义(主色蓝、成功绿、失败红、标签灰),白天户外对比度优先;不使用横屏布局。
6. 关键模块设计
6.1 设备信息获取(IP / MAC / SN,已在设备上验证可行路径)
- IP:
NetworkInterface.getNetworkInterfaces()枚举,取wlan0(其次 eth0)上非 loopback、非 link-local 的 IPv4。实测当前192.168.50.70。无需权限。 - MAC(Android 6+ 官方 API 被屏蔽,返回 02:00:00:00:00:00),按顺序兜底:
- 直接读
/sys/class/net/wlan0/address—— 本机实测权限-r--r--r--可读,值a4:e8:a3:8d:82:56,作为首选; - 枚举
/sys/class/net/*/address找首个非 00/02 开头地址; NetworkInterface.getHardwareAddress();WifiInfo.getMacAddress()(基本是兜底假值,并明确标注「系统限制」)。
- 直接读
- SN:
Build.getSerial()在 API 29+ 需要 READ_PRIVILEGED_PHONE_STATE(普通应用拿不到),改用Build.SERIAL(28 以下)+ 读ro.serialno反射/备用;本设备 SN 与 ADB 一致(MT95LTFFD00631)。如系统接口受限,设置页允许手工填写设备编号并保存。 - 进入设置页与点击刷新时重新获取。
6.2 相机与识别
- CameraX:
Preview+ImageAnalysis(STRATEGY_KEEP_ONLY_LATEST),目标 640×480 / 接近 4:3;输出 YUV_420_888 → 转 Bitmap(按 targetRotation 旋转为正向)→ 必要时缩放到宽 ≤1280 →copyPixelsToBuffer得到 RGBA byte[]。 - 识别在单线程 Executor 串行执行(ReentrantLock 思路同 demo),帧间隔 ≥400ms;识别期间丢弃新帧。
- 结果过滤:仅取
name=plate_number;min(recgProb) ≥ 阈值(可配)才接受;记录颜色。 - 命中后
analysis.clearAnalyzer()停止取流,显示结果卡片;重拍时重新绑定。 - 手电筒:
CameraControl.enableTorch;权限:CAMERA 运行时申请。 - 引擎生命周期:
BaiduPlateRecognizer随 Application 持有单例,进入核销页前确保已授权 +initFromAssets完成(只初始化一次);切换引擎/页面不重复初始化。
6.3 模拟识别(MockPlateRecognizer,本期默认)
- 实现同一个
PlateRecognizer接口,延时 200~500ms 返回PlateInfo:- 车牌 = 设置项固定值,或内置车牌库随机(如 京A·8D6F8 蓝牌、粤B·D12345 新能源绿牌、黄底牌等);
- 置信度 0.90~0.99;颜色随牌型。
- 也支持在结果卡片直接手输车牌(真机演示时的保底手段)。
- 目的:不依赖百度授权即可完整演示「拍(模拟)→ 识 → 改 → 核销 → 记录」闭环。
6.4 后台核销接口
- 统一封装
VerifyApi接口,MockVerifyApi与真实HttpVerifyApi可按设置切换,业务层无感。 - 请求在子线程,OkHttp 超时连接 5s / 读取 10s;支持 http 明文(现场内网地址),通过
network_security_config放行内网明文,不全局放开。 - 接口契约为草案(待与预约平台对齐),见第 7 章;Mock 按该契约返回。
- 每次成功/失败均写本地记录(时间、车牌、员工、结果、原因),主界面显示「今日成功核销数」。
6.5 设置存储 AppPrefs
- 单个 SharedPreferences(
car_verify_prefs)封装全部键:staffId、baseUrl、engineType、scoreThreshold、mockPlate、mockResult、设备编号手填值等;改动即时生效。
6.6 授权管理 LicenseManager
- 封装:查询设备指纹、查询授权状态与到期时间、在线序列号激活、离线文件 authFromFile。
- 严格遵守官方时序:授权(子线程)成功 → 再 initFromAssets(子线程),二者不并发、不重复初始化;所有错误码映射为中文提示(第 3.3 节)。
- App 启动时若引擎为「百度」:后台静默校验授权与模型状态,主界面/设置页展示;未授权则点击「车辆核销」时引导去授权页,不会闪退。
7. 后台核销接口契约(草案,待平台方确认)
请求
POST {后台核销地址}/api/checkin/carVerify
Content-Type: application/json
{
"plateNo": "京A8D6F8", // 车牌号(必填)
"plateColor": "blue", // blue/yellow/green/white/black
"staffId": "10086", // 核销员工ID
"deviceSn": "MT95LTFFD00631", // 设备SN
"mac": "a4:e8:a3:8d:82:56",
"ip": "192.168.50.70",
"timestamp": 1727230000000 // 毫秒
}
响应
{
"code": 0, // 0=核销成功,其他=失败
"msg": "核销成功",
"data": {
"reservationId": "YY20260925001",
"visitorName": "张三",
"lotName": "P1 预约车位",
"visitTime": "2026-09-25 09:00-18:00",
"plateNo": "京A8D6F8"
}
}
- 常见失败:无预约、已核销、不在有效期、车牌不匹配等,以
code/msg展示。 - 真实路径、字段名、鉴权方式(是否加 token/签名/租户号)拿到平台文档后只改
HttpVerifyApi与 model,界面不动。
8. 第一期(今天,模拟版)交付内容与验收
交付:可在 MT95L 上安装运行的 debug APK + 完整 Android Studio 工程。
- 新建工程、拷贝百度 jar/so/模型进工程(不启用不调用,仅保证能打包通过)。
- 主界面两个按钮 + 今日核销数;全屏竖屏。
- 设置页:IP/MAC/SN 自动显示并可刷新;员工 ID、核销地址可保存;引擎/阈值/模拟参数可配;显示百度设备指纹(即使未授权也能取到,供申请授权用)。
- 核销页:CameraX 后摄预览、对焦、闪光灯、对齐框、手动拍照;模拟识别出车牌;结果卡片可改车牌;模拟核销返回成功/失败(可配);成功/失败结果页 + 振动;本地记录与今日计数。
- 相机、网络等运行时权限处理。
adb install -r安装到 MT95LTFFD00631 真机走查全部流程(竖屏、720×1440 适配)。
验收标准
- 手持机上能连续演示:进入核销 → 对准/拍照 →(模拟)出号 → 改号 → 确认核销 → 成功/失败 → 继续下一辆。
- 设置页显示的 IP=
192.168.50.x、MAC=a4:e8:a3:8d:82:56(以实际为准),员工 ID 与地址重启后保留。 - 切换「模拟核销结果=失败/随机」时错误流程与提示正确。
9. 第二期(授权与真实接口到位后)
- 你方在百度控制台完成授权(流程见第 11 章),或提供在线序列号。
- 引擎切「百度离线识别」:真机初始化模型 → 真实车牌识别调试(光照、距离、新能源/黄牌、置信度阈值、识别耗时实测)。
- 按预约平台真实文档实现
HttpVerifyApi、联调核销(含无预约/重复核销等异常分支),必要时加 token/签名。 - release 签名、混淆(追加百度 keep 规则:
-keep class com.baidu.vis.**{*;}、-keep class com.baidu.liantian.**{*;}、keep native 方法)、出正式包。 - 如多台手持机推广:采用在线批量序列号,在设置页逐台激活,避免每台单独打包 license 文件。
10. 构建、安装与验证命令
# 工程就绪后(本机 JDK17 + Android SDK 34)
./gradlew assembleDebug
adb -s MT95LTFFD00631 install -r app/build/outputs/apk/debug/app-debug.apk
adb logcat | grep -iE "Ocrgve|BDLicense|carverify" # 看授权/初始化/识别日志
- 真机信息复核:
adb shell wm size→ 720x1440;getprop ro.product.cpu.abi→ arm64-v8a。 - 授权排障时以设备指纹 + 授权错误码(第 3.3)为准,不凭猜测下结论。
11. 百度授权办理流程(需要你方操作,开发侧配合)
- applicationId 已确认为
com.ectrip.checkincar,授权申请即按此包名,之后不再更改。 - 安装第一期 APK 后,在「系统设置」页复制百度设备指纹(Device-id,授权必绑,注意不是硬件 SN)——每台手持机不同;第一期 App 无需授权即可显示该值。当前这台 MT95L 用同版本 SDK 实测指纹为
153DA2A38757D7D7016E76F8EE0AACE367(可直接拿它去控制台申请,正式 App 装好后再复核一次)。 - 登录百度智能云控制台 → 车牌识别离线 SDK 授权管理:
- 单台离线授权:新建单台授权,选择模型「车牌识别」、填设备指纹、绑定包名 → 离线激活 → 下载授权文件(
*.ini+ 对应 licenseKey)→ 发给开发放入assets/重新出包(一台一证,适合设备少)。 - 在线序列号(推荐多机):申请单台/批量序列号 → 在 App 设置页输入激活(需联网一次),激活信息本地持久化,之后离线可用。
- 单台离线授权:新建单台授权,选择模型「车牌识别」、填设备指纹、绑定包名 → 离线激活 → 下载授权文件(
- 开发侧用
authFromFile(key, 文件名, true, algId)或initLicenseOnLine(序列号)完成鉴权,成功后初始化模型。 - 注意:App 签名、包名需与申请时一致;正式发布前用 release 签名重新核对授权策略。
12. 风险与对策
| 风险 | 影响 | 对策 |
|---|---|---|
| 暂无百度授权文件/序列号 | 真机无法真实识别 | 第一期用模拟引擎跑通全部业务;SDK 物料与封装先行;设置页先取设备指纹便于立即申请授权 |
| 授权绑定包名+设备指纹 | 包名定死、换机失效 | 包名先评审确认;多设备用在线批量序列号;换机要重新授权(官方规则) |
| Android 11+ MAC 被系统屏蔽 | 设置页显示假 MAC | 首选读 sysfs(本机实测可读真实 MAC),多级兜底并标注来源 |
| 工业设备 CameraX 兼容性 | 取流异常 | CameraX 先行;异常回退 demo 的 Camera1 方案,识别层不变 |
| 现场内网 HTTP 明文 | 9.0+ 默认禁止明文 | networkSecurityConfig 仅放行配置的内网地址/端口 |
| 模型/so 体积大(约 50MB) | APK 偏大 | arm64+v7a 都保留(通用手机兼容);后续可按渠道拆 ABI 包 |
| 重复初始化/线程错误 | 模型初始化失败 | 严格单例、子线程、授权后初始化;错误码中文映射 |
| 后台接口未确定 | 无法真实核销 | 接口层抽象 + Mock;契约草案先行,平台文档到位后仅替换实现 |
13. 配置/权限清单(开发时照此实施)
AndroidManifest.xml:CAMERA、INTERNET、ACCESS_NETWORK_STATE、READ_EXTERNAL_STORAGE(仅相册调试,API≤28);largeHeap=true;全部 Activity 竖屏;FileProvider(相册选图)。build.gradle(app):minSdk 23 / target+compile 34;ndk { abiFilters 'arm64-v8a','armeabi-v7a' };jniLibs.srcDirs=['libs']或使用标准 jniLibs 目录;implementation files('libs/*.jar');CameraX / OkHttp 依赖;compileOptions 1.8。- 资源:
assets/ocrgve_resource_plate/整目录;so 与 jar 不重打包损坏(从 zip 解压后二进制原样拷贝)。
14. 评审结论与遗留事项
已确认(2026-09-25 评审)
- ✅ 包名 applicationId:com.ectrip.checkincar,显示名「车辆核销」。
- ✅ 第一期默认模拟识别 + 模拟核销,百度 SDK 同步完成集成与封装、暂不启用真实识别(当前无授权文件,这是今天跑通真机全流程的方式)。
- ✅ 识别交互:对准自动识别 + 手动拍照兜底 + 结果可人工修正(详见 5.3 五状态设计)。
- ✅ 核销员工 ID:系统设置页手工填写并本地保存。
- ✅ 本次重点细化方向:界面与流程(第 5 章已补充设计原则、页面跳转、三屏线框图、核销页状态机、异常分支、硬件键与反馈、适配规则)。
仍遗留(不阻塞第一期开发,可后续提供)
- 预约平台核销接口是否已有正式文档?没有则先按第 7 章契约模拟,拿到后仅替换
HttpVerifyApi与 model。 - 百度授权由谁在控制台申请、预计何时提供序列号或授权文件(流程见第 11 章)。本机指纹
153DA2A38757D7D7016E76F8EE0AACE367已实测取得,现在就可以去控制台申请离线授权;官方 demo APK(车牌识别7.5)已临时安装在手持机上用于取指纹,可保留用于授权后自测,也可卸载。 - 是否需要核销记录的查询/导出页面?(第一期仅本地落盘 + 主界面今日计数;如需要可补一个记录列表页,支持按日期查看。)
- MT95L 实体扫描键是否需要映射为拍照快捷键(第一期先适配音量下键,扫描键拿到真机键值后即可加开关)。
方案已按评审意见完成界面与流程细化。如无其他修改意见,请确认,我将新增 com.ectrip.checkincar 工程并在 MT95L 真机上开发、安装、验证第一期(模拟版)。
15. 第二轮变更(2026-09-25):品牌图标、科技风主界面、在线/离线识别三路由
第一期模拟版真机走查通过后追加。本章与前述章节冲突处,以本章为准。
15.1 应用图标更换为 ECTRIP 鼎标(本轮已实施 ✅)
- 源文件:工程根目录 ectrip.ico(Windows ICO,含 16/32/48/256 四档,256 主档透明底红色「鼎形 ECTRIP」标,与现场手持机上「智能移动验证系统」图标同源)。
- 已用 PIL 从 256px 主档生成全套 Android 启动图标(桌面图标 = 安装后显示图标,Android 不区分二者):
mipmap-{mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi}/ic_launcher.png(48/72/96/144/192px 方形,白底红鼎)- 同目录
ic_launcher_round.png(圆形裁剪版,供部分启动器) - 同目录
ic_launcher_foreground.png(108/162/216/324/432px,自适应图标前景,鼎标占画布 60%,保证落在 66dp 安全圈内) - mipmap-anydpi-v26/ic_launcher.xml /
ic_launcher_round.xml:白底 + 鼎标前景(API26+ 自动裁剪成圆形/圆角方形);旧蓝底车牌矢量前景已删除
- 背景取白色(红鼎在深蓝底上偏闷;参考 App 的品牌呈现也是红鼎 + 浅蓝徽标轮廓 + 白底)。
- 已
assembleDebug+ 装机(2026-09-25),桌面/任务管理/安装确认界面均显示新图标。 - 主界面顶部要展示的竖版品牌区另用 256px 透明底 PNG(
drawable-nodpi/ic_logo_ectrip.png),实施 15.2 时拷入,不做进 mipmap(mipmap 仅用于启动图标)。
15.2 主界面科技风改版(参考现场鼎游「智能移动验证系统」界面)(本轮已实施 ✅)
实施落地(2026-09-25 真机截图确认):品牌区 200dp 深蓝渐变 + 八边形描边徽标(
drawable/bg_octagon.xml,内嵌drawable-nodpi/ic_logo_ectrip.png38dp 红鼎)+「鼎 游 信 息 / ECTRIP · SMART VERIFY」+ 25sp 宽字距标题 + 模式胶囊;底 2dp#35C2FF高光线;内容区四角 L 形青色角标(drawable/decor_corners.xml);主按钮 96dp 蓝渐变bg_btn_tech_primary.xml、车形图标在左;次按钮 88dp 白底蓝描边齿轮图标;状态行改为三张白底小卡片(引擎/网络/今日核销);左下角版本号 + 设备编号。核销页顶栏用半透明深蓝渐变(bg_brand_header_overlay.xml,压住相机预览),设置页顶栏用实色渐变。
参考照片(Newland MT95 上现有 App)关键元素:顶部红色 ECTRIP 鼎标 +「鼎游信息」、深色大标题、绿色「当前:在线检票」模式状态、整宽蓝色大按钮(字距拉开,硬朗、高对比)、底部「版本号 / 设备编号」。在此基础上融入科技感。
竖屏线框(360×720dp,从上到下)
┌────────────────────────────────────┐
│ ▓▓▓▓▓ 深蓝→藏青渐变标题区 ▓▓▓▓▓▓▓ │ 高 200dp
│ ╭──────────────╮ │
│ │ ◇ 红鼎ECTRIP ◇ │ │ ← 浅蓝八边形徽标(矢量描边)
│ ╰──────────────╯ │ 内嵌鼎标 64dp
│ 鼎 游 信 息 │ ← 12sp 浅蓝白,字距加宽
│ │
│ 车 辆 核 销 系 统 │ ← 24sp 白色粗体,字距 6sp
│ ┌────────────────────┐ │
│ │ ● 当前:在线识别 │ │ ← 模式胶囊,在线=荧光绿 #39FF88
│ └────────────────────┘ │ 离线=亮蓝 #4FC3F7 / 模拟=暖橙 #FFB74D
├────────────────────────────────────┤
│ (浅灰底 #F4F6FA,微弱科技网格/角标) │
│ ┌──────────────────────────────┐ │
│ │ ▰▰ 车 辆 核 销 │ │ ← 主按钮 高 96dp,蓝色渐变
│ └──────────────────────────────┘ │ #1565C0→#0D47A1,白色车形图标
│ ┌──────────────────────────────┐ │ 圆角 12dp,按压 0.97 缩放
│ │ ⚙ 系 统 设 置 │ │ ← 次按钮 高 88dp,白底蓝描边
│ └──────────────────────────────┘ │
│ │
│ 引擎:在线 网络:在线 今日核销:1 │ ← 13sp 深灰状态行
├────────────────────────────────────┤
│ 版本号 v1.0.0 │
│ 设备编号 MT95LTFFD00631 │ ← 12sp 浅灰,左对齐(同参考App)
└────────────────────────────────────┘
设计要点:
- 品牌区:深蓝渐变(
#1565C0→#0D2B5C)标题区,内含浅蓝八边形(双切角矩形)描边徽标 + 红鼎 +「鼎 游 信 息」;标题文字白色、宽字距,复刻现场 App 的硬朗工业感。 - 科技感装饰(克制、不影响信息):标题区底部一道 2dp 荧光青
#35C2FF高光线;内容区四角放 L 形青色角标线;背景#F4F6FA叠极淡网格(可选,避免视觉噪点)。 - 模式胶囊随设置引擎实时变色(onResume 刷新),文字「当前:在线识别 / 离线识别 / 模拟识别」——直接对应现场 App「当前:在线检票」。
- 按钮整宽 88%、大字距(4 字之间留空,与参考一致)、主按钮带车形图标;保持本期已验证的点击区域(底部手掌握持区)。
- 底部双行信息保留参考 App 习惯:版本号 + 设备编号(SN);其上保留我们自有的「引擎/网络/今日核销」状态行。
- 核销页不推翻:顶栏改为同样的深蓝渐变 + 青色高光,车牌框四角 L 线沿用(本来就是科技风),其余 5 状态机不动。
- 设置页顶栏同步深蓝渐变;卡片式分组不变。
15.3 识别引擎改三路由:模拟 / 在线 / 离线
设置页「识别引擎」由 2 段改 3 段:模拟识别 | 在线识别 | 离线识别。业务层(VerifyActivity 状态机、结果卡片、核销接口)零改动,只在识别实现层增加一个云端实现:
| 引擎 | 实现类 | 依赖 | 要授权/密钥吗 | 识别位置 |
|---|---|---|---|---|
| 模拟识别 | MockPlateRecognizer(已有,默认) |
无 | 否 | 本地假数据 |
| 在线识别(新增) | OnlinePlateRecognizer(新增) |
外网 + 百度 AI 开放平台 | AK/SK(已提供,预置默认值) | 百度云 REST |
| 离线识别 | BaiduPlateRecognizer(已有) |
离线 SDK 授权 | 设备指纹授权/序列号(第 11 章) | 端侧 so |
工厂 PlateRecognizerFactory.java create(Context) 按 AppPrefs 当前引擎返回实例;主界面状态卡片/胶囊文案同步改为三态。旧引擎值迁移:AppPrefs 初始化时把旧 engine=1(百度离线)一次性迁到 2,标记位 engineV2Migrated。
15.3.1 在线识别接口(依据示例 rest2.0ocrv1license_plate.java 与百度 AI 开放平台文档)
- 获取 access_token(AK/SK 换 token,有效期 30 天,需缓存):
POST https://aip.baidubce.com/oauth/2.0/token
Content-Type: application/x-www-form-urlencoded
body: grant_type=client_credentials&client_id={APIKey}&client_secret={SecretKey}
→ JSON 取 access_token、expires_in(秒,通常 2592000)
- 车牌识别:
POST https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate?access_token=...
Content-Type: application/x-www-form-urlencoded
body: image={JPEG 的 Base64,URLEncode}&multi_detect=false&multi_scale=false
成功:{"words_result":{"color":"blue","number":"粤BD12345","probability":[...]},
"words_result_num":1,"log_id":...}
失败:{"error_code":110,"error_msg":"Access token invalid"}
- 已提供的凭证(控制台应用「闸机检票系统」,ak_sk 鉴权):
| 项 | 值 | App 内存储 |
|---|---|---|
| client_id(API Key / AK) | iKKGwJRwHxIe9CAAXgafSpyt |
AppPrefs,作默认值预填,可在设置改 |
| client_secret(Secret Key / SK) | FS0byCisfASTaqoaEcRDoye4wiPFuosq |
同上,输入框默认密文显示(小眼睛切换) |
15.3.2 客户端实现要点(新增 OnlinePlateRecognizer)
- token 管理(新增
BaiduCloudToken小类):成功获取后把 token 与到期时间戳存 AppPrefs;剩余有效期 >24h 直接复用,否则重新获取;识别返回error_code=110/111(token 失效)时自动重取一次并重试一次,仍失败才报错。 - 图像:沿用现有 JPEG 通路(YUV→NV21→JPEG / 拍照 JPEG),宽压到 ≤1280、质量 80,Base64 后通常 100~300KB;OkHttp
FormBody自动 URLEncode;连接 5s / 读取 10s(云端比本地慢)。 - 节流:在线请求有 QPS 与计费,自动分析帧间隔由 400ms 放宽到 ≥1000ms,识别中丢帧;命中即停(同离线)。S2 文案显示「云端识别中…」。
- 自动帧本地预筛(省次数):自动分析帧在上传前先缩到 160 宽灰度,统计平均亮度与梯度能量(
OnlinePlateRecognizer.frameWorthSending):亮度 245、边缘能量 手动拍照是有意触发,不做预筛(再暗也只产生一次调用,避免"按了没反应")。 - 空结果静默:
error_code=282102/282103(未检测到车牌/目标识别错误,对空画面百度每次都回这个)与无words_result一样按"本帧无车牌"静默处理,不弹 Toast;其余错误(17/18/19 额度·QPS·并发、110/111 鉴权、网络)才经getLastError()在界面节流提示(3s 一次)。 - 结果映射:
words_result.number→车牌;color(blue/yellow/green/white/black/yellowgreen)→现有颜色标签(yellowgreen→green 按新能源处理);取probability数组最小值为置信度;云返回不带 probability 时 PlateInfo.confidence=-1,结果卡片显示「云识别」。在线引擎不过本地置信阈值。 - 错误中文映射:网络不可达 →「无法连接百度云,请检查外网/WiFi」;token 获取失败 →「鉴权失败:…」;
error_code 17/18/19→「云端识别今日次数已超限 / QPS 超限 / 并发超限」;216200/216201/216202 图片类错误;其余显示error_msg(code)。失败后不静默降级,由员工决定切引擎。 - 在线引擎不校验百度离线授权、不加载 so 模型;LicenseManager 与百度 jar 不参与该路径。
- 核销接口不变:
VerifyApiFactory仍按「仅模拟引擎 → MockVerifyApi,其余 → HttpVerifyApi」工作;在线识别 + 真实后台即生产组合,无后台时确认核销走失败页(「网络异常: …」,不闪退)。
15.3.3 设置页联动(在现有「识别配置」分组上改)
── 识别配置 ──────────────────────
识别引擎 [模拟识别|在线识别|离线识别] ← 3 段
置信阈值 [────●────] 0.80 ← 仅「离线识别」显示
── 在线配置(引擎=在线时展开)────────
API Key [iKKGwJ****afSpyt 👁] ← 预填默认 AK,可改
Secret Key [******************** 👁] ← 预填默认 SK,密文
鉴权状态 未获取 Token / 有效(至 MM-dd HH:mm) ← 绿色,实测 30 天
[ 测试鉴权并获取 Token ] ← 立即调 oauth 验证 AK/SK
── 模拟配置(引擎=模拟时展开,已有)──
── 离线授权(引擎=离线时展开,已有)──
- AppPrefs 新增键:
engine(0模拟/1在线/2离线,复用原键改值域并迁移)、cloudAk、cloudSk、cloudToken、cloudTokenExpireAt、engineV2Migrated;旧两段选择迁移(旧值 1 → 2)。AK/SK 输入框 TextWatcher 即时保存,改动即 clearCloudToken() 强制重鉴。 - 在线引擎立即可用的前提:手持机能访问外网(
aip.baidubce.com);现场若只有内网,切离线或模拟。 - 主界面「网络:在线」检测需能区分内网通但外网不通:在线模式启动时可选做一次轻量探测(HEAD baidu.com / DNS),不通时胶囊仍可点但进核销页提示。
15.3.4 安全与计费说明(须知)
- AK/SK 内置在 APK 中可被逆向提取,仅适用于专用手持机、受控分发场景;控制台请勿开启无关付费接口,必要时在百度云设置用量告警。更稳妥的后续方案:我方业务后台代发 OCR 请求(App 调自己的核销地址,由后台持密钥),列为二期可选优化。
- 在线识别按次计费(车牌识别有每日免费额度,超出按次计费,以百度价目表为准),故自动帧 1s 节流 + 暗/糊帧本地预筛(15.3.2),仅在 S1 自动识别;手动拍照为操作员有意触发,不节流、不预筛,每次只产生一次调用。
- 在线引擎不需要第 11 章的设备指纹授权,包名变更不影响在线识别;离线引擎仍受包名+指纹绑定约束。
15.4 实施任务与验收(2026-09-25 实施)
| # | 任务 | 状态 |
|---|---|---|
| A | ECTRIP 图标全套 | ✅ 已装机(15.1) |
| B | 主界面/顶栏科技风改版:品牌区、渐变、胶囊、角标高光、版本/SN | ✅ 真机截图与参考照一致 |
| C | OnlinePlateRecognizer + token 缓存/重试 + 颜色/错误映射 + 1s 节流 + 帧预筛 |
✅ 新增 OnlinePlateRecognizer.java、BaiduCloudToken.java、PlateRecognizerFactory |
| D | 设置页 3 段引擎、在线配置分组(AK/SK 密文预填、测试鉴权)、联动、prefs 迁移 | ✅ |
| E | MT95L 真机走查 | ✅ ②③④ 已验(详见下);①待对准真实车牌实测 |
已验记录(2026-09-25,设备 MT95LTFFD00631,WiFi HUAWEI-ECTRIP-5G 可出外网):
- 鉴权:设置页「测试鉴权并获取 Token」成功,状态「有效(至10-25 12:57)」;AK/SK 与 oauth/REST 通路在设备上打通。
- OCR 接口与凭证:用合成蓝牌「京A12345」调 license_plate 成功返回 number=京A12345、color=blue、probability 数组(各字符 0.9998+),解析契约与 App 实现一致。
- 在线取景:核销页提示「请将车牌放入框内(在线识别)」、S2「云端识别中…」;无牌暗画面经帧预筛零云端调用(实测 10s 0 次请求);282103 空识别静默不弹窗。
- 三切换联动:模拟→模拟配置组;在线→在线配置组(AK 预填/SK 密文+小眼睛/鉴权状态/测试按钮);离线→阈值滑块+授权激活组;主界面胶囊橙/荧光绿/亮蓝三态与引擎卡片同步。
- 不回归:模拟引擎自动识别(粤JS77X4 蓝牌 94%)、结果卡片/重拍/确认流程正常;在线引擎下核销无后台时落失败页,不闪退。
待办(现场补测):引擎切「在线识别」对准真实车牌(蓝/黄/新能源绿牌)验证 1~2 秒回号并走通核销;改错误 SK 后中文失败提示;断网进入不闪退。
验收标准:引擎切「在线识别」、设备连外网时,对准真实车牌 1~2 秒内回号并走通核销(核销仍走 Mock/后台);AK/SK 错误时有明确中文提示;断网时不闪退;模拟与离线现有功能不回归。
16. 第三轮变更(2026-09-27):现场演示打磨(已装机 ✅)
目标:客户现场真机演示前的界面与流程微调。全部改动已构建装机(MT95LTFFD00631)并走查,当前引擎停留在「在线识别」(prefs engine=1)。
16.1 变更清单
| # | 需求 | 实施 |
|---|---|---|
| 1 | 核销失败弹窗也显示车牌 | VerifyActivity.java 失败分支 addInfoRow("车牌", currentPlate.number),位于失败原因下方 |
| 2 | 识别结果卡增加「返回」按钮 | 底部由「重拍 / 确认核销」改为「重拍 / 确认核销 / 返回」三按钮;返回 = finish() 回主页(activity_verify.xml btnBackHome) |
| 3 | 车牌取景框加大 | 引导框 300×96dp → 340×116dp → 356×120dp(屏宽 360dp,横向近满屏,竖向按车牌 440:140 比例)。纯视觉,识别仍送整帧 |
| 4 | 首页引擎状态醒目 | 引擎卡片值 15sp → 17sp 加粗并按引擎着色:在线=#00A854 绿、离线=#0288D1 蓝、模拟=#EF6C00 橙(与顶部胶囊同色);onResume 刷新 |
| 5 | 核销后台固定走模拟、固定成功 | VerifyApiFactory.java 不再按识别引擎分流,恒返回 MockVerifyApi;Mock 去掉随机失败/失败原因分支,固定 600ms 返回成功。留 TODO:正式后台就绪后恢复 HttpVerifyApi 分流 |
| 6 | 成功页预约信息补全 | 展示行:预约号 / 预约时间 / 预约人 / 证件号(脱敏 3502*******1234)* / 预约类型(老人/正常) / 进入日期 / 核销流水(HX+时间戳);模型新增 visitorIdCard / visitorType / verifySerialNo,fromJson 同步解析同名字段 |
| 7 | 「车位」改为「进入日期」 | 字段契约全局收口:lotName → enterDate(VerifyResult、VerifyRecord 本地历史键、MockVerifyApi、VerifyActivity)。取值规则:data.enterDate 优先,空则取 data.enterTime 兼容;Mock 演示值=当天 yyyy-MM-dd(实测显示 2026-09-27) |
16.2 真机走查记录(2026-09-27)
- 模拟引擎全流程:自动识别 → 三按钮结果卡 → 确认 → 绿色成功页,7 行信息完整(含进入日期当日),2 秒自动连刷正常。
- 失败弹窗车牌行:Mock 失败路径造数验证过(本轮后失败路径在现场不再触发,因核销恒成功)。
- 在线引擎取景页文案、引导框尺寸、引擎卡片绿色加粗,截图确认。
- 百度云 access_token 性质澄清(已向客户说明):应用级凭证,不绑设备,同一 AK/SK 可多台 MT95L 共用,额度/QPS 按 AK 累计;与离线 License 的「指纹+包名一机一授权」不同。Token 约 30 天有效(2026-10-25 前后到期,App 自动刷新)。
16.3 明天(及后续)待办
- 现场真实车牌实测:在线识别对准蓝/黄/新能源绿牌,确认 1~2s 回号(合成牌通路已验,真牌尚未现场拍)。
- 正式核销接口对接:后台 JSON 就绪后,①
VerifyApiFactory恢复/新增 HttpVerifyApi 分流;② 确认data.enterDate真实字段名(不一致则在 VerifyResult.fromJson 的兼容处加映射,只改数据入口,不动界面);③ 预约时间/预约人/证件号/预约类型/核销流水字段名一并对齐。 - 非阻塞遗留:离线 License 激活(控制台 SN 流程见第 11 章)、MT95L 扫描键映射、核销记录查询页、断网/错误 SK 的现场演示话术。
16.4 现场操作备忘
- 设备当前:引擎=在线识别,核销=模拟恒成功(任何引擎下点确认核销都成功),今日核销计数每天 0 点重置。
- 常用命令:构建
./gradlew assembleDebug --no-daemon;装机adb -s MT95LTFFD00631 install -r app/build/outputs/apk/debug/app-debug.apk;启动adb shell am start -n com.ectrip.checkincar/.ui.main.MainActivity(VerifyActivity/SettingsActivity 未导出,不能直接 am start,需从主页点入)。 - 坐标(720×1440):主页「车辆核销」约 (360,705)、「系统设置」约 (360,976);设置页引擎三段 y≈1237:模拟 x≈157 / 在线 x≈360 / 离线 x≈563。
17. 第四轮:对接真实核销后台 mqttScan(2026-09-28)
依据:
车牌订单核销模拟接口说明.md+ 后端源码(只读参考,严禁修改):/Volumes/USERDATA/ideaProjects/wts_vehicle_reservation(分支 tdos-dev)。
17.1 后台事实(源码核实)
- 入口
ParkingController.mockMqttScan(open-api/.../parking/controller/ParkingController.java)→CallbackService.processRecognizedPlate→ParkingService.queryVehicleInfo;通道业务模式=3 时调ParkingOrderHandler.parkingVerify执行真实订单消费(消费畅游通订单 + 写 OrderCheckRecord 核销流水 + MQTT 开闸)。 - 请求:
GET {baseUrl}/parking/mock/mqttScan?deviceCode=&licensePlate=&colorValue=;colorValue 0蓝/1绿/2黄(LicensePlateColorEnum);车牌明文需 URL 编码。 - 响应外壳
AjaxData{success:boolean, info:string, data:VehicleInfoVO}(不是 code/msg)。 - VehicleInfoVO:orderId、orderCode、licencePlate(Base64 明文车牌)、createTime(首次入场时间)、seq、vehicleType(1本地/2工作/3预约/4非预约/5非本地/10免票)、licenseColor(蓝/绿/黄)、carType、pass(isPass→JSON 键 pass)、secondEntry、entryTimes。
- 判定规则(ParkingService 427-462 行):模式 1 只查询;模式 2 查询并放行;模式 3 + vehicleType=3 + pass=true 才真实核销。
- 已核销预约车再扫:status=1 → pass=false、secondEntry=true、entryTimes=N;免票车 vehicleType=10 且 pass=false;非预约 4/5 直接 return(pass=false,不开闸不核销,但仍写一条 VehicleChannelRecord 通道流水)。
- 3 秒同设备同车牌 Redis 去重:
success=false, info="3秒内重复车牌识别消息已忽略"。 - 坑:
parkingVerify抛异常被 processBusinessMode catch 仅打印日志,HTTP 仍 success=true —— App 必须按 data 的 pass/vehicleType/secondEntry 组合判定,不能只看 success。 - 副作用警告:接口名为 mock 实为正式链路,真实预约车牌测一次即消费一单。
17.2 实施方案(用户已确认三项决策)
- 核销方式开关:设置页「核销配置」组新增分段「模拟核销|后台核销」(AppPrefs
verifyMode0/1,默认 0);VerifyApiFactory.create()按此分流,默认模拟防误核销。识别引擎三路由不受影响。 - 设备编号:AppPrefs 新增
deviceCode,默认 DEVICE001,设置页可改(即时保存);与手持机 SN 无关,须为后台已配置(device.status=1)且绑定模式 3 通道的编号。 - HttpVerifyApi 重写:
- GET,query 参数 deviceCode / licensePlate(URLEncoder UTF-8) / colorValue(blue→0、green→1、yellow→2,未知→0);连 5s 读 8s。
- 解析 AjaxData:success=false →
VerifyResult.fail(info)(原样展示后台中文原因)。 - 解析 data:orderCode→预约号、orderId、Base64 解码 licencePlate、vehicleType→中文类型、licenseColor、createTime(兼容字符串/毫秒)、seq、pass、secondEntry、entryTimes。
- 成功/失败判定只看 AjaxData.success:false → 红色失败页,原样显示 info(设备未配置/已禁用/3秒重复等);true → 成功页,data 九项字段全部显示(null 显示“-”):订单号 orderCode、车牌号(licencePlate Base64 解码)、首次入场时间 createTime、入场序号 seq、车辆类型(编码+中文)、车牌颜色、是否允许通行 pass(是/否)、是否重复入场 secondEntry(是/否)、当前入场次数 entryTimes。标题:预约车(type=3)=核销成功,其余=处理成功,副标题显示 info 原文。
- 成功页分行展示:真实模式固定九行(见上,全部显示);模拟模式保持现有 7 行演示信息(Mock 继续填模拟值)。
- 红色失败页保留后台 info 原文 + 车牌行。
17.3 联调与测试
- 安全通路测试(不消费订单):用无预约的假车牌调后台 → 应得 success=true、vehicleType=4、pass=false,验证网络/解析/判定(副作用仅一条通道流水日志)。
- 真实核销测试前提:后台提供绑定模式 3 通道的有效 deviceCode + 当天未核销预约车牌(一次性);核销后第二次扫同牌应拦截为重复入场。
- 回归:核销方式切回模拟时,全流程与第 16 章一致。
2026-09-28 现场网络勘察结果:192.168.50.1:8080 当前返回 404 / Server: Embedded Server/1.0.0(网关或道闸设备自带服务,非本系统 Spring 后台);192.168.50.1-254 全段 8080 扫描无其他服务在线,手持机(.70)正常。结论:后台服务今日未部署/未启动在本网段,真实链路待后台上线后联调;App 侧代码已就绪,后台不通时红色失败页显示「后台返回 HTTP 404 / 网络异常」,不闪退。另注:open-api 模块 resources 无 application.yml(配置走外部配置中心),端口与 context-path 以部署环境为准,当前按文档直连 8080 根路径。
17.4 待后台确认/遗留
- DEVICE001 是否真实存在且绑定模式 3 通道;不行则由后台管理员提供 deviceCode(设置页可直接改,无需发版)。
- 后台不返回预约人/证件/预约类型/核销流水,成功页暂不展示;后续后台加字段后仅在数据入口加映射。
- parkingVerify 失败对调用方不可见(HTTP 仍成功)——如需精确核销结果,需后台后续在 VehicleInfoVO 增加核销结果字段或提供核销流水查询接口。
18. 第六轮:核销确认机制 + 预约查询接口(2026-10-27)
背景:前几轮识别车牌后直接提交核销,无信息确认环节。用户要求增加确认弹窗,显示车牌号、驾驶员、预约日期、订单号等信息,确认后才执行核销。 核心问题:原核销接口
/tdos/consumeOrder_/appedit.do只返回{success, info}两字段,不含任何预约详情,无法在核销前展示。 解决方案:后台新增免登录预约查询接口,App 侧改为两步流程(先查后核销)。
18.1 后台新增查询接口(已上线)
请求地址:
GET /tdos/consumeOrder_/appquery.do
POST /tdos/consumeOrder_/appquery.do
请求参数:
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
| licensePlate | 是 | String | Base64 编码后的车牌号(UTF-8),GET 时需再 URL 编码 |
| callback | 否 | String | JSONP 回调函数名 |
Base64 编码规则:与核销接口 appedit.do 完全一致——Base64.encodeToString(plate.getBytes("UTF-8"), Base64.NO_WRAP),再用 URLEncoder.encode(encoded, "UTF-8") 处理 + / = 特殊字符。
成功响应(查到有效预约):
{
"success": true,
"info": "查询成功",
"data": {
"licensePlate": "粤A12345",
"orderId": 10001,
"orderCode": "202610270001",
"driverName": "张三",
"driverPhone": "13800138000",
"useStartDate": "2026-10-27",
"parkingAddress": "停车场A区",
"status": 0,
"enterTime": null
}
}
成功响应(无有效预约):
{
"success": false,
"info": "未查询到当天有效车牌预约信息",
"data": {
"licensePlate": "粤A12345"
}
}
错误响应:
{"success": false, "info": "车牌号Base64编码格式错误"}
返回字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| licensePlate | String | 解码后的车牌号 |
| orderId | Integer | 订单 ID |
| orderCode | String | 订单编号 |
| driverName | String | 驾驶员姓名 |
| driverPhone | String | 驾驶员手机号 |
| useStartDate | String | 预约使用日期 |
| parkingAddress | String | 停车地点 |
| status | Integer | 0=待入场,1=已入场核销 |
| enterTime | String/null | 入场核销时间,未核销时为 null |
接口特点:
- 免登录(与
appedit.do同属/consumeOrder_/路径,Shiro 白名单已配置) - 只查不消费订单,不会核销
- Spring MVC
.do后缀拦截规则生效(路径必须带.do) - 生产地址前缀:
https://wtspw.wtsykfwzx.com:8145/tdos - 开发测试地址前缀:
https://wtstest.jingqu.cn:9143/tdos
18.2 App 侧新增文件
| 文件 | 说明 |
|---|---|
model/QueryResult.java |
查询结果模型:ok(网络层) + success(业务层) + info + data 各字段 |
net/QueryApi.java |
查询接口定义:query(String plateNo) 返回 QueryResult |
net/HttpQueryApi.java |
真实后台实现:GET {baseUrl}/tdos/consumeOrder_/appquery.do,5s 超时 |
net/MockQueryApi.java |
模拟实现:固定 300ms 耗时,80% 概率返回模拟预约详情 |
res/layout/dialog_verify_confirm.xml |
确认弹窗自定义布局(车牌号/状态/驾驶员/手机号/订单号/预约日期/停车地点 + 取消/确认核销按钮) |
res/drawable/bg_btn_outline.xml |
取消按钮背景(描边样式) |
18.3 已有文件改动
| 文件 | 改动 |
|---|---|
net/VerifyApiFactory.java |
新增 createQuery() 方法,按 verifyMode 分流真实/模拟查询 |
ui/verify/VerifyActivity.java |
① onRecognized() 改为先调 queryAndShowConfirm() 再弹确认窗;② 取消自动核销(AUTO_VERIFY_ON 选项保留但不再自动提交);③ 新增 showConfirmDialog()(有预约)和 showNoReservationDialog()(无预约降级);④ 新增 confirmDialog 和 queryPending 状态变量;⑤ backToScanning() 和 onDestroy() 中清理弹窗状态 |
core/prefs/AppPrefs.java |
① 核销方式缺省值 VERIFY_MOCK → VERIFY_SERVER(新安装默认后台核销);② 识别引擎缺省值 ENGINE_MOCK → ENGINE_ONLINE(新安装默认在线识别);③ migrateEngineV2() 中旧默认值同步更新 |
18.4 核销流程(改造后)
拍照/识别车牌
│
▼
onRecognized(PlateInfo)
│
├─ 取消原 AUTO_VERIFY_ON 自动提交逻辑
│
▼
workExecutor.execute: QueryApi.query(车牌)
│
┌──────────────────────────────────┐
│ │
查询成功 + success=true 查询失败 或 success=false
(有有效预约) (无预约 / 网络异常)
│ │
▼ ▼
弹确认弹窗 Toast + 双按钮对话框
┌─────────────────────┐ ┌─────────────────┐
│ 🚗 粤A12345 │ │ 未查到预约信息 │
│ 📌 待入场/已入场 │ │ 是否继续核销? │
│ 👤 张三 │ │ [继续核销][取消] │
│ 📱 138****8000 │ └─────────────────┘
│ 📋 202610270001 │ │
│ 📅 2026-10-27 │ ┌──────┴──────┐
│ 🅿️ A区停车场 │ │ │
├─────────────────────┤ submitVerify backToScanning
│ [取消] [确认核销] │
└─────────────────────┘
│ │
backToScanning submitVerify(currentPlate)
│
▼
调 HttpVerifyApi.submit()
POST /tdos/consumeOrder_/appedit.do
│
▼
展示核销结果页
18.5 边界处理
| 场景 | 处理 |
|---|---|
| 查询成功 + status=0(待入场) | 正常弹窗,确认核销走原流程 |
| 查询成功 + status=1(已入场) | 弹窗状态标签红色「已入场」,点「确认核销」后二次确认 |
| 查询成功 + 无预约(success=false) | 提示"未查到预约信息",双按钮:继续核销 / 取消 |
| 查询超时 / 网络异常 | 降级为"未查到预约信息"处理,不阻塞核销 |
| 模拟核销模式 | MockQueryApi 返回模拟预约详情,流程与真实模式完全相同 |
| 手动输入车牌 | 同样走查询+确认弹窗流程,与识别的车牌无区别 |
| 查询中返回扫描态 | queryPending 标志位防止重复提交,用户返回后忽略查询结果 |
| 确认弹窗中按返回键 | setCancelable(true),取消弹窗回到扫描态 |
18.6 接口路径汇总
| 功能 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 预约查询 | GET/POST | /tdos/consumeOrder_/appquery.do |
免登录,只查不核销,Base64+URL 编码车牌 |
| 核销提交 | POST | /tdos/consumeOrder_/appedit.do |
免登录,真实消费订单,Base64 编码车牌 |
| 生产地址前缀 | — | https://wtspw.wtsykfwzx.com:8145/tdos |
.do 后缀由路径自行拼接 |
| 开发测试地址前缀 | — | https://wtstest.jingqu.cn:9143/tdos |
同上 |
18.7 关键代码参考
// HttpQueryApi.java — 查询接口实现
private static final String PATH = "/tdos/consumeOrder_/appquery.do";
public QueryResult query(String plateNo) throws Exception {
String base = AppPrefs.getBaseUrl(); // 自动去除末尾 /
String encodedPlate = Base64.encodeToString(
plateNo.getBytes("UTF-8"), Base64.NO_WRAP);
String url = base + PATH + "?licensePlate="
+ URLEncoder.encode(encodedPlate, "UTF-8");
// GET 请求,5s 超时
// 解析 JSON → QueryResult.fromServerJson(body)
}
// VerifyActivity.java — 识别成功后先查询再弹窗
private void onRecognized(PlateInfo info) {
currentPlate = info;
queryAndShowConfirm(info); // 替代原 autoVerify 逻辑
}
private void queryAndShowConfirm(PlateInfo plate) {
workExecutor.execute(() -> {
QueryResult result = VerifyApiFactory.createQuery().query(plate.number);
runOnUiThread(() -> {
if (result.ok && result.success) {
showConfirmDialog(result); // 有预约 → 弹确认窗
} else {
showNoReservationDialog(result.info); // 无预约 → 降级提示
}
});
});
}
18.8 2026-10-27 现场测试结果
- 缺省值调整:新安装 App 默认为「在线识别 + 后台核销 + 生产正式地址」
- 查询接口
.do后缀修正(初版漏写导致 404) - Base64 编码
NO_WRAP+ URL 编码处理+/=特殊字符 - 完整流程:拍照识别 → 查询 → 弹确认窗 → 确认核销 → 展示结果,测试通过