preface

This paper introduces the implementation of JSBridge, the core of Quick Hybrid framework

Because in the latest version, iOS7 and other low versions have not been considered, so in the selection of scheme did not use the URL scheme, but directly based on WKWebView implementation

Interaction principle

For the specific interaction principle between H5 and Native, please refer to the interaction principle between H5 and Native mentioned above

The interaction schematic diagram is as follows:

The expected end result

If the analysis step by step, and finally see the effect, may be very boring, and even a little simplification for the appearance of complexity. (I feel it is easiest to read the code directly, but I have to add a lot of description every time I write the article.)

So let’s take a look at what the final product should look like.

// An example of calling alert in the UI
callHandler({
    // Module name. The API in this article divides modules
    module: 'ui'./ / the method name
    name: 'alert'.// Request parameters to be passed to native
    data: {
        message: 'hello',},callback: function(res) {
        Code: 1, message: 'description ', // Data data: {},} */}});Copy the code

architecture

Implementing a JSBridge from scratch makes it easy to get blindfolded.

So we need to define the functional interaction in the big picture first, and then start building the details and coding the implementation

Functional analysis and validation

According to the core architecture, the functions to be implemented are planned:

  • Design of H5 Bridge Object (JSBridge)

    • Short – term callback pool, automatic collection

    • Long-term callback pool, which can be used multiple times

    • A channel that calls a Native method, bridging a native-registered receiver method on an object

    • The channel that receives Native calls, bridging the H5 registered receiver method on the object

    • H5 can register methods that are actively called to native

  • Design of native bridge objects

    • Long-term method pools, where each long-term call is stored in the callback pool and can be used multiple times

    • Short term immediate execution, every short term call is immediate execution

    • The channel that calls the H5 method, bridging the h5-registered receive method on the object

    • The channel that receives the H5 call, Bridges the native registered receive method on the object, and the underlying layer automatically parses it and then executes the corresponding API

    • The callback object, the underlying channel that calls H5, is called back to H5 through the callback object after each execution

    • Active calls to H5, unlike callback objects that can only respond passively, can actively call methods registered in H5

  • The design of the API

    • The API in H5 is called by the front end, and the bottom layer calls the Native method through the channel, and then sends the preprocessed parameters to the Native

    • Native API, real function implementation

Next up is the implementation of JSBridge

Validation of global communication objects

Most importantly, several global bridge objects in communication between H5 and Native are determined first:

  • JSBridge, H5 end of the bridge object, the object is bound to receive the native call method _handleMessageFromNative, as well as internal callback function management

  • Its. MessageHandlers. WKWebViewJavascriptBridge postMessage, iOS the bridge object, this method takes H5 calls

  • Prompt, the Android bridge object, overwrites the onJsPrompt in WebChromeClient for convenience

// H5 internal logic processing
window.JSBridge = {... }// Receive native calls, including callback and active calls
JSBridge._handleMessageFromNative = function() {... }Copy the code
// H5 actively calls native
if (os.ios) {
    / / ios adoption
    window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(...) ; }else {
    window.top.prompt(...) ; }Copy the code

Implementation of JSBridge objects

H5 relies on this object to communicate with Native. Only the core logic is introduced here

JSBridge = {
    // A collection of locally registered methods, native can only actively call locally registered methods
    messageHandlers: {},
    // A collection of short-term callback functions that are automatically removed after a native call to the corresponding method
    responseCallbacks: {},
    // A collection of long-lived callbacks that can be called multiple times
    responseCallbacksLongTerm: {},
    
    _handleMessageFromNative: function(messageJSON) {
        // Internal processing:/ ** if it is a callback function: If it is a short-term correction responseCallbacks query callback id, and perform, destroyed automatically after execution If it is a short-term correction responseCallbacksLongTerm query callback id, and perform the * / / * * if you are Native active call: Go to messageHandlers, the locally registered method pool, and do * /},callHandler: function(.) {
        // The underlying layer calls the native receiver methods of Android or iOS, respectively
        
        // If it is a short callback, the callback is added to the responseCallbacks
        / / if it is a long-term callback, the callback will be added to the responseCallbacksLongTerm
        
        // omit some logic. if (os.ios) {/ / ios adoption
            window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(...) ; }else {
            window.top.prompt(...);
        }
    },
    
    registerHandler: function(handlerName, handler) {
        // H5 registers methods locally that can be called natively},... };Copy the code

Android bridge object implementation

The core of Android is JSBridge, and the rest is built around it. Here is the pseudocode, listing the main logic

public class JSBridge {
    // Cache all API modules (added during registration)
    static exposedAPIModlues =  new HashMap<>();
    
    static register(String apiModelName, Class<? extends IBridgeImpl> clazz) {
        // All framework API modules are automatically found during registration and added to the cache exposedAPIModlues. Each module can have several apis
        // Each module needs to implement the IBridgeImpl interface. }static callAPI(...) {
        // The parameters (passed in H5) are parsed first, which API is called, and what is passed
        / / the port: H5 passed callback id, is the key in the responseCallbacks or responseCallbacksLongTerm
        ModuleName: the moduleName of the API called to retrieve modules registered in exposedAPIModlues
        // name: the method name of the API to be called. Look for the API in the found module
        // Others: include parameters passed, etc
        
        // A callback object is then generated based on the callback port number of H5.
        Callback callback = new Callback(port);
        
        // Then look for API methods based on parsed parameters
        // java.lang.reflect.Method;
        Method method = searchMethodBy(moduleName, name);
        
        // No method was found to call back for error messages
        // Otherwise, execute the method for which the parsed argument is passed
        // Call back to H5 for information within method
        method.invoke(..., callback);
    }
}
Copy the code

Callback class pseudocode is as follows:

public class Callback {
    apply(...) {
        // Parses the assembled parameters and assembs them into javascript code that contains the port value (Callback ID) for the Callback.. String js = javascript:JSBridge._handleMessageFromNative(for json arguments); callJS(js); } callHandler(...) {// Call H5 initiatively, encapsulating handleName instead of callback ID. callJS(js); } callJS(js) {// The underlying implementation is loadUrl. webviewContext.loadUrl(js); }}Copy the code

The IBridgeImpl interface is empty and is an abstract definition. Here is an example of an API that implements this interface

For clarity, use ui.alert as an example
public class xxxApi implements IBridgeImpl {
    // Define a registered module alias for easy lookup, such as UI
    static RegisterName = "ui";
    
    // An API in the module, such as alert
    public static voidalert(... , Callback callback) {// The next step is to implement the logic in this API.// Finally, H5 is notified by triggering callback
        callback.apply(...);
    }
}
Copy the code

Finally, you can see that in the WebView, the onJsPrompt of the WebChromeClient has been reactivated to receive the H5 call

And JSBridge register is called when the WebView loads

public class XXXWebChromeClient extends WebChromeClient { @Override public boolean onJsPrompt(... , JsPromptResult result) {// Internally trigger jsbridge.calljavaresult.confirm(JSBridge.callJava(...) );return true; }}Copy the code

The above several are Andorid JSBridge core implementation, other such as long-term callback, short-term callback, detail implementation optimization is not the core logic, on the list, details can refer to the final source code

IOS bridge object implementation

There is still the OC, main reference marcuswestin/WebViewJavascriptBridge implementation

Core is still a WKWebViewJavascriptBridge, everything else is through it to delivery agent

@ implementation WKWebViewJavascriptBridge {/ / internal based on a WebViewJavascriptBridgeBase base class (interactive methods defined in the base class) WebViewJavascriptBridgeBase *_base; } /** * API */ - (void)callHandler:(NSString *)handlerName data:(id)data { Send data to H5} - (void)registerModuleFrameAPI { Module USES the alias agents [self registerHandlersWithClassName: @ "UIApi moduleName:" @ "UI"]. / / which is registerHandlersWithClassName presented.demonstration registered the module to the role of global, filibuster-proof majority} - (void) excuteMessage message: (nsstrings *) {/ / internal implementations of the API, // module.name,port(callbackID), etc. } #pragma mark - WKScriptMessageHandler is a protocol that follows, - (void)userContentController:(WKUserContentController *)userContentController DidReceiveScriptMessage :(WKScriptMessage *)message {// listen for API calls, Underlying invokes excuteMessage if ([message. The name isEqualToString: @ "WKWebViewJavascriptBridge"]) {[self excuteMessage: message. The body].  }}Copy the code

Then look at the realization of its base class WebViewJavascriptBridgeBase

@implementation WebViewJavascriptBridgeBase

- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
    // The underlying layer assembles the received data into JS code for execution. NSString* javascriptCommand = [NSString stringWithFormat:@"JSBridge._handleMessageFromNative('%@');", messageJSON];
    
    [_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
}

- (void)excuteMsg:(NSString *)messageQueueString moduleName:(NSString *)moduleName {
    // Find the registered handler based on the module name and API name.// Then create a callback objectWVJBResponseCallback responseCallback = (notify H5 of callback data via sendData);// Then execute this handler
    handler(message[@"data"], responseCallback);
}
Copy the code

Next is the definition of the API

RegisterBaseClass, the base class that all modules must implement, defines how to register

Handlers and Handlers {// subclasses override the handlers and handlers of the API} #pragma Mark-handler access - (void)registerHandlerName:(NSString *)handleName handler:(WVJBHandler)handler {// register an API for a module} - (WVJBHandler)handler:(NSString *)handlerName {// get the API by name}Copy the code

To define an API module, inherit RegisterBaseClass and override registerHandlers (use uI.alert for clarity).

@implementation UIApi
- (void)registerHandlers {
    [self registerHandlerName:@"alert" handler:^(id data, WVJBResponseCallback responseCallback) {
        // Again, notify H5 via responseCallback after data has been received and processed. responseCallback(...) ; }}Copy the code

When in the webview to load is called WKWebViewJavascriptBridge registerModuleFrameAPI, for UI module name and alias UIApi, can see at the time of registration, there are one-to-one correspondence relationship between them

And then when the WebView is created, it’s going to listen, userContentController

WKWebViewConfiguration * webConfig = [[WKWebViewConfiguration alloc] init]; WKUserContentController * userContentVC = [[WKUserContentController alloc] init]; webConfig.userContentController = userContentVC; WKWebView * wk = [[WKWebView alloc] initWithFrame: CGRectZero configuration: webConfig]; self.wv = wk; ./ / agent
self.bridge = [WKWebViewJavascriptBridge bridgeForWebView: self.wv];
[self.bridge setWebViewDelegate: self];

// Add a bridge for js to call oc. The name here corresponds to the name in WKScriptMessage, which we think of as the method name in most cases.
[self.wv.configuration.userContentController addScriptMessageHandler: self.bridge name: @"WKWebViewJavascriptBridge"];
Copy the code

Similarly, other non-core things in iOS, such as long-term callbacks, have been temporarily hidden

The design of the API

According to the above implementation, a complete JSBridge interaction process can be constructed, and the interaction between H5 and Native has been realized

The next step is to design the API to actually call the outside world

To be precise, the API design has moved beyond the JSBridge interaction to the hybrid framework application level, so there will be a separate section on the API in Quick Hybrid

How is the API implemented? See IBridgeImpl for Android and RegisterBaseClass for iOS and override the registerHandlers

As for what API to plan for, it depends on the actual requirements, but in general, things like UI.alert and so on are required

For more details, see the following sections

conclusion

One last picture just to reinforce it

At this point, the entire JSBridge interaction is complete

In summary, considered a variety of forms, found that if the full text description, very boring, it is difficult to insist on reading it, if it is all sorts of principle are described in the drawing +, found that will simplify as complexity, abruptly increase the difficulty for level, so eventually adopt the pseudo code (false and true) display form (excluding some invalid information, Extract the key, and it doesn’t conflict with the final code)

Although said, this whole set of procedures are not particularly difficult place, involving knowledge points are not particularly deep. But it includes the front end, Android, iOS three areas. Therefore, if you want to make the whole set of work better, it is better to have a division of labor, compared to a person’s limited energy, really specialized in multiple fields of people is relatively few, and the subsequent various optimization content is also a lot of (API, optimization, and so on…)

Back to root

  • How to implement a Hybrid framework

The source code

Github implements the framework

quickhybrid/quickhybrid

The appendix

The resources

  • marcuswestin/WebViewJavascriptBridge