熟悉我们购物比价应用的朋友一定记得,支付环节里有个绕不开的流程——人脸活体核身。用户改支付密码、大额转账、开通免密、首次绑定银行卡,都得先过一道活体检测。华为提供了 startLivenessDetection这个一站式入口,参数里还能配 successfulRouteUrlfailedRouteUrl,看起来能"检测完自动跳"。但真接进我们商城的 Navigation 架构时,第一时间就撞墙了——

检测完了没跳我预期的页面,反而跳去了某个"默认页",像是首页或者登录页。

查了官方文档才搞明白:startLivenessDetection自带的路由跳转只支持 Router 方式,不支持 Navigation。而我们商城首页早已经切到 Navigation + NavPathStack的单栈架构了,Router 那一整套基本废弃。这篇文章就把这个"Router 到 Navigation 的断层"怎么补上,完整捋一遍。

问题场景:支付前的那道人脸核身

商城里几个典型触发点:

场景

触发时机

成功后要去哪

修改支付密码

验证旧密码后

设置新密码页

大额转账确认

输入金额后

转账结果页

开通免密支付

勾选协议后

免密开通成功页

高风险下单

风控拦截后

二次确认页

这几个场景共用同一套活体检测入口,但成功/失败后要去的页面各不相同,而且都在 Navigation 栈里。官方 successfulRouteUrl那种"写死一个 url"的玩法,对我们来说太僵了——更何况它还不认 Navigation。

核心 API 先对齐

API / 类型

来自哪个 Kit

一句话用途

faceLiveness.startLivenessDetection(config, callback?)

@kit.VisionKit(人脸活体)

拉起活体检测页,支持 Promise 拿跳转结果、callback 拿检测结果

InteractiveLivenessConfig

同上

检测配置,含 successfulRouteUrl/ failedRouteUrl/ routeMode

RouteRedirectionMode.BACK_MODE

同上

callback 方式目前只认这个 mode,配错就跳默认页

NavPathStack.pushPath()/ replacePath()

@kit.ArkUI

Navigation 架构下的页面跳转,是我们真正要用的

官方文档里那个 Q&A 点得很死:

使用 callback 回调获取检测结果的 startLivenessDetection当前只适用于 RouteRedirectionMode.BACK_MODE跳转模式。检查入参 InteractiveLivenessConfig.routeMode是不是 BACK_MODE,否则就会跳到默认页而不是你期望的页。

这是我们踩的第一个坑,也是全文最关键的一句话。

实现思路(两段式)

官方给的思路其实很清楚,分两段走:

  1. 活体检测本身​ → 用 startLivenessDetection拉起,入参 InteractiveLivenessConfig里可以配 successfulRouteUrl/failedRouteUrl,但我们不指望它们跳 Navigation 页,只把它们当一个"兜底返回"用(或者干脆配个空路由也行)。

  2. 检测结果 → 自己跳​ → 在 startLivenessDetectioncallback​ 里拿检测结果(成功/失败/超时/用户取消),再根据这个结果手动调 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.pageStackthis.navPathStack传下来的,确保你的 Navigation 根节点已经把 NavPathStack挂好,并且 'LivenessSuccess'/ 'LivenessFailed'两个 name 在 NavigationnavDestination里已经注册了 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 必须在 NavigationnavDestination里注册过 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 限制

callback拿检测结果 + NavPathStack.pushPath/replacePath手动跳

routeMode 必须配对

InteractiveLivenessConfig.routeMode = BACK_MODE,否则跳默认页

成功/失败页灵活绑定

callback 里按 result.isSuccess分两支 push,不同业务场景传不同 param

push vs replace

改密/开通类用 replace,避免返回时又落到活体页

Navigation 注册别忘了

navDestination里要把成功/失败页的 name 注册 Builder

改完这套后,我们商城的"人脸核身 → Navigation 目标页"链路终于顺了——活体检测该干的事归 VisionKit,跳转该归 Navigation 归 Navigation,两边各司其职,不再互相掣肘。如果你也在做购物比价/支付类应用,碰到 startLivenessDetection跳不过 Navigation 的尴尬,这套 callback 接管思路可以直接套。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐