专栏首页前端技术江湖JSBridge原理解析—以WebviewJavascriptBridge实现方式为例

JSBridge原理解析—以WebviewJavascriptBridge实现方式为例

一、什么是 JSBridge?

JSBridge 是一种 webview 侧和 native 侧进行通信的手段,webview 可以通过 jsb 调用 native 的能力,native 也可以通过 jsb 在 webview 上执行一些逻辑。

二、JSB 的实现方式

在比较流行的 JSBridge 中,主要是通过拦截 URL 请求来达到 native 端和 webview 端相互通信的效果的。

这里我们以比较火的 WebviewJavascriptBridge 为例,来解析一下它的实现方式。

源码地址:https://github.com/marcuswestin/WebViewJavascriptBridge

2-1、在 native 端和 webview 端注册 Bridge

注册的时候,需要在 webview 侧和 native 侧分别注册 bridge,其实就是用一个对象把所有函数储存起来。

function registerHandler(handlerName, handler) {
    messageHandlers[handlerName] = handler;
}
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
    _base.messageHandlers[handlerName] = [handler copy];
}

2-2、在 webview 里面注入初始化代码

function setupWebViewJavascriptBridge(callback) {
       if (window.WebViewJavascriptBridge) { return callback(WebViewJavascriptBridge); }
       if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
       window.WVJBCallbacks = [callback];
       var WVJBIframe = document.createElement('iframe');
       WVJBIframe.style.display = 'none';
       WVJBIframe.src = 'https://__bridge_loaded__';
       document.documentElement.appendChild(WVJBIframe);
       setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}

这段代码主要做了以下几件事:

(1)创建一个名为 WVJBCallbacks 的数组,将传入的 callback 参数放到数组内

(2)创建一个 iframe,设置不可见,设置 src 为https://__bridge_loaded__

(3)设置定时器移除这个 iframe

2-3、在 native 端监听 URL 请求

iOS 中有两种 webview,一种是 UIWebview,另一种是 WKWebview,这里以 WKWebview 为例:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
    if (webView != _webView) { return; }

    __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;
    if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationResponse:decisionHandler:)]) {
        [strongDelegate webView:webView decidePolicyForNavigationResponse:navigationResponse decisionHandler:decisionHandler];
    }
    else {
        decisionHandler(WKNavigationResponsePolicyAllow);
    }
}

这段代码主要做了以下几件事:

(1)拦截了所有的 URL 请求并拿到 url

(2)首先判断isWebViewJavascriptBridgeURL,判断这个 url 是不是 webview 的 iframe 触发的,具体可以通过 host 去判断。

(3)继续判断,如果是isBridgeLoadedURL,那么会执行injectJavascriptFile方法,会向 webview 中再次注入一些逻辑,其中最重要的逻辑就是,在 window 对象上挂载一些全局变量和WebViewJavascriptBridge属性,具体值如下:

window.WebViewJavascriptBridge = {
        registerHandler: registerHandler,
        callHandler: callHandler,
        disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
        _fetchQueue: _fetchQueue,
        _handleMessageFromObjC: _handleMessageFromObjC
};

var sendMessageQueue = [];
var messageHandlers = {};

var responseCallbacks = {};
var uniqueId = 1;

(4)继续判断,如果是 isQueueMessageURL,那么这就是个处理消息的回调,需要执行一些消息处理的方法(第四步会详细讲)

2-4、webview 调用 native 能力

当 native 和 webview 都注册好了 Bridge 之后,双方就可以互相调用了,这里先介绍 webview 调用 native 能力的过程。

2-4-1、webview 侧 callHandler

当 webview 调用 native 时,会调用 callHandler 方法,这个方法具体逻辑如下:

bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
       console.log("JS received response:", responseData)
})

function callHandler(handlerName, data, responseCallback) {
        if (arguments.length == 2 && typeof data == 'function') {
                responseCallback = data;
                data = null;
         }
        _doSend({ handlerName:handlerName, data:data }, responseCallback);
}

function _doSend(message, responseCallback) {
        if (responseCallback) {
               var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
               responseCallbacks[callbackId] = responseCallback;
               message['callbackId'] = callbackId;
         }
         sendMessageQueue.push(message);
         messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}

实际上就是先生成一个 message,然后 push 到 sendMessageQueue 里,然后更改 iframe 的 src。

2-4-2、native 侧 flushMessageQueue

然后,当 native 端检测到 iframe src 的变化时,会走到 isQueueMessageURL 的判断逻辑,然后执行 WKFlushMessageQueue 函数,获取到 JS 侧的 sendMessageQueue 中的所有 message。

- (void)WKFlushMessageQueue {
    [_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {
        if (error != nil) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);
        }
        [_base flushMessageQueue:result];
    }];
}

- (void)flushMessageQueue:(NSString *)messageQueueString{
    if (messageQueueString == nil || messageQueueString.length == 0) {
        NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");
        return;
    }

    id messages = [self _deserializeMessageJSON:messageQueueString];
    for (WVJBMessage* message in messages) {
        if (![message isKindOfClass:[WVJBMessage class]]) {
            NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);
            continue;
        }
        [self _log:@"RCVD" json:message];

        NSString* responseId = message[@"responseId"];
        if (responseId) {
            WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
            responseCallback(message[@"responseData"]);
            [self.responseCallbacks removeObjectForKey:responseId];
        } else {
            WVJBResponseCallback responseCallback = NULL;
            NSString* callbackId = message[@"callbackId"];
            if (callbackId) {
                responseCallback = ^(id responseData) {
                    if (responseData == nil) {
                        responseData = [NSNull null];
                    }

                    WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
                    [self _queueMessage:msg];
                };
            } else {
                responseCallback = ^(id ignoreResponseData) {
                    // Do nothing
                };
            }

            WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];

            if (!handler) {
                NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);
                continue;
            }

            handler(message[@"data"], responseCallback);
        }
    }
}

当一个 message 结构存在 responseId 的时候说明这个 message 是执行 bridge 后传回的。取不到 responseId 说明是第一次调用 bridge 传过来的,这个时候会生成一个返回给调用方的 message,其 reponseId 是传过来的 message 的 callbackId,当 native 执行 responseCallback 时,会触发_dispatchMessage 方法执行 webview 环境的的 js 逻辑,将生成的包含 responseId 的 message 返回给 webview。

2-4-3、webview 侧 handleMessageFromObjC

function _handleMessageFromObjC(messageJSON) {
    _dispatchMessageFromObjC(messageJSON);
}

function _dispatchMessageFromObjC(messageJSON) {
       if (dispatchMessagesWithTimeoutSafety) {
             setTimeout(_doDispatchMessageFromObjC);
 } else {
             _doDispatchMessageFromObjC();
 }

 function _doDispatchMessageFromObjC() {
        var message = JSON.parse(messageJSON);
        var messageHandler;
        var responseCallback;
        if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId];
                if (!responseCallback) {
                          return;
                }
                responseCallback(message.responseData);
                delete responseCallbacks[message.responseId];
         } else {
               if (message.callbackId) {
                       var callbackResponseId = message.callbackId;
                       responseCallback = function(responseData) {
                             _doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });
                        };
                }

                var handler = messageHandlers[message.handlerName];
                if (!handler) {
                        console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
                } else {
                        handler(message.data, responseCallback);
                }
          }
     }
}

如果从 native 获取到的 message 中有 responseId,说明这个 message 是 JS 调 Native 之后回调接收的 message,所以从一开始 sendData 中添加的 responseCallbacks 中根据 responseId(一开始存的时候是用的 callbackId,两个值是相同的)取出这个回调函数并执行,这样就完成了一次 JS 调用 Native 的流程。

2-4-4、过程总结

过程如下图

1、native 端注册 jsb

2、webview 侧创建 iframe,设置 src 为__bridge_load__

3、native 端捕获请求,注入 jsb 初始化代码,在 window 上挂载相关对象和方法

4、webview 侧调用callHandler方法,并在responseCallback上添加callbackId: responseCallback,并修改 iframe 的 src,触发捕获

5、native 收到 message,生成一个responseCallback,并执行 native 侧注册好的方法

6、native 执行完毕后,通过 webview 执行_handleMessageFromObjC方法,取出 callback 函数,并执行

2-5、native 调用 webview 能力

native 调用 webview 注册的 jsb 的逻辑是相似的,不过就不是通过触发 iframe 的 src 触发执行的了,因为 Native 可以自己主动调用 JS 侧的方法。其具体过程如下图:

1、native 侧调用callHandler方法,并在responseCallback上添加callbackId: responseCallback

2、native 侧主动调用_handleMessageFromObjC方法,在 webview 中执行对应的逻辑

3、webview 侧执行结束后,生成带有responseId的 message,添加到sendMessageQueue中,并修改 iframe 的 src 为__wvjb_queue_message__

4、native 端拦截到 url 变化,调用 webview 的逻辑获取到 message,拿到responseId,并执行对应的 callback 函数

本文分享自微信公众号 - 前端技术江湖(bigerfe)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-06-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • android和js的交互之jsbridge使用教程

    众所周知,app的一些功能可能会使用到H5开发,这就难免会遇到java与js 的相互调用,android 利用WebViewJavascriptBridge 实...

    砸漏
  • 搞定混合开发面试,这一篇就够了!

    上面的定义是百度百科的官话,用白话文翻译就:所谓hybridApp就是在app中嵌入web页面

    用户7413032
  • objC与js通信实现--WebViewJavascriptBridge

    场景   在移动端开发中,最为流行的开发模式就是hybmid开发,在这种native和h5的杂糅下,既能在某些需求中保证足够的性能,也可以在某些列表详情的需求下...

    欲休
  • iOS下JS与OC互相调用(五)--UIWebView + WebViewJavascriptBridge

    WebViewJavascriptBridge是一个有点年代的JS与OC交互的库,使用该库的著名应用还挺多的,目前这个库有7000+star。我去翻看了它的第一...

    Haley_Wong
  • WebViewJavaScriptBridge深入剖析

    前一篇文章中,我们大致的讲述了一下JavaScriptCore这个库在iOS开发中的应用。在文中最后的阶段,我们提到了WebViewJavaScriptBrid...

    iOSSir
  • 【iOS 开发】WebViewJavaScriptBridge 实现原理

    WebViewJavaScriptBridge 使用一个不可见的 iFrame 来触发消息传递, 应该是为了兼容 UIWebView 和 WKWebView。

    吴老师
  • [源码分析]iOS WebViewJavascriptBridge使用及原理

    在开发中涉及到网页与iOS原生交互,于是就想到了WebViewJavascriptBridge 这个开源库。经过最近几天的研究和了解算是熟悉了它的使用和大致的处...

    Light413
  • iOS下JS与OC互相调用(六)--WKWebView + WebViewJavascriptBridge

    上一篇文章介绍了UIWebView 如何通过WebViewJavascriptBridge 来实现JS 与OC 的互相调用,这一篇来介绍一下WKWebView ...

    Haley_Wong
  • 【Web技术】252- Hybrid 应用中 H5 与 NA 通信的那点事儿

    Hybrid App 是半原生半 web 的开发模式,部分界面或者功能是原生的,其余部分是 html 和 js 来完成,最终 webview 加载 H5 页面;...

    pingan8787
  • 探究Hybrid-APP技术原理

    随着Web技术的发展和移动互联网的发展,Hybrid技术已经成为一种前端开发的主流技术方案。那什么是Hybrid App呢?

    Tiffany_c4df
  • JSBridge深度剖析

    概述 做过混合开发的人都知道Ionic和PhoneGap之类的框架,这些框架在web基础上包装一层Native,然后通过Bridge技术的js调用本地的库。 在...

    xiangzhihong
  • 小白必看,JSBridge 初探

    近些年,移动端普及化越来越高,开发过程中选用 Native 还是 H5 一直是热门话题。Native 和 H5 都有着各自的优缺点,为了满足业务的需要,公司实际...

    前端劝退师
  • js和native交互方法浅析

    一、背景 最近接触公司项目,需要和原生app做交互,由此业务需求,开始了学习探索之路。 二、解决方案之WebViewJavascriptBridge  想要和a...

    吴裕超
  • 小白必看,JSBridge 初探

    近些年,移动端普及化越来越高,开发过程中选用 Native 还是 H5 一直是热门话题。Native 和 H5 都有着各自的优缺点,为了满足业务的需要,公司实际...

    政采云前端团队
  • WebViewJavascriptBridge源码探究--看OC和JS交互过程

          今天把实现OC代码和JS代码交互的第三方库WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了stri...

    tandaxia
  • Hybrid App技术解析 -- 原理篇

    随着 Web 技术和移动设备的快速发展,Hybrid 技术已经成为一种最主流最常见的方案。一套好的 Hybrid架构方案 能让 App 既能拥有极致的体验和性能...

    程序员宝库
  • 前端工程师所需要了解的WebView

    | 导语 现如今,在做移动端 H5 开发时,少不了与 Native 之间进行交互。而在Native中,H5的承载容器为 WebView,其核心是使用 WebVi...

    秋风的笔记
  • 用WebViewJavascriptBridge进行JS和app进行交互

    做项目的时候有一个业务是需要前端web给安卓发送指令,进行拍照个读取身份证的操作,这个时候是需要用户进入页面的时候直接进行的操作,那么这个时候我需要做的是告诉安...

    何处锦绣不灰堆
  • UIWebView与JS的交互

    要实现这样一个需求:按照本地的CSS文件展示一串网络获取的带HTML格式的只有body部分的文本,需要自己拼写完整的HTML。除此之外,还需要禁用获取的HTML...

    用户2491699

扫码关注云+社区

领取腾讯云代金券