安卓一把过、iOS 总翻车:WebViewJavascriptBridge 实战复盘

2 阅读4分钟

H5 接 App 登录:安卓一把过,iOS 为什么总出幺蛾子

最近做了一个 H5 嵌入 App 的登录联动,目标听起来很简单:

  • App 里已经登录:H5 直接复用登录态,不让用户再输一次账号密码
  • App 里没登录:H5 不跳自己的登录页,而是拉起 App 原生登录页
  • 用户在原生登录成功后,再回到 H5,继续业务流程

真正做起来,最容易出问题的地方不是接口,也不是路由,而是 H5 和 App 之间那层桥接。尤其是 iOS。

这篇文章结合项目里的实现,把这条链路讲清楚:桥怎么拿、登录怎么判、路由怎么接,以及我们踩过的一个典型坑。


这不是“网页登录”,而是“借 App 登录”

嵌入 App 之后,H5 自己并不知道 App 里的登录态,必须靠 App 主动告诉它。

所以这不是普通网页的认证流程,而是一套:

H5 向原生查询状态 → 调用原生能力 → 再同步 H5 自己的 token

在我们项目里,桥接封装在 src/utils/myyule-app-bridge.ts,业务编排放在 src/utils/myyule-app-auth.ts,路由入口在 src/plugins/permission.ts


第一步:先把 WebViewJavascriptBridge 拿到手

桥接的核心就是拿到 window.WebViewJavascriptBridge,然后 callHandler 调原生。

安卓通常比较顺:很多实现会较早把 bridge 挂到 window 上,页面脚本一跑就能读到。

iOS 常见两类问题:

  1. 注入是异步的:页面脚本执行时 bridge 还没挂上
  2. 经典 WebViewJavascriptBridge 需要握手:H5 要先触发一次 wvjbscheme://__BRIDGE_LOADED__,原生才会把 bridge 建起来

所以我们在 waitWebViewJavascriptBridge 里做了三件事:

  • 如果已经有 bridge,立刻返回
  • 监听 WebViewJavascriptBridgeReady
  • 同时做一次短轮询兜底,避免“只等事件但事件没发”的边缘情况
  • 再补上 iOS 经典握手:插入隐藏 iframe 触发 wvjbscheme://__BRIDGE_LOADED__

这一步解决的是:不是“永远拿不到”,而是“来得比你判断得晚”


第二步:协议怎么读,比“bridge 通不通”更关键

原生侧一般会提供三个能力(handler 名与请求体里的 type 对齐):

  • myyule_app_isLogin:查询是否登录
  • myyule_app_userInfo:拿手机号等信息
  • myyule_app_toLogin:跳原生登录页

文档里对 myyule_app_isLogin 的返回大致是:

  • status0 成功,1 失败
  • isLogin0 已登录,1 未登录

这里最容易踩坑的是:把“未登录”当成“调用失败”

举个真实场景:bridge 已经通了,callHandler 也回调了,但返回里同时出现 statusisLogin 的组合让你很难一眼判断“下一步该干嘛”。

我们最后把业务判断收敛成两条主线:

主线 A:App 已登录

  1. myyule_app_isLogin 确认已登录
  2. myyule_app_userInfo 拿手机号
  3. 调后端接口把手机号换成 H5 自己的 access_token / refresh_token

主线 B:App 未登录

  1. myyule_app_isLogin 明确未登录
  2. myyule_app_toLogin 拉起原生登录页
  3. 路由守卫中止当前导航,等用户在原生完成登录

不要在中间夹太多“模糊状态”。用户已经在 App 里,登录这件事优先交给 App。


第三步:路由守卫不是“拦”,而是“分流”

免登逻辑挂在 src/plugins/permission.ts 的全局 beforeEach 里,这是合理的:它是页面跳转的统一入口。

这里的关键不是“未登录就拦”,而是想清楚 拦完之后让谁接手

  • 同步成功:继续进目标页
  • 需要原生登录:中止当前路由,避免又跳进 H5 /login
  • bridge 不可用或同步失败:再回退到 H5 登录页

也就是说,路由守卫更像分流器:这个用户此刻应该进 H5、进原生登录,还是回退到普通网页登录


我们踩过的一个典型坑:判断顺序

早期很容易写成:先看 status,只要不等于成功就直接 skipped,于是流程回退到 H5 登录页。

但业务上更合理的是:

  • 只要 isLogin 明确是未登录,就应该优先拉起原生登录
  • 不要让 status 先把“未登录”这种业务语义吃掉

这也是很多团队会误判“桥接失败”的原因:bridge 其实通了,回调也回来了,只是前端把返回解释错了。


写在最后

很多人觉得“H5 接 App”就是写几个 callHandler

但真做过就知道,麻烦点永远在:

  • iOS 和安卓初始化时序不同
  • 返回值里同时混着“调用是否成功”和“业务是否登录”
  • 路由守卫里要决定“让原生接手还是让 H5 接手”

把边界想清楚,代码反而不复杂。真正复杂的,是一开始把它想得太简单。