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 常见两类问题:
- 注入是异步的:页面脚本执行时 bridge 还没挂上
- 经典 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 的返回大致是:
status:0成功,1失败isLogin:0已登录,1未登录
这里最容易踩坑的是:把“未登录”当成“调用失败”。
举个真实场景:bridge 已经通了,callHandler 也回调了,但返回里同时出现 status 和 isLogin 的组合让你很难一眼判断“下一步该干嘛”。
我们最后把业务判断收敛成两条主线:
主线 A:App 已登录
myyule_app_isLogin确认已登录myyule_app_userInfo拿手机号- 调后端接口把手机号换成 H5 自己的
access_token/refresh_token
主线 B:App 未登录
myyule_app_isLogin明确未登录- 调
myyule_app_toLogin拉起原生登录页 - 路由守卫中止当前导航,等用户在原生完成登录
不要在中间夹太多“模糊状态”。用户已经在 App 里,登录这件事优先交给 App。
第三步:路由守卫不是“拦”,而是“分流”
免登逻辑挂在 src/plugins/permission.ts 的全局 beforeEach 里,这是合理的:它是页面跳转的统一入口。
这里的关键不是“未登录就拦”,而是想清楚 拦完之后让谁接手:
- 同步成功:继续进目标页
- 需要原生登录:中止当前路由,避免又跳进 H5
/login - bridge 不可用或同步失败:再回退到 H5 登录页
也就是说,路由守卫更像分流器:这个用户此刻应该进 H5、进原生登录,还是回退到普通网页登录。
我们踩过的一个典型坑:判断顺序
早期很容易写成:先看 status,只要不等于成功就直接 skipped,于是流程回退到 H5 登录页。
但业务上更合理的是:
- 只要
isLogin明确是未登录,就应该优先拉起原生登录 - 不要让
status先把“未登录”这种业务语义吃掉
这也是很多团队会误判“桥接失败”的原因:bridge 其实通了,回调也回来了,只是前端把返回解释错了。
写在最后
很多人觉得“H5 接 App”就是写几个 callHandler。
但真做过就知道,麻烦点永远在:
- iOS 和安卓初始化时序不同
- 返回值里同时混着“调用是否成功”和“业务是否登录”
- 路由守卫里要决定“让原生接手还是让 H5 接手”
把边界想清楚,代码反而不复杂。真正复杂的,是一开始把它想得太简单。