HarmonyOS 6商城开发学习:活体检测后的路由断层——用 callback + NavPathStack 把 Router 限制掰回来
熟悉我们购物比价应用的朋友一定记得,支付环节里有个绕不开的流程——人脸活体核身。用户改支付密码、大额转账、开通免密、首次绑定银行卡,都得先过一道活体检测。华为提供了 startLivenessDetection这个一站式入口,参数里还能配 successfulRouteUrl和 failedRouteUrl,看起来能"检测完自动跳"。但真接进我们商城的 Navigation 架构时,第一时间就撞墙了——
检测完了没跳我预期的页面,反而跳去了某个"默认页",像是首页或者登录页。
查了官方文档才搞明白:startLivenessDetection自带的路由跳转只支持 Router 方式,不支持 Navigation。而我们商城首页早已经切到 Navigation + NavPathStack的单栈架构了,Router 那一整套基本废弃。这篇文章就把这个"Router 到 Navigation 的断层"怎么补上,完整捋一遍。
问题场景:支付前的那道人脸核身
商城里几个典型触发点:
|
场景 |
触发时机 |
成功后要去哪 |
|---|---|---|
|
修改支付密码 |
验证旧密码后 |
设置新密码页 |
|
大额转账确认 |
输入金额后 |
转账结果页 |
|
开通免密支付 |
勾选协议后 |
免密开通成功页 |
|
高风险下单 |
风控拦截后 |
二次确认页 |
这几个场景共用同一套活体检测入口,但成功/失败后要去的页面各不相同,而且都在 Navigation 栈里。官方 successfulRouteUrl那种"写死一个 url"的玩法,对我们来说太僵了——更何况它还不认 Navigation。
核心 API 先对齐
|
API / 类型 |
来自哪个 Kit |
一句话用途 |
|---|---|---|
|
|
|
拉起活体检测页,支持 Promise 拿跳转结果、callback 拿检测结果 |
|
|
同上 |
检测配置,含 |
|
|
同上 |
callback 方式目前只认这个 mode,配错就跳默认页 |
|
|
|
Navigation 架构下的页面跳转,是我们真正要用的 |
官方文档里那个 Q&A 点得很死:
使用 callback 回调获取检测结果的
startLivenessDetection,当前只适用于RouteRedirectionMode.BACK_MODE跳转模式。检查入参InteractiveLivenessConfig.routeMode是不是BACK_MODE,否则就会跳到默认页而不是你期望的页。
这是我们踩的第一个坑,也是全文最关键的一句话。
实现思路(两段式)
官方给的思路其实很清楚,分两段走:
-
活体检测本身 → 用
startLivenessDetection拉起,入参InteractiveLivenessConfig里可以配successfulRouteUrl/failedRouteUrl,但我们不指望它们跳 Navigation 页,只把它们当一个"兜底返回"用(或者干脆配个空路由也行)。 -
检测结果 → 自己跳 → 在
startLivenessDetection的 callback 里拿检测结果(成功/失败/超时/用户取消),再根据这个结果手动调NavPathStack.pushPath()或replacePath() 跳到 Navigation 里对应的成功页或失败页。
这样就绕开了 Router 限制,而且成功/失败页可以跟业务状态灵活绑定,不必写死 url。
最少必要代码骨架
只留关键片段,不铺完整页面。
// utils/LivenessHelper.ets
import { faceLiveness } from '@kit.VisionKit'
import { BusinessError } from '@ohos.base'
export class LivenessHelper {
/**
* 拉起活体检测,检测完后在 callback 里走 Navigation 跳转
* @param nav NavPathStack,从页面里传进来
* @param onResult 业务侧拿到结果后可再做些埋点/风控上报
*/
static startLiveness(nav: NavPathStack, onResult?: (success: boolean) => void) {
const config: faceLiveness.InteractiveLivenessConfig = {
// 成功/失败的 url 这里可以随便配一个合法路由(Router 模式下才有意义)
// 因为我们走 callback + Navigation,这两个字段实际不会被用到
successfulRouteUrl: 'pages/dummySuccess',
failedRouteUrl: 'pages/dummyFailed',
routeMode: faceLiveness.RouteRedirectionMode.BACK_MODE, // ⚠️ 必须 BACK_MODE
}
// startLivenessDetection 有两个"出口":
// - Promise 返回的是"跳转结果"(Router 那边的)
// - callback 返回的是"检测结果"(活体成功/失败/超时…)
faceLiveness.startLivenessDetection(config, (err: BusinessError, result) => {
if (err) {
console.error(`活体检测异常: ${err.code} ${err.message}`)
// 异常一般也归到"失败"分支
nav.pushPath({ name: 'LivenessFailed', param: { reason: 'detect_error' } })
onResult?.(false)
return
}
// result 里能拿到活体是否通过、是否超时、是否用户取消等
const passed = result.isSuccess ?? false
if (passed) {
// 走 Navigation 成功页,比如"设置新密码"或"转账确认"
nav.pushPath({ name: 'LivenessSuccess', param: { from: 'payment_modify' } })
} else {
// 走 Navigation 失败页,可带 reason(超时/人脸不匹配/用户取消)
nav.pushPath({ name: 'LivenessFailed', param: { reason: 'not_match' } })
}
onResult?.(passed)
})
}
}
页面侧调用的样子(极简):
// 在"修改支付密码"页里
Button('开始人脸核身')
.onClick(() => {
LivenessHelper.startLiveness(this.navStack, (success) => {
// 这里还能补埋点:支付密码修改-活体通过率
})
})
注意:
nav是从页面里this.pageStack或this.navPathStack传下来的,确保你的 Navigation 根节点已经把NavPathStack挂好,并且'LivenessSuccess'/'LivenessFailed'两个 name 在Navigation的navDestination里已经注册了 Builder,否则 pushPath 会没反应。
遇到的问题与解决方案
问题 1:检测完跳去了"默认页",不是我 push 的那个
这是官方 Q&A 里点名最多的。routeMode没设成 BACK_MODE时,即使你配了 successfulRouteUrl,系统也可能不按你预期走,甚至直接回某个默认页(跟你的 Navigation 根页重合时就更迷惑)。
解法:InteractiveLivenessConfig.routeMode = RouteRedirectionMode.BACK_MODE,然后用 callback 接管跳转,url 字段随便填(反正不走 Router)。
问题 2:Navigation 里没注册 name,pushPath 没反应
nav.pushPath({ name: 'LivenessSuccess' })的 name 必须在 Navigation的 navDestination里注册过 Builder,否则静默失败。
解法:检查根 Navigation 的注册表:
Navigation(this.navStack) {
// 主页
NavDestination({ name: 'Home' }) { ... }
// 活体相关
NavDestination({ name: 'LivenessSuccess' }) { LivenessSuccessPage() }
NavDestination({ name: 'LivenessFailed' }) { LivenessFailedPage() }
}
问题 3:成功页应该 push 还是 replace?
支付密码场景:"核身 → 设置新密码 → 完成",用户按返回应该回到"账户安全"页,而不是再回到活体页。所以活体 → 成功页用 replacePath 更合理;如果是"转账确认"这种线性流程,成功后再 push 结果页也 OK,看业务。
// 改密场景推荐 replace
nav.replacePath({ name: 'SetNewPassword' })
问题 4:callback 和 Promise 怎么分工
startLivenessDetection(config, callback)这个重载里:
-
Promise → 返回的是"Router 跳转的结果"(如果你 routeMode 配了别的模式、且指望系统跳的话)
-
callback → 返回的是"活体检测本身的结果"(通过/不通过/超时/取消)
我们这套玩法的核心就是弃 Promise 走 callback,因为 Router 那侧我们不用。
总结
|
要点 |
实现方式 |
|---|---|
|
突破 Router 限制 |
|
|
routeMode 必须配对 |
|
|
成功/失败页灵活绑定 |
callback 里按 |
|
push vs replace |
改密/开通类用 replace,避免返回时又落到活体页 |
|
Navigation 注册别忘了 |
|
改完这套后,我们商城的"人脸核身 → Navigation 目标页"链路终于顺了——活体检测该干的事归 VisionKit,跳转该归 Navigation 归 Navigation,两边各司其职,不再互相掣肘。如果你也在做购物比价/支付类应用,碰到 startLivenessDetection跳不过 Navigation 的尴尬,这套 callback 接管思路可以直接套。
更多推荐
所有评论(0)