什么是JSBridge

在 JavaScript 代码中提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能(例如:地址位置、摄像头)。

它的核心是构建 Native 和非 Native 间消息通信的通道,而且这个通信的通道是双向的。

JSBridge 的实现原理

JavaScript 是运行在一个单独的 JS Context 中(例如,WebView 的 Webkit 引擎、JSCore)。由于这些 Context 与 Native 原生运行环境的天然隔离,我们必须通过一定的方法来实现 Native 和 JS Context 的双向通信。主要包括:

  1. Native 调用 js 方法
  2. js 调用 Native 方法

Native调用js方法

Native => JS

iOS

1
webview.stringByEvaluatingJavaScriptFromString("Math.random()")
1
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];

Android

1
2
3
4
5
6
mWebView.evaluateJavascript("javascript: Math.random();", new ValueCallback() {
@Override
public void onReceiveValue(String value) {
//这里的value即为对应JS方法的返回值
}
});

JS => Native

对于 Webview 中发起的网络请求,Native 都有能力去捕获/截取/干预。所以 JSBridge 的核心就是设计一套uri方案,让 Native 可以识别,从而做出响应,执行对应的操作。

js调用Native方法

JavaScript 调用 Native 的方式主要有两种,可以通过 注入API拦截URL SCHEME 的方式来实现。

注入API

注入 API 方式的主要原理是,通过 WebView 提供的接口,向 JavaScript 的 Context(window)中注入对象或者方法,让 JavaScript 调用时,直接执行相应的 Native 代码逻辑,达到 JavaScript 调用 Native 的目的。

对于 iOS 的 UIWebView,实例如下:

1
2
3
4
5
6
7
8
9
JSContext *context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

context[@"postBridgeMessage"] = ^(NSArray<NSArray *> *calls) {
// Native 逻辑
};


//前端调用方式:
window.postBridgeMessage(message);

对于 iOS 的 WKWebView 可以用以下方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@interface WKWebVIewVC ()<WKScriptMessageHandler>

@implementation WKWebVIewVC

- (void)viewDidLoad {
[super viewDidLoad];

WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = [[WKUserContentController alloc] init];
WKUserContentController *userCC = configuration.userContentController;
// 注入对象,前端调用其方法时,Native 可以捕获到
[userCC addScriptMessageHandler:self name:@"nativeBridge"];

WKWebView wkWebView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];

// 显示 WebView
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"nativeBridge"]) {
NSLog(@"前端传递的数据 %@: ",message.body);
// Native 逻辑
}
}

// 前端调用方式:
window.webkit.messageHandlers.nativeBridge.postMessage(message);

Android 可以通过 addJavascriptInterface 方法 将 Native 的一个对象注入到页面中,供JS调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** 
* 添加javascriptInterface
* 第一个参数:这里需要一个与js映射的java对象
* 第二个参数:该java对象被映射为js对象后在js里面的对象名,在js中要调用该对象的方法就是通过这个来调用
*/
webView.addJavascriptInterface(new JSInterface(), "android");

private final class JSInterface{
/**
* 注意这里的@JavascriptInterface注解, target是4.2以上都需要添加这个注解,否则无法调用
* @param text
*/
@JavascriptInterface
public void showToast(String text){
Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
}
}

// 前端调用方式:
android.showToast('toast');

拦截 URI SCHEME

URI SCHEME 是一种 uri 的链接,是为了方便app直接互相调用设计的,形式和普通的 uri 近似,主要区别是 protocol 和 host 是自定义的。
拦截 URI SCHEME 的主要流程是:Web 端通过某种方式(例如 iframe.src)发送 URI Scheme 请求,之后 Native 拦截到请求并根据 URI SCHEME(包括所带的参数)进行相关操作。

在时间过程中,这种方式有一定的缺陷:

  • 使用 iframe.src 发送 URL SCHEME 会有 url 长度的隐患。
  • 创建请求,需要一定的耗时,比注入 API 的方式调用同样的功能,耗时会较长。

JSBridge 接口实现

从上面的剖析中,可以得知,JSBridge 的接口主要功能有两个:调用 Native(给 Native 发消息) 和 接被 Native 调用(接收 Native 消息)。因此,JSBridge 可以设计如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
window.JSBridge = {
// 调用 Native
invoke: function(bridgeName, data) {
// 判断环境,获取不同的 nativeBridge
nativeBridge.postMessage({
bridgeName: bridgeName,
data: data || {}
});
},
receiveMessage: function(msg) {
var bridgeName = msg.bridgeName,
data = msg.data || {};
// 具体逻辑
}
};

对于JSBridge的回调,和jsonp类似,url 参数里会有 callback 参数,其值是 当前页面唯一 的,而同时以此参数值为 key 将回调函数存到 window 上,随后,服务器返回 script 中,也会以此参数值作为句柄,调用相应的回调函数。