背景
多年的运营策略发现,用户其实对折扣的大小比活动可玩性要关注的多。所以我们的运营活动多是简单的促销页面,很少有多级,从而降低用户的购买阻力。而且经常需要刷新用户的支付状态,所以我们的运营活动页面基本很少涉及到keep-alive的需求,我们基本上没有对我们的框架增加这一功能。keep-alive多在我们的后台项目有集成,不过用起来其实也是很难受,懂得都懂。
最近我们和译林社合作举办了一场线上大赛,里面涉及的页面有许多,而且有各种跳转,有一些特殊页面用户需要浏览作品列表,决定是否点赞。因此会有来回切换的场景,而且需要在预览作品后回来时上一级页面保持原样。第一时间就想到了keep-alive。但是想到之前操作它的经历,心里一阵痛苦。一直在疯狂的问chatgpt有没有更优雅的方式,在不影响其他页面和系统的情况下,兼容这个需求。在一阵疯狂搜索尝试下,忽然发现之前在做app内做安全链接的解决方案的时候,顺便解决了这个问题。
安全链接
scheme
做过app内h5的同学们应该知道,h5需要经常和app端进行两类交互,一类是jsbridge,一类是scheme。jsbridge在我的两外一篇文章中有详细说明。本文主要说这个scheme,单说scheme有些同学可能不太明白他是什么,但是只要说它是和http/https一样都是一种url,大家就明白了。这里贴一下两者的大概定义:
HTTP(超文本传输协议)是互联网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。HTTP用于在Web浏览器和网站服务器之间传递信息,信息是以HTTP请求和响应的形式进行传递的。
自定义scheme是一种特殊的URL,它可以在用户点击链接时启动一个特定的应用。例如,你可能会在电子邮件中看到一个像“myapp://myparameters”的链接。当你点击这个链接时,如果你的设备上安装了能处理“myapp”scheme的应用,那么这个应用就会启动,并接收到“myparameters”作为输入参数。自定义scheme常用于深度链接和跨应用交互。
这两者的主要区别在于:HTTP是标准的网络传输协议,用于Web浏览器和服务器之间的通信;而自定义scheme是为了在特定应用间进行交互或启动特定应用而设计的URL格式。大家应该也看到过weixin://,其实在浏览器跳转这个就能跳转到微信,它就属于自定义scheme。
webview
webview,做过客户端h5的应该都知道,它是客户端提供的内部浏览器容器。小程序现在也提供了这个玩意,原理基本都是一样。这里也贴一下它的定义:
WebView容器是一个承载WebView的组件或者元素。在不同的环境和平台下,WebView容器可能有不同的实现方式。
在Android应用中,WebView容器可以是任何能够容纳和管理WebView的组件,例如一个Activity或Fragment。在这种情况下,你会在布局文件(XML)中定义WebView,并在对应的Java或Kotlin代码中加载和管理WebView。
在iOS应用中,WebView容器可以是一个UIViewController,你可以在Interface Builder中拖拽一个WKWebView到视图控制器的视图中,或者在代码中动态创建并添加到视图层级结构。
在Web应用或者HTML中,WebView容器可以是一个iframe元素,它可以嵌入另一个HTML文档。
在React Native或其他跨平台应用开发框架中,WebView容器可以是一个特殊的组件,例如React Native的WebView组件。
总的来说,WebView容器的主要职责是承载WebView,以及管理和控制WebView的生命周期,例如加载URL,处理页面导航,执行JavaScript代码等。
scheme和webview
正常来说,我们的页面基本都是在一个webview容器中渲染,而且所有的行为都在一个webview中完成。那么如果有这样的需求,我们需要和客户端的其他容器进行交互,互相跳转,纪录一些路由内容,跨层级回退,甚至要跳转到app外部的浏览器(用户协议里面的引用链接,有些渠道端禁止随便跳,必须引导到自带的安全浏览器,参考《好烦啊,为什么点个链接还让我确认一下?》)。那么h5显然是做不到的,客户端做这个又很简单,这时候就需要我们通过scheme来处理这些需求了。比如我们定义一个跳转到其他webview的scheme: myapp://webview?url=xxx 和一个跳转到客户端外部浏览器的scheme: myapp://out_webview?url=xxx。具体的客户端实现这里就不贴了,感兴趣的可以去百度一下或者问一下chatgpt,都比较简单。那么我们只需要传入url即可实现我们的需求,开一个新的webview容器和跳转到站外浏览器打开。站外打开本文就不过多赘述,后面仅详细介绍站内webview跳转。
封装
虽然客户端的功能实现了,但是当调用多了之后就会发现有个问题,这玩意写完之后维护起来比较麻烦,每次都得拼装url。我们用的路由组件是vue-router,这时就想有没有可能实现类似的api来实现router push呢。开干! 首先根据vue-router获取相应的url:
// 根据vue-router参数生成目标跳转链接 *需要vue实例调用 内部使用到this*
function RouterToLink(routeOptions) {
// 合并当前页面的query
routeOptions.query = {
...this.$route.query,
...routeOptions.query
}
const { href } = this.$router.resolve(routeOptions)
const { origin } = window.location
return href && href !== '' ? `${origin}${href}` : ''
}
这个其实也是我们常用的需求,所以单独抽象出一个方法。
然后模拟push跳转:
// 根据vue-router参数进行新webveiw跳转 *需要vue实例调用 内部使用到this*
function InnerRouterPush(routeOptions) {
const targetLink = this.RouterToLink(routeOptions)
if (targetLink !== '') {
if (commonJs.GetEnv() !== 'app') {
window.location.href = targetLink
} else {
window.location.href = `${commonJs.GetSchemeByFrom(
this.from
)}://webview?url=${targetLink}`
}
}
}
这里也兼容了站外分享页,走默认跳转,然后我们把这两个方法挂载到vue实例上,再加上智能提醒,用起来不要太爽。调用:
this.InnerRouterPush({
name: 'page1',
params: {
p1: this.p1
},
query: {
q1: this.q1
}
})
this.$router.push({
name: 'page1',
params: {
p1: this.p1
},
query: {
q1: this.q1
}
})
和vue-router的调用一模一样,但是不同的是我们是在新的webview容器打开了指定的路由页面。
惊喜
得益于客户端容器的特点,当我们返回上一个容器时,客户端会销毁当前webview容器,同时把上一个webview容器展示出来,里面的甚至没有一点点的延迟和变化,不要太丝滑,不要太keepalive。相较于我们绞尽脑汁去实现和调试vue的那套keep-alive组件,此时我们只需要把this.$router.push换成this.InnerRouterPush即可,什么meta、什么scrollbehavior都见鬼去吧。