Frontend

手机 QQ 内置浏览器按返回键直接退出,试了四种办法都没用

1901 字约 7 分钟

摘要论坛是单页应用,在安卓手机 QQ 里从列表点进帖子,再按系统返回键,整个浏览器直接退出。用临时页面实测了 pushState、改锚点、整页跳转和 QQ 的关闭回调,都拦不住,最后在顶栏加了一个页面里的返回按钮。

我的论坛有不少访客是从 QQ 群里点链接进来的,也就是说,他们用的是手机 QQ 的内置浏览器。有一天我自己在手机 QQ 里打开论坛,从帖子列表点进一个帖子,看完想回列表,按了系统的返回键,结果整个浏览器直接退出了。

先看是不是自己的代码有问题

论坛是一个单页应用,换页的时候不会重新加载整个页面,而是用 pushState 往浏览器的历史记录里添加一条。从列表进详情页用的是普通的站内链接,会照常添加一条。详情页里,随着阅读楼层改变地址栏的地方用的是 replaceState,只替换当前这条,既不添加也不删除。进入页面时,也没有任何代码偷偷往历史里加东西。

所以在普通浏览器里,按返回应该能回到列表。我查了线上的访问记录,我的手机 QQ 是安卓上的 X5 内核,也就是腾讯自己的浏览器内核,不是系统的 WebView。

单页应用的“返回”是怎么回事

传统网页每换一页,浏览器就多一条历史记录,按返回就是回到上一条。单页应用不重新加载页面,所以得靠 pushState 手动往历史里加一条,让返回键依然有处可去。

如果某个浏览器在处理返回键时,不认这种手动加的记录,就会出问题。网上能找到安卓微信(同样是 X5 内核)上 pushState 和返回键配合不好的老文章,但它们讲的都是怎么拦截返回键,没有说到我这种情况。

先用别人的网站做个对照

怀疑是 QQ 的问题还是我的问题,最快的办法是找一个别人做的单页应用试试。助手请我在手机 QQ 里打开 react.dev,点顶部的 Learn 进入下一页,再按系统返回。

结果也直接退出了。这说明不是我的论坛写错了,QQ 对单页应用的返回键本身就有问题。

第一个方案和它的代价

最直接的想法是,只在安卓 QQ 的 X5 内核里,让站内链接改成整页跳转,每次换页都是真正的新页面,返回肯定正常。

助手把副作用量化了一下,没有急着做。网络流量不是问题,一个长帖子现在换页只取数据,压缩后约 11.5KB,整页跳转约 23.6KB,每次多约 12KB。真正的代价在体验上。

  • 从列表返回时,位置对不上。现在往下滚、加载了第 2、3 页,点进帖子再返回,列表会原样恢复。整页跳转之后,返回等于重新打开列表,只剩第一页。
  • 换页变慢,还会闪一下白屏。中低端的安卓机上,每次大概多等零点几秒到一秒多。
  • 不只是链接,发帖后跳转、表单提交后跳转这些代码里的 navigate() 调用也要处理,约 10 个文件里有。

受影响的人也不少。最近一周的访问记录里,安卓 QQ 用 X5 内核的约 400 人,是人数第二多的一类。

第二个思路,试验页

另一个思路是,QQ 提供一套给网页调用的 JavaScript 接口,旧文档里有“设置关闭前的回调”(mqq.ui.setOnCloseHandler)。如果还能用,就可以接管返回键,站内有上一页就后退,没有才真的关闭。这样能保留单页应用的体验,上面的副作用都没有。

但这个接口现在还开不开放给普通网站,不确定。所以助手搭了一个临时的试验页,放在我本机,通过 Cloudflare 的临时隧道让手机能访问,每一步的结果都存在浏览器的存储里,退出后重新打开还能看到。

试验分三轮,每轮从第一页开始,点两次,再按系统返回键。

轮次做法结果
A用 pushState 换页,就是单页应用的正常做法直接退出
B只改地址栏的 # 锚点直接退出
C整页跳转,作为对照直接退出
关闭回调加载 QQ 的脚本并注册关闭前的回调脚本能加载,mqq 对象存在,回调从未触发

C 轮是关键。整页跳转会产生真正的历史记录,任何浏览器按返回都会回到上一页,连这个都退出,说明在我这台手机上,QQ 在系统返回键被按下时,根本不看历史记录,直接关闭了浏览器。助手之前提的整页跳转方案也随之作废了。

猜测的原因,没有证实

我的手机是安卓 16,QQ 里的内置浏览器顶栏左上角也没有它自己的返回箭头。助手的猜测是,新版安卓默认启用了预测性返回,系统不再调用应用里老的返回键处理方法。如果 QQ 的内置浏览器还是用老办法实现“能后退就后退,不能就关闭”,在安卓 16 上这段逻辑就被跳过了,结果就是直接关闭。

这只是推测。要证实,得找一个用旧版安卓的群友,试一下同样的操作。如果旧版上能正常返回,基本就能坐实。我在文档里也特意只写了“部分安卓手机上的 QQ”,没写“安卓 16”,因为还没有证据。

最后的做法,页面里自己放一个返回按钮

既然系统的返回键拦不住,能做的就是在页面里放一个返回按钮,让用户有一条不依赖系统返回键的路。

我把它放在手机顶栏的最左边,菜单按钮前面,只在窄屏上显示,电脑浏览器本来就有返回按钮,再加一个只是重复。首页不显示,登录页和管理后台也不加。

它的行为是这样的。

  • 站内有上一页的时候,点它等于浏览器后退,列表加载过的几页和滚动位置都保留。判断依据是 React Router 记在历史状态里的序号是否大于 0,不用另外记。
  • 站内没有上一页,比如从群里的链接直接打开的帖子,就去它所在的版块,其他页面去首页。
  • 它是一个普通的链接,没加载脚本时也能用。

所有手机用户都显示,我没有按浏览器标识去判断是不是安卓 QQ。按标识判断本来就不可靠,iOS 上的 QQ 和其他内置浏览器的返回也未必好用,统一显示更省事,界面也一致。

验证是在 360 像素宽度下用自动化浏览器点出来的。首页往下滚、加载到 13 条后进入帖子,点返回,还是 13 条,滚动位置和离开时一样。直接打开帖子点返回,去了所属版块。搜索页点返回,去了首页。顶栏多了一个按钮,右边已有四个图标,也检查过放得下。

这个问题没有真正解决

系统返回键在那些手机上还是会直接退出,这是 QQ 自己的行为,网页这边无能为力,只能靠大家习惯点页面里的按钮。这个办法是补救,不是修复。

评论