JsBridge is the bridge between JS and native communication. This paper does not talk about conceptual things, but analyzes the source code of jsBridge in our project. Let’s get a sense of how it works from a front-end perspective.

Js call method

Look at the first, js how to invoke a native method, first initialization will call window. WebViewJavascriptBridge. The init method:

window.WebViewJavascriptBridge.init()
Copy the code

Then if you want to call a native method you can use the following function:

function native (funcName, args = {}, callbackFunc, errorCallbackFunc) {
    // Check whether the parameter is valid
    if (args && typeof args === 'object' && Object.prototype.toString.call(args).toLowerCase() === '[object object]' && !args.length) {
        args = JSON.stringify(args);
    } else {
        throw new Error('ARGS does not conform to specification');
    }
    // Check whether the environment is mobile
    if (getIsMobile()) {
        / / call window. WebViewJavascriptBridge callHandler method of objects
        window.WebViewJavascriptBridge.callHandler(
            funcName,
            args,
            (res) = > {
                res = JSON.parse(res);
                if (res.code === 0) {
                    return callbackFunc(res);
                } else {
                    returnerrorCallbackFunc(res); }}); }}Copy the code

Incoming to invoke the method name, parameter and callback, it first to check the parameters, and then will call window. WebViewJavascriptBridge. CallHandler method.

Callbacks can also be provided for native invocation:

window.WebViewJavascriptBridge.registerHandler(funcName, callbackFunc);
Copy the code

Then look at the window. What is WebViewJavascriptBridge object.

The android

WebViewJavascriptBridge. Js file is a since the execution function, first of all, some variables are defined:

// Define variables
var messagingIframe;
var sendMessageQueue = [];// The queue that sends the message
var receiveMessageQueue = [];// Queue to receive messages
var messageHandlers = {};// Message handler

var CUSTOM_PROTOCOL_SCHEME = 'yy';// Custom protocol
var QUEUE_HAS_MESSAGE = '__QUEUE_MESSAGE__/';

var responseCallbacks = {};// The callback of the response
var uniqueId = 1;
Copy the code

According to the variable name simple translation, the specific use will be analyzed next. Next we define the WebViewJavascriptBridge object:

var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
    init: init,
    send: send,
    registerHandler: registerHandler,
    callHandler: callHandler,
    _fetchQueue: _fetchQueue,
    _handleMessageFromNative: _handleMessageFromNative
};
Copy the code

This is an ordinary object with some methods mounted on it.

var doc = document;
_createQueueReadyIframe(doc);
Copy the code

The _createQueueReadyIframe method is called:

function _createQueueReadyIframe (doc) {
    messagingIframe = doc.createElement('iframe');
    messagingIframe.style.display = 'none';
    doc.documentElement.appendChild(messagingIframe);
}
Copy the code

This method is as simple as creating a hidden iframe and inserting it into the page, continuing:

Create an Event object of type Events (base Event module)
var readyEvent = doc.createEvent('Events');
/ / define events called WebViewJavascriptBridgeReady
readyEvent.initEvent('WebViewJavascriptBridgeReady');
// Trigger the event via document
doc.dispatchEvent(readyEvent);
Copy the code

Here we define a custom event and distribute it directly, so that other places can listen for it as if it were a native event:

document.addEventListener(
    'WebViewJavascriptBridgeReady'.function () {
        console.log(window.WebViewJavascriptBridge)
    },
    false
);
Copy the code

Here use my understanding is when the jsBridge file if is introduced after the other code need to make sure that the code before can know window. WebViewJavascriptBridge object when it is available, if stipulated the jsBridge must first introduced so don’t need this processing.

Now that we’re done executing the function, let’s look at the initial init method:

function init (messageHandler) {
    if (WebViewJavascriptBridge._messageHandler) {
        throw new Error('WebViewJavascriptBridge.init called twice');
    }
    // init calls with no arguments, so messageHandler=undefined
    WebViewJavascriptBridge._messageHandler = messageHandler;
    ReceiveMessageQueue is also an empty array
    var receivedMessages = receiveMessageQueue;
    receiveMessageQueue = null;
    for (var i = 0; i < receivedMessages.length; i++) { _dispatchMessageFromNative(receivedMessages[i]); }}Copy the code

From an initialization point of view, this init method seems to do nothing. Let’s look at the callHandler method and see how android methods are called:

function callHandler (handlerName, data, responseCallback) {
    _doSend({
        handlerName: handlerName,
        data: data
    }, responseCallback);
}
Copy the code

The _doSend method is called after processing the argument:

function _doSend (message, responseCallback) {
    // If a callback is provided
    if (responseCallback) {
        // Generate a unique callback ID
        var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
        // The callback is stored in the responseCallbacks object by id
        responseCallbacks[callbackId] = responseCallback;
        // Add the callback ID to the message to be sent to native
        message.callbackId = callbackId;
    }
    // The message is added to the message queue
    sendMessageQueue.push(message);
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ': / /' + QUEUE_HAS_MESSAGE;
}
Copy the code

This method first stores the callback from the native method call into the responseCallbacks object defined initially by generating a unique ID, and then adds that ID to the message to be sent, so a message structure looks like this:

{
    handlerName,
    data,
    callbackId
}
Copy the code

We then add the message to the sendMessageQueue array we originally defined, and finally set the SRC property of iframe: Yy ://__QUEUE_MESSAGE__/, this is actually a custom protocol URL, I did a simple search, native will intercept this URL to do the corresponding processing, we can’t go on at this point, because I don’t know what native does, a simple search, found this library: WebViewJavascriptBridge, we should be based on the library changes, combined with some online articles after know, probably native to intercept to this url will be called after the js window. The WebViewJavascriptBridge. _fetchQueue method:

function _fetchQueue () {
    // Queue the message we want to send into a string
    var messageQueueString = JSON.stringify(sendMessageQueue);
    // Clear the message queue
    sendMessageQueue = [];
    // Android cannot read the returned data directly, so it communicates with Java via iframe SRC
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://return/_fetchQueue/' + encodeURIComponent(messageQueueString);
}
Copy the code

After intercepting the URL, Android knows that JS has sent a message to Android, so it actively calls js _fetchQueue method to fetch the message added to the queue before. Since the data returned by JS method cannot be read directly, it adds the formatted message to the URL and sends it again through iframe. Yy ://return/_fetchQueue/; return/_fetchQueue/; When the native method after the execution will take the initiative to call js window. WebViewJavascriptBridge. _handleMessageFromNative method:

function _handleMessageFromNative (messageJSON) {
    ReceiveMessageQueue is set to null, so the else branch is used
    if (receiveMessageQueue) {
        receiveMessageQueue.push(messageJSON);
    } else{ _dispatchMessageFromNative(messageJSON); }}Copy the code

Have a look at what did _dispatchMessageFromNative method:

function _dispatchMessageFromNative (messageJSON) {
    setTimeout(function () {
        // The native message is a string, converted to JSON
        var message = JSON.parse(messageJSON);
        var responseCallback;
        // When the Java call is complete, the responseId returned is the callbackId we sent it earlier
        if (message.responseId) {
            // Retrieve the callback method associated with this ID from the responseCallbacks object
            responseCallback = responseCallbacks[message.responseId];
            if(! responseCallback) {return;
            }
            // Execute the callback, js calls the Android method and receives the message successfully
            responseCallback(message.responseData);
            delete responseCallbacks[message.responseId];
        } else {
            // ...}}); }Copy the code

A messageJSON is a message that is sent back by the responseCallbacks method. It also carries the callbackId that we passed to the responseCallbacks. This js call to the native method flow ends. But the apparent function and does not exist in the id of the branch, is used to do here, we call native methods described above is js, but obviously, native also can send messages directly to js, common interception return key functions, such as when the original after listening to return key events it will take the initiative to send information to tell front page, the page you can perform the corresponding logic, The else branch is used to handle this:

function _dispatchMessageFromNative (messageJSON) {
    setTimeout(function () {
        if (message.responseId) {
            // ...
        } else {
            // Just as the message we send to the native can carry an ID, the message we send to the native can also carry an ID, and the native will associate a callback with this ID
            if (message.callbackId) {
                var callbackResponseId = message.callbackId;
                // If the front end needs to send a message back to the native, it carries the id from the original, so that the native can find the corresponding callback and execute it
                responseCallback = function (responseData) {
                    _doSend({
                        responseId: callbackResponseId,
                        responseData: responseData
                    });
                };
            }
            // We didn't set the default _messageHandler, so undefined
            var handler = WebViewJavascriptBridge._messageHandler;
            // The natively sent message contains the handler name
            if (message.handlerName) {
                // Go to the messageHandlers object using the method name to see if there is a corresponding handler
                handler = messageHandlers[message.handlerName];
            }
            try {
                // Execute the processing methodHandler (message data, responseCallback); }catch (exception) {
                if (typeof console! = ='undefined') {
                    console.log('WebViewJavascriptBridge: WARNING: javascript handler threw.', message, exception); }}}}); }Copy the code

We want to listen to native return key events, for example, we first through the window. The object WebViewJavascriptBridge method to register:

window.WebViewJavascriptBridge.registerHandler('onBackPressed'.() = > {
    // Do something...
})
Copy the code

The registerHandler method looks like this:

function registerHandler (handlerName, handler) {
    messageHandlers[handlerName] = handler;
}
Copy the code

Simple, we store the event name and method we are listening for in the messageHandlers object, and if we listen natively for a return key event, we send a message with the following structure:

{
    handlerName: 'onBackPressed'
}
Copy the code

This way we can find our registered function through handlerName and execute it.

At this point, android environment JS and native call each other’s logic is over, summary is:

1. Js calls native

Generate a unique ID, store the callback and ID, then add the message to be sent (with the generated unique ID) to a queue, then send a custom protocol request via iframe, After native intercepted call js window. WebViewJavascriptBridge object information, a method to get the queue parsed out request executed after the corresponding native methods and parameters, And then the response (id) take the front came through a call to js window. WebViewJavascriptBridge specified method is passed to the front, front end before by id find store callback, to perform.

2. Call JS natively

First front need to register to monitor events beforehand, the event name and callback, preservation and native window. At some point will call js WebViewJavascriptBridge object of the specified method, front end according to the return parameter event name find registered callback to perform, at the same time, the native will be to get an id, If the front end needs to send a message back to the native after executing the logic, the id is taken back and the native uses the ID to find the corresponding callback to execute.

As you can see, the logic on both sides of js and native is consistent.

ios

Ios and Android are basically the same, some details are a little different, first is the protocol is different, ios is like this:

var CUSTOM_PROTOCOL_SCHEME_IOS = 'https';
var QUEUE_HAS_MESSAGE_IOS = '__wvjb_queue_message__';
Copy the code

Ios then initializes the creation of an iframe with a request:

var BRIDGE_LOADED_IOS = '__bridge_loaded__';
function _createQueueReadyIframe (doc) {
    messagingIframe = doc.createElement('iframe');
    messagingIframe.style.display = 'none';
    if (isIphone()) {
        // Load the bridge for ios
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME_IOS + ': / /' + BRIDGE_LOADED_IOS;
    }
    doc.documentElement.appendChild(messagingIframe);
}
Copy the code

Ios does not need iframe to retrieve our message queue, it can directly retrieve the data returned by executing js functions:

function _fetchQueue () {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;// Return directly without iframe
}
Copy the code

Everything else is the same.

conclusion

This article analyzes the jsBridge source code, you can find that it is actually a very simple thing, but usually may not seriously understand it, always want to do some “big” things, so that become a “ambitious” people, I hope you do not like the author.

In addition, this article only analyzes the jsBridge implementation of our company, there may be different, better or newer implementation, welcome to comment.