通过手机浏览器访问H5页面,对用户来说还是太麻烦了!
我们平常也都很少在手机上访问H5网站,最多也就是用手机浏览器搜点东西。不管是你想逛社区、看视频、看直播、搜路线,或者其他需求,都是通过对应的App来访问内容;只有在PC端,我们才通过网站去访问(当然一些人喜欢通过客户端访问)。
现在手机内存和存储空间都很大,下个App对用户来说是完全可以接受的,用户的习惯也是使用App,就像我们平常花在各种App上很多时间一样。
虽然有着PWA(Progressive Web App--渐进式Web App),但是在国内浏览器上兼容性实在不行,除了Android Chrome上没啥问题。而且PWA想要使用手机的原生功能,怎么都不如Native App来的好,而且不需要前端来实现,我们只要告诉App我们要使用什么什么功能,我们想获取什么什么状态,App实现具体的逻辑,把结果告诉我们就好了。
那么App和H5之间是怎么通信的呢?
对于H5来说,想要打破webview的厚障壁、直接去访问App是不可能的,所以只能通过webview的某些机制,让App可以感知到H5想要进行的操作。大体上可以分为2种方式:拦截和注入。
1️⃣拦截
1. url scheme
机制:App可以拦截H5发起的请求
我们可以在请求地址上添加一些query参数,表明功能的参数+回调函数,比如扫码功能:
axios.get('https://xxx?func=scan&callback=QRCodeCallback')。当拦截到某个请求,根据origin来判断是否应该放行。
当然,更加主流的方式,是通过自定义scheme,符合scheme的请求,肯定就是想要通信的请求。
自定义URL scheme的一般格式如下:
scheme://[host]/[path]?[query]
scheme:自定义的协议名,如jsbridge。host:可选的主机名或应用程序内的资源标识符。path:资源的路径或特定操作的标识符。query:传递给应用程序的参数,通常以键值对的形式出现。
比如:jsbridge://scan?callback=QRCodeCallback
H5端发起请求的方式有多种,通常使用以下3种:
- 使用 a 标签跳转
<a href="jsbridge://xxxx"></a> - 重定向
location.href = "jsbridge://xxxx" - iframe 跳转
const iframe = document.createElement("iframe"); iframe.src = "jsbridge://xxxx"; iframe.style.display = "none" document.body.appendChild(iframe)
这种方式感觉类似于以前实现跨域请求的jsonp方式:发一个请求,把回调传过去,调用回调,把数据传过来
2. 弹窗
机制:弹窗会触发webview对应的事件(这个弹窗是prompt、confirm、alert的弹窗)
webview.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, final JsPromptResult result) {
// message 就是 js 中 prompt() 传递过来的信息
if (message === xxxx) {
// 解析message的值,调用对应方法
...
// 为 js 中的 prompt 框返回数据
result.confirm('resultString')
}
return true
}
})
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt message: String, defaultValue: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping(String?) -> Void) {
// 解析message的值,调用对应方法
// 为 js 中的 prompt 框返回数据
completionHandler('resultString')
}
const result = prompt(message, defaultValue)
2️⃣注入
注入类似于web页面中,嵌入同源的iframe,可以通过iframe.contentWindow访问iframe的window对象;不同源的使用postMessage进行通信。
当然,webview没有同源不同源这个说法。Android和iOS有对应的原生方法,可以往webview的window上加东西。这样,App加的东西,H5自然可以访问到(加到window上,H5当然可以访问)。并且,Android和iOS还有原生方法,可以直接访问webview的window对象上的属性和方法(H5往window上加东西)。
通过这种App注入H5调用,H5注入App调用的方式,就实现了App和H5的双向通信。
// 注入
webview.addJavascriptInterface(new JSBridge(), 'jsbridge')
private class JSBridge {
@JavascriptInterface
public String call(String methodName, String argStr) {
// ...
}
}
// 这样,就往window上注入了一个jsbridge对象,这个对象中有一个call方法,H5可以调用这个方法
// 调用
private void _evaluateJavascript(String script) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webview.evaluateJavascript(script, null);
} else {
webview.loadUrl("javascript:" + script);
}
}
// H5注入
window.testFn = (data) => {
// ...
}
dsBridge
dsBridge是根据jsBridge机制实现的jsBridge库,Android、IOS、Javascript 三端易用,轻量且强大、安全且健壮。三端都有对应的包,统一且用起来很简单。
import dsBridge from 'dsbridge'
// 注册
dsBridge.register('warning', (id: number) => {
// 通过点击app接收到的告警通知,进入告警详情页面
})
// 判断是否有此方法
const has = dsBridge.hasNativeMethod('getNFCStatus')
// 同步调用
// 获取蓝牙开关状态
const status = dsBridge.call('getBluetoothStatus'[,'getBluetoothStatus'])
// 异步调用
// 扫码
dsBridge.call('scan', 'scan', (qrCode: string) => {
// ...
})
H5端差不多就这些东西,本身源码也没多少
有一点需要注意的是,当异步调用时,需要传一个函数,这个函数是通过var cbName = 'dscb' + window.dscb++; window[cbName] = cb加到window上的。由此可知,如果我们使用匿名函数的话,每次调用,都会在window上添加一个新属性,指向本次的匿名函数。由于每次调用的匿名函数是不相等的,会有一定的内存泄露风险(dsBridge这样设计不知道是基于什么考虑的)。所以我们可以使用具名函数,先定义函数,再使用(Android和iOS如何使用,就叫app的小伙伴去看文档吧)。
readme: github.com/wendux/DSBr…