背景
现今中小型的互联网公司的运营手段多依赖网页或小程序的展现形式。小程序可以说是微信圈内对用户粘性较高的网页应用,有较好的推广效果。
网页则被互联网行业称为 H5 ,拥有访问门槛低、功能可控度高等特性,非常适合用于产品的迭代。同是也是公司探索市场新模式,寻找产品卖点,布局营销宣传的工具。
现有架构
在 APP 架构中,网页的存在,能很好以弥补开发周期长,迭代慢的缺点。使其成为强而有力的工具,帮助功能快速实现落地。在这种架构中,网页应用依然离不开其宿主浏览器(WebView)。
围绕着用户进行设计的产品,用户系统作为支撑业务的底层十分重要。针对其特殊的环境,团队的工程师对APP 架构中的网页应用支持进行了如下设计:
- 建立 APP 与网页应用的通道,提供网页主动调用的函数;
- 搭建业务功能,如获取用户信息和登录的功能;
产生异常的情况
提供的登录功能主要包括,支持调起原生登录页,且包含登录成功的回调。但受限于时间和技术水平,登录回调被设计成只有登录成功,才刷新当前网页。
现有一个推向微信平台和 APP 网页项目,我在开发调试过程中,发现安卓端登录成功后面刷新功能失效的情况,但苹果端正常。看起来情况特殊,调查刻不容缓。
登录成功后页面没有刷新(左图)。而功能测试案例中的页面则正常,获取到用户 ID(右图)
正常和异常的结果均在同一版本(7.3.2)APP 中得出,那么可以简单地判定为开发项目存在的差异,导致了刷新功能失效。
定位导致异常的原因
开发中的项目项目使用了基于 Vue.js 的 Nuxt.js 框架,并启用了路由和状态管理功能。而页面路由的存在,访问的地址比普通的地址多出部分内容,长成这样:
http://domain.my/index.html/#/mypage
多出来的内容通常以 #
开头。#
号字符读作 hash ,称为 hash mark。
图为 fragment 位于 URI 结构中的位置
#
在 URI(统一资源定位符)中被称作 fragment ,用来标识次级资源。在网页制作中,最广泛的用途是被制作页面锚点。它有两个值得关注特点:
- fragment 的值并不像 query 查询参数那样,它的值并不会随着 URL 发送到 HTTP 服务器;
- fragment 值的改变不会触发浏览器刷新页面,但是会生成浏览历史;
Clients are not supposed to send URI fragments to servers when they retrieve a document, and without help from a local application (see below) fragments do not participate in HTTP redirections.
前端利用 fragment 实现的应用路由
得益于上述的两种特性,配合浏览器提供的 window.location 对象(BOM),现代前端框架如 Vue.js 实现了页面上的路由。
导致页面不刷新的原因很可能是页面存在 fragment ,但不禁怀疑“刷新当前网页”的功能当初是如何定义的。
功能设计缺陷
带着疑问,翻阅了安卓端项目对应登录功能的源代码。
public void onEventMainThread(LoginSuccess success) {
webView.loadUrl(originUrl); // not working in hash on the url
historyUrls.clear();
}
刷新功能被设计成重新加载原先的 URL ,而不是采用组件提供的重载方式(类似浏览器的重新加载按钮)。
对于不含有 #
的普通地址来,重新加载 URL 即可触发浏览器刷新。这时如果加载了含有 #
的地址,重新加载是不会触发浏览器刷新的。
安卓开发者文档中提供了重新加载当前 URL 的方法 webview.reload
,二话不说,修改源代码,进行测试。
public void onEventMainThread(LoginSuccess success) {
webView.reload(); // using new method replace with loadUrl
historyUrls.clear();
}
将代码修改成新的加载方式,网页顺利刷新。
更优雅的解决方案
原本登录业务的实现方案受团队和时间的限制,具体实现方式又少有人知。面对日新月异的应用场景,现有的方案显得捉襟见肘。久而久之积累下技术债务,是导致本次功能失效的主要原因。
往后 APP 将会面临更多与网页有交互的挑战,必须提前准备好解决方案。比如登录业务原本设计成刷新网页,刷新网页会降低用户体验,同时也增大了业务状态本就复杂的网页开发难度。
设计成函数回调的方式,从编程的角度达到结构的作用,大幅提升了系统的可维护性。
public void onEventMainThread(LoginSuccess success) {
webView.loadUrl("javascript:webViewLoginSuccess()"); // login success callback
}
以安卓端为例。网页开发者提前在项目中定 义JavaScript 函数 webViewLoginSuccess
函数, 一旦登录成功后,在安卓端 WebView 执行该函数,达到告知网页登录的效果。
根据实际情况,你可以从下方的参考链接中找到合适的解决方案。