计划日期:2026-09-01
项目仓库:/Users/liang/AndroidStudioProjects/CusumptionMachine
当前分支:fix_pay_by_face
设计依据:2026-09-01-face-recognition-runtime-encapsulation-design.md
状态:一期、二期自动实施完成,真机待验
一期在不修改 7 个业务页面现有调用方式、不改变人脸识别弹框交互、不改变支付和核验业务的前提下完成内部封装;二期只统一页面弹框创建入口,继续保持原业务监听器、支付和核验逻辑不变。整体改造包括:
删除人脸识别弹框模型失败后的应用重启和进程结束。
使用进程级 FaceRecognitionRuntime 统一模型初始化状态和并发请求。
使用单次 FaceRecognitionSession 管理相机、倒计时、识别回调和资源释放。
保持 FaceRecognitionPop 构造方法、监听器、UI、重试和确认操作不变。
保证菜单消费识别异常后订单信息不变,并可重新点击支付。
不创建或切换 Git 分支,不创建工作树。
不执行 Git 暂存、提交或推送。
不修改 7 个页面的支付或核验接口。
不改弹框布局和资源文案。
不改支付成功、支付失败和订单清空规则。
启动页仍负责许可证异常、数据库初始化和页面导航。
Runtime 仅统一模型运行状态,并记录本地人脸库是否已由启动流程装载。
代码使用 UTF-8 无 BOM,新增类增加职责和生命周期说明。
回归测试与基线构建 ↓ FaceRecognitionRuntime ↓ LaunchActivity 兼容接入 ↓ FaceRecognitionSession ↓ FaceRecognitionPop 内部替换 ↓ 构建、静态不变量检查、真机回归清单 ↓ 变更记录
说明:先用 JVM 单元测试定义 Runtime 的单次初始化、并发等待、失败重试、旧回调隔离和页面销毁取消规则。
验收条件:
首次请求只启动一次初始化。
初始化中到达的请求共享同一次结果。
成功后再次请求直接成功。
失败后允许下一次主动请求重新初始化。
旧初始化任务的迟到回调不能覆盖新状态。
已取消请求不再持有或分发页面回调,但共享初始化仍可正常完成。
验证:
./gradlew :app:testDebugUnitTest --tests "com.cpt.cusumption.faceRecognition.runtime.FaceRecognitionRuntimeTest"
预计文件:
app/src/test/java/com/cpt/cusumption/faceRecognition/runtime/FaceRecognitionRuntimeTest.java
app/src/main/java/com/cpt/cusumption/faceRecognition/runtime/FaceRecognitionRuntime.java
app/src/main/java/com/cpt/cusumption/faceRecognition/runtime/FaceRecognitionRuntimeState.java
依赖:无。
说明:封装现有 InitFaceModelManager,只持有 ApplicationContext,合并并发初始化请求;保留启动页的许可证和失败处理回调。
验收条件:
Runtime 不持有 Activity、Fragment 或 View。
SDK 已就绪时不重复加载模型。
初始化成功、失败回调均只分发一次。
每个调用方持有可取消的 InitRequest,取消不影响其他调用方共享初始化。
Runtime 不执行页面跳转、应用重启或进程结束。
验证:执行任务 1 的单元测试。
依赖:任务 1。
说明:只替换 initCustomFaceModel() 中直接创建 InitFaceModelManager 的部分,原许可证状态更新、错误处理、数据库初始化和导航保持原顺序。
验收条件:
模型已就绪时继续进入原数据库加载流程。
模型成功后继续执行原状态更新和数据库加载。
模型失败后继续执行原启动页许可证或重试处理。
数据库装载完成后通知 Runtime,业务弹框不再重复装载。
启动页销毁时取消自身初始化回调引用,不中断共享初始化任务。
预计文件:
app/src/main/java/com/cpt/cusumption/LaunchActivity.java
依赖:任务 2。
说明:使用纯 Java 会话门闩测试重复匹配、停止后回调和旧会话回调的过滤规则。
验收条件:
同一会话只接收一次匹配结果。
会话停止后拒绝后续回调。
重新开始后旧会话回调失效。
当前新会话仍可正常接收结果。
验证:
./gradlew :app:testDebugUnitTest --tests "com.cpt.cusumption.faceRecognition.session.FaceRecognitionSessionGateTest"
预计文件:
app/src/test/java/com/cpt/cusumption/faceRecognition/session/FaceRecognitionSessionGateTest.java
app/src/main/java/com/cpt/cusumption/faceRecognition/session/FaceRecognitionSessionGate.java
依赖:无。
说明:从弹框抽出 Runtime 就绪等待、相机方向、相机启动停止、倒计时、检测调用、匹配防重复和异步任务释放。
验收条件:
Runtime 未就绪时不启动相机检测。
Runtime 成功后才启动原相机预览和倒计时。
匹配到用户后停止相机和倒计时,并返回原 SDK User。
重试复用当前弹框并启动新会话。
超时、异常和销毁均幂等释放资源。
Session 停止时取消 Runtime 等待请求,避免进程级单例保留已关闭弹框回调。
预计文件:
app/src/main/java/com/cpt/cusumption/faceRecognition/session/FaceRecognitionSession.java
app/src/main/java/com/cpt/cusumption/faceRecognition/session/FaceRecognitionSessionCallback.java
依赖:任务 2、任务 4。
说明:保持公开构造方法、监听器和 UI 不变,内部改为创建 Session;删除模型初始化、重复数据库装载、应用重启和直接相机检测代码。
验收条件:
7 个业务页面不需要修改调用方式。
识别结果展示、“重试”“确认”“取消”行为不变。
模型异常通过原结束通道安全关闭弹框。
restartSelf() 和弹框内 Process.killProcess() 完全移除。
onDestroyView() 停止 Session。
预计文件:
app/src/main/java/com/cpt/cusumption/view/FaceRecognitionPop.java
依赖:任务 5。
说明:执行完整 Debug 单元测试和 APK 构建,并静态检查 7 个页面入口和菜单订单清空逻辑未被改动。
验收条件:
Debug 单元测试通过。
Debug APK 构建通过。
FaceRecognitionPop 仍有原构造方法和原三个业务回调。
7 个页面调用位置数量和业务方法保持不变。
人脸识别代码不调用 mSelectDishList.clear()。
弹框代码不存在应用重启和杀进程。
验证:
JAVA_HOME=/Users/liang/Library/Java/JavaVirtualMachines/corretto-1.8.0_482/Contents/Home \ sh ./gradlew :app:testDebugUnitTest :app:assembleDebug
依赖:任务 1 至任务 6。
说明:将实际修改文件、关键实现、自动验证结果、真机待验项和剩余风险写入 change_records,生成同名 Markdown 与 HTML。
验收条件:
变更记录与实际差异一致。
明确区分自动验证完成项和真机待验项。
不宣称未执行的真机测试已经通过。
依赖:任务 7。
Runtime 单元测试全部通过。
LaunchActivity 原失败处理和导航顺序未改变。
没有引入 Activity 或 View 静态引用。
会话门闩测试全部通过。
弹框公开接口未改变。
模型失败路径不存在应用重启。
超时、取消和确认关闭时机保持不变。
全部 Debug 单元测试通过。
Debug APK 构建通过。
静态业务不变量检查通过。
Git 差异只包含本次范围文件和对应文档记录。
自动构建不能替代摄像头和双屏真机验证,代码完成后仍需在设备执行:
菜单消费单屏识别成功、重试、确认、超时、取消。
菜单消费双屏识别成功、重试、确认、超时、取消。
模型失败时弹框关闭、不白屏、不返回首页、订单保留。
选择菜品和自定义金额在失败后保持不变。
重新点击支付可以再次打开弹框并识别。
其余 6 个页面逐一验证成功、超时和取消。
连续打开关闭弹框,摄像头可以再次正常启动。
代码落地完成要求:自动测试和 Debug 构建通过、静态业务边界符合设计、变更记录已沉淀。摄像头、双屏和模型故障注入属于真机验收项;未执行前必须明确标记为待验,不能视为已经完成。
已新增进程级 FaceRecognitionRuntime 和运行状态枚举。
Runtime 初始化请求支持生命周期取消,启动页和 Session 销毁时会解除各自回调引用。
已新增单次 FaceRecognitionSession、内部回调和结果门闩。
已将 LaunchActivity 的自定义模型初始化兼容接入 Runtime,原许可证、数据库和导航流程保留。
已将 FaceRecognitionPop 的模型、数据库、相机、倒计时和检测工作移出弹框。
已删除弹框内 restartSelf()、Process.killProcess() 和“即将重启应用”路径。
FaceRecognitionPop 原构造方法、User 成功结果和三个业务回调保持不变。
一期未修改 7 个业务页面;二期仅迁移其弹框创建入口,原业务监听器、支付和核验方法没有修改。
当前为 7 个业务页面、7 个 Launcher 调用点;页面内直接构造为 0,Launcher 内统一构造为 1。
Runtime 新增 9 个单元测试,Session 门闩新增 4 个单元测试。
Launcher 与请求新增 8 个单元测试。
全项目 Debug 单元测试共 49 个,失败 0、错误 0、跳过 0。
JDK 8 下 :app:testDebugUnitTest :app:assembleDebug 执行成功。
摄像头、双屏、模型故障注入和菜单订单真机验证仍待设备执行。
已新增 FaceRecognitionLauncher、FaceRecognitionLaunchGate、FaceRecognitionRequest 和 FaceRecognitionScene。
已统一 Activity 生命周期、Fragment 状态保存和重复弹框门禁。
已为 7 个页面建立明确的场景、金额、副屏、确认文案和 Tag 参数映射。
已将 8 个页面内直接构造分支收敛为 Launcher 内 1 个构造位置。
MainActivity 仍传递原副屏预览和总金额。
GuoxinFacePayActivity 与 OrderVerifyActivity 仍使用原确认文案。
7 个页面原成功、结束、取消回调及支付、核验调用保持不变。
Launcher 拒绝展示且没有活动弹框时,自由/定额页面恢复支付门禁,菜单页面恢复副屏状态。
二期独立实施计划:2026-09-01-face-recognition-launcher-phase2-implementation-plan.md。