Native 和 H5 都有着各自的优缺点,为了满足业务的需要,实际项目的开发过程中往往会融合两者进行 Hybrid 开发。
WebView
WebView为移动端提供的运行JavaScript的环境,是系统渲染Web网页的一个控件,可与页面JavaScript交互,实现混合开发。可以把Webview看作是一个性能打八折的移动浏览器。
WebView控件除了能加载指定的url外,还可以对URL请求、JavaScript的对话框、加载进度、页面交互进行强大的处理,之后会提到拦截请求、执行JS脚本都依赖于此。
JSBridge
在Hybrid模式下,H5会经常需要使用Native的功能,比如打开二维码扫描、调用原生页面、获取用户信息等,同时Native也需要向Web端发送推送、更新状态等,而JavaScript是运行在单独的JS Context中(Webview容器、JSCore等),与原生有运行环境的隔离,所以需要有一种机制实现Native端和Web端的双向通信,这就是JSBridge。
Native端调用Web端
JavaScript作为解释性语言,最大的一个特性就是可以随时随地地通过解释器执行一段JS代码,所以可以将拼接的JavaScript代码字符串,传入JS解析器执行就可以,JS解析器在这里就是WebView。(因为webView就存在App里面,所以native端可以拿到webView这个对象,然后用这个webView去解释执行js代码)。
Android 4.4之后提供了evaluateJavascript来执行JS代码,并且可以获取返回值执行回调:
webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value){
xxx
}
});
注意:原生端调用web的方法,方法必须挂载到web端的window对象下面
Web端调用Native端
Web调用Native端主要有两种方式:
拦截Webview请求的URL Schema
URL Schema是类URL的一种请求格式,格式如下:
<protocol>://<host>/<path>?<qeury>#fragment
我们可以自定义JSBridge通信的URL Schema,比如:jsbridge://showToast?text=hello,下图是微信提供的Schema:
Native加载WebView之后,Web发送的所有请求都会经过WebView组件,然后Native可以重写WebView里的方法,对拦截Web发起的请求,我们对请求的格式进行判断:
- 如果符合我们自定义的URL Schema,对URL进行解析,拿到相关操作、操作,进而调用原生Native的方法
- 如果不符合我们自定义的URL Schema,我们直接转发,请求真正的服务
Web发送URL请求的方法有这么几种:
a标签location.href- 使用
iframe.src - 发送
ajax请求 这些方法,a标签需要用户操作,location.href可能会引起页面的跳转丢失调用,发送ajax请求Android没有相应的拦截方法,所以使用iframe.src是经常会使用的方案。安卓和IOS都提供了相应的拦截方法,比如安卓使用shouldOverrideUrlLoading方法进行拦截。下面我们来封装一个js-sdk:
const jsSdk = {
invoke(url, data, onSuccess, onError) {
const iframe = document.createElement('iframe')
iframe.style.visibility = 'hidden'
document.body.appendChild(iframe)
iframe.onload = () => {
const content = iframe.contentWindow.document.body.innerHTML
onSuccess(JSON.parse(content))
document.body.removeChild(iframe)
}
iframe.onerror = () => {
onError()
document.body.removeChild(iframe)
}
iframe.src = `jsbridge://${url}?data=${data}`
},
fn1(data, onSuccess, onError) {
this.invoke('/fn1', data, onSuccess, onError)
},
fn2(data, onSuccess, onError) {
this.invoke('/fn2', data, onSuccess, onError)
}
}
这种方式适用于异步情况下的调用,通过回调函数拿到结果
向Webview中注入JS API
这个方法会通过webView提供的接口,App将Native的相关接口注入到JS的Context(window)的对象中,一般来说这个对象内的方法名与Native相关方法名是相同的,Web端就可以直接在全局window下使用这个暴露的全局JS对象,进而调用原生端的方法。
Android提供了addJavascriptInterface注入:
// 注入全局JS对象
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");
class NativeBridge {
// 增加JS调用接口
public void showNativeDialog(String text) {
new AlertDialog.Builder(ctx).setMessage(text).create().show();
}
}
在Web端直接调用这个方法即可:
window.NativeBridge.showNativeDialog('hello');
这种方式适用于同步方式的调用