What is JSB
The H5 pages we developed run in a WebView container on the side. Many business scenarios where H5 relies on information/capabilities provided on the side, we need a bridge between the native and JS runtime environments. This bridge is JSB, which enables two-way communication between Web and Native.
Summary of the WebView
WebView is a control in the mobile terminal that provides a sandbox environment for JS to run. The implementation of JSB depends on the various interfaces exposed by The WebView, which can load the specified URL and intercept the various requests issued by the page. For historical reasons, both Android and iOS have two versions of the WebView kernel:
Platforms and versions | WebView The kernel |
---|---|
iOS 8+ | WKWebView |
iOS 2-8 | UIWebView |
The Android 4.4 + | Chrome |
The Android 4.4 – | Webkit |
PS: The later versions in the following paragraphs refer to iOS 8+ or Android 4.4+. The earlier versions are the opposite.
JSB principle
To realize two-way communication, Native sends messages to the Web and Web sends messages to Native successively.
Native sends messages to the Web
The basic principle of Native sending messages to the Web is to dynamically execute a JS script in a WebView container, usually by calling a method mounted in a global context. Both Android and iOS provide different interfaces to do this.
methods
- There are two ways to execute JS strings directly on Android:
Android version | API | The characteristics of |
---|---|---|
Low version | WebView.loadUrl | Unable to perform callback |
High version | WebView.evaluateJavascript | You can get the return value of JS execution |
- There are also two different implementations of iOS:
The iOS version | API | The characteristics of |
---|---|---|
Low version | UIWebView.stringByEvaluatingJavaScriptFromString | Unable to perform callback |
High version | WKWebView.evaluateJavaScript | You can get the return value of JS execution |
practice
Let’s use a small Demo to see the actual effect of implementing Native messaging to the Web side on the iOS side:
(all demos in this article are running in the iOS14.5 emulator, and the WebView container uses the WKWebView kernel)
The UI of the top half of the page is webpage rendered by HTML + CSS, which is a pure static webpage, and the input box and button in the middle are Native Native controls, directly covering the WebView container. Native button is bound to a click event: the character entered in the text box is treated as a JS string and directly executed by calling the relevant API.
You can see that when we enter the following characters in the text box and click the button, the content of the P tag with ID test in the H5 page is changed.
document.querySelector('#test').innerHTML = 'I am from native';
Copy the code
We added a few lines of JS code to the static HTML file just now:
function evaluateByNative(params) { const p = document.createElement('p'); p.innerText = params; document.body.appendChild(p); return 'Hello Bridge! '; }Copy the code
Enter evaluateByNative(23333) in the text box to see the results of the call:
We can see that the Native side can directly call the global method mounted on the window and pass in the corresponding function execution parameters, and after the end of the function execution Native side can directly get the return value of the successful execution.
The Web sends messages to Native
The message sent by Web to Native is in essence perceived on the execution end of a certain JS code. At present, there are two mainstream implementation schemes in the industry, namely interception and injection.
Intercept type
Similar to the browser, all requests made in WebView can be sensed by the Native container (whether Gecko is thought). Therefore, interception specifically refers to the URL request that Native intercepts from the Web. Both parties agree on a JSB request format before this. If the request is JSB, it is processed accordingly; if not, it is forwarded directly.
Native hook methods for intercepting requests:
platform | API |
---|---|
Android | shouldOverrideUrlLoading |
iOS 8+ | decidePolicyForNavigationAction |
iOS 8- | shouldStartLoadWithRequest |
The basic process of interception is as follows:
There are several problems with the above process:
- How to make the request?
The Web side can issue requests in a variety of ways, such as , iframe.src, location.href, Ajax, etc., but needs to be triggered manually by the user, and location.href may lead to page jump, and Androidside lacks the ability to intercept Ajax. Therefore, most interception implementations use iframe to send requests.
- How to rule JSB Request format?
A standard URL consists of
://
:
// The Web dynamically creates the iframe and sets the SRC to a url scheme conforming to the two-end specification
const CUSTOM_PROTOCOL_SCHEME = 'prek'
function web2Native(event) {
const messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ': / /' + event;
document.documentElement.appendChild(messagingIframe);
setTimeout(() = > {
document.documentElement.removeChild(messagingIframe);
}, 200)}Copy the code
Interception has very good downward compatibility on both ends. It used to be the most mainstream IMPLEMENTATION of JSB, but it has been gradually phased out in higher versions of systems because of the following disadvantages:
- Message loss may occur when sent consecutively (this can be resolved using message queues)
- The length of the URL string is limited
- The performance is mediocre, the URL Request takes some time to create the request (200-400ms on Android)
Practical cases
Also take a look at how to send messages to Native using an intercepting implementation of the Web with a simple Demo2, which evoking Native photo albums on the Web side.
Following the above implementation, the code for the Web to send a message is as follows:
const CUSTOM_PROTOCOL_SCHEME = 'prek' // Customize the URL scheme
function web2Native(event_name) {
const messagingIframe = document.createElement('iframe')
messagingIframe.style.display = 'none'
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ': / /' + event_name
document.documentElement.appendChild(messagingIframe)
setTimeout(() = > {
document.documentElement.removeChild(messagingIframe)
}, 0)}const btn = document.querySelector('#btn')
btn.onclick = () = > {
web2Native('openPhotoAlbum')}Copy the code
Native side by decidePolicyForNavigationAction this delegate requests to intercept, parse the URL parameter, if the URL scheme is prek argues that the request is a JSB calls from the Web:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
NSLog(@"Intercepting a request to the Web = %@", url);
if ([self isSchemeMatchPrek:url]) {
NSString* host = url.host.lowercaseString;
if ([host isEqualToString: @"openphotoalbum"]) {
[self openCameraForWeb]; // Open the photo album
NSLog(@"Open photo album");
}
decisionHandler(WKNavigationActionPolicyCancel);
return;
} else{ decisionHandler(WKNavigationActionPolicyAllow); }}Copy the code
To see the results of Native interception more clearly, put a break point in the above proxy method:
Keep going, Congratulation! The simulator’s photo album is open!
injection
The principle of injection is to inject objects or methods into THE JS global context object (Window) through the interface provided by WebView. When called by JS, the corresponding Native code logic can be directly executed, so as to achieve the purpose of calling Native by Web.
Native injection API
platform | API | The characteristics of |
---|---|---|
Android | addJavascriptInterface | A version later than 4.2 has security risks |
iOS 8+ | WKScriptMessageHandler | There is no |
iOS 7+ | JavaSciptCore | There is no |
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"getAppInfo"] = ^(msg) {
return @"ggl_2693";
};
window.getAppInfo(); // 'ggl_2693'
Copy the code
This approach is simple and intuitive, and there is no parameter length limit or performance bottleneck, and the current mainstream JSB SDKS take injection as a priority. The implementation of injection is very simple, and I won’t do a case study here.
Compare the two schemes
To clarify the difference between the two approaches, here’s a comparison chart:
plan | compatibility | performance | Parameter length limit |
---|---|---|---|
Intercept type | No compatibility problem | Poor, especially androidend | There are limits |
injection | Available for Android 4.2+ and iOS 7+ | good | There is no |
How do I perform a callback
We already know how to send messages to each other, but the “response” action is missing because the callback function is missing. Take interception as an example, a common JSB call is the Web to obtain the current App information, Native intercepts bytedance://getAppInfo such a request to obtain the current App information, then how to make the Web end to get the information after the completion of obtaining?
One of the simplest ways to do this is to emulate the implementation of JSONP. We can concatenate the event name of the callback method on the REQUESTED URL and mount the event on the global window. Since the Native end can easily execute JS code, we can directly execute the callback method corresponding to the event name after the end of the logic. Take getAppInfo as an example:
// Web
const uniqueID = 1 // To prevent event name conflicts, give each callback a unique identifier
function webCallNative(event, params, callback) {
if (typeof callback === 'Function') {
const callbackID = 'jsb_cb_' + (uniqueID++) + '_' + Date.now();
window[callbackID] = callback
}
const params = {callback: callbackID}
// Construct the URL scheme
const src = 'bytedance://getAppInfo? ' + JSON.stringify(params)
...
}
// Native
1.Parse the passed arguments'getAppInfo'You know that the Web wants to get AppInfo2.The execution end obtains AppInfo logically3.The callback method is mounted globally with AppInfo as an argument to the callback methodCopy the code
Therefore, as long as the corresponding callback method is mounted on the global object, Native can transfer the response after each call to the Web end in the form of dynamic execution of JS method, so that the whole communication process is closed loop.
The process of serial two-terminal communication
Now that we know how to send messages to each other and perform callbacks, it doesn’t look good: first, JSB needs to concatenated parameters and corresponding callbacks after method names, and second, the callbacks need to be mounted on global objects one by one.
The way we want to use it is this:
// Web
web.call('event1', {param1}, (res) = >{... })// Trigger native Event1 execution
web.on('event2'.(res) = >{... })// Native
// Use js instead to understand the general meaning
native.call('event2', {param2}, (res) = >{... })// Trigger Web Event2 execution
native.on('event1'.(res) = >{... })Copy the code
The JSB here acts like an EventEmitter across both ends, and therefore requires the Web and Native to follow the same scheduling mechanism.
The figure above shows the execution process of Web call -> Native listener, and the same logic applies to Native call -> Web listener, but the realization of the two sides of the exchange of a language, which will not be repeated here.
Post a sequence diagram drawn by another student to help you understand the communication process
Demo3 is based on the open source WebViewJavascriptBridge to demonstrate a complete set of communication process is how to proceed, interested students please stamp source address JSB_Demo experience. (You need to use Xcode to open it, which involves some knowledge of the client, please use it with the documents and Google).
A little feeling
The bridge used by the author’s business is the latest SDK in the department, which has no historical baggage and a very good use experience. Since the client complies with the implementation mechanism of the SDK, even students who do not understand the principles of JSB encounter almost no obstacles when connecting to the bridge on the end. It’s not easy to implement a common and usable JSB without a company’s infrastructure, so it’s helpful to know how. Giant’s shoulders stand for a long time, really ba shi very 🐶
Bytedance school/social recruitment post link: job.toutiao.com/
Push-in code: 7EZKXME
reference
Simple JSBridge
JSB of actual combat