WebViewJavascriptBridge的使用和實現(xiàn)原理

在HTML添加交互代碼

<!-- script 嵌入JS代碼 -->
 window.onerror = function(err) {
   log('window.onerror: ' + err)
 }
 
 /*這段代碼是固定的,必須要放到js中*/
 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';
   //src是js交互的標識碼
   WVJBIframe.src = 'kadios://__KAD_BRIDGE_LOADED__';
   document.documentElement.appendChild(WVJBIframe);
   //加載這個方法后就刪除自定義的src 讓后面重定向url
   //把iframe一起干掉,既然改變src不會刷新頁面,重新創(chuàng)建一個iframe 就會刷新
   setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
 }

 /*與OC交互的所有JS方法都要放在此處注冊,才能調用通過JS調用OC或者讓OC調用這里的JS*/
 setupWebViewJavascriptBridge(function(bridge) {
   //JS 調用 OC方法 callHandler
   document.getElementById("showButton").onclick = function(e){
       bridge.callHandler('goHome:', {'name': 'lemon' + Math.random()}, function(response) {

       })
   }
     
   /*JS給ObjC提供公開的API,在ObjC端可以手動調用JS的這個API。接收ObjC傳過來的參數(shù),且可以回調ObjC*/
   //OC 調用JS registerHandler
   bridge.registerHandler('getUserInfos', function(data, responseCallback) {
     document.getElementById("changeTitle").innerHTML = data;
     //回調給ObjC
     responseCallback({'userId': '123456', 'title': 'Hello World!'})
   })
                              
 })  
     

app中使用WebViewJavascriptBridge的代碼

//創(chuàng)建WebViewJavascriptBridge做為屬性
@property (nonatomic, strong) WebViewJavascriptBridge *bridge;
//給webView建立JS與OC橋梁
self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView];
//設置代理
[self.bridge setWebViewDelegate:self ];

// 1.JS主動調用OjbC的方法
// 這是JS會調用getUserIdFromObjC方法,這是OC注冊給JS調用的
// JS需要回調,當然JS也可以傳參數(shù)過來。data就是JS所傳的參數(shù),不一定需要傳
// OC端通過responseCallback回調JS端,JS就可以得到所需要的數(shù)據(jù)
    
[self.bridge registerHandler:@"goHome:" handler:^(id data, WVJBResponseCallback responseCallback) {
   self.showDataLB.text = [NSString stringWithFormat:@"%@",data];
   [self.showDataLB sizeToFit];
   NSLog(@"goHome:%@",data);
}];


-(void) clickShowButton{
    //OC調用了js方法
    [self.bridge callHandler:@"getUserInfos" data:@"我點擊了showButton" responseCallback:^(id responseData) {
        //data是js的回調數(shù)據(jù)
        NSLog(@"%@",responseData);
    }];

}

上面的代碼是WebViewJavascriptBridge的基本使用。

下面是關于WebViewJavascriptBridge的原理

WebViewJavascriptBridge類的作用是綁定webView,在該類中處理WebView的代理。

js調用OC的原理

+ (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView {
    WebViewJavascriptBridge* bridge = [[self alloc] init];
    [bridge _platformSpecificSetup:webView];
    return bridge;
}

- (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView {
    _webView = webView;
    _webView.delegate = self;
    _base = [[WebViewJavascriptBridgeBase alloc] init];
    //base實例會把該注冊事件放進到base的一個消息池子(負責接受多個OC注冊事件)中,方便后續(xù)處理
    _base.delegate = self;
}

webView

//每次在重新定向URL的時候,這個方法就會被觸發(fā),通常情況,我們會在這里做一些攔截完成js和本地的間接交互什么的
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    NSURL *url = [request URL];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    //攔截URL看是不是 判斷是否是自定義的路徑
    //在html中我們設置了 (WVJBIframe.src = 'kadios://__KAD_BRIDGE_LOADED__';)
    //isCorrectProcotocolScheme 方法判斷 scheme 是否等于 kadios
    //isBridgeLoadedURL 方法 判斷 host 是否等于 __KAD_BRIDGE_LOADED__
    if ([_base isCorrectProcotocolScheme:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            //oc 調用 js
            //此時執(zhí)行在工程里面放置的JS文件
            [_base injectJavascriptFile];
            
            //由于js函數(shù)中一進來便主動觸發(fā)了registerHandler,所以url變成了 kadios://__KAD_QUEUE_MESSAGE__
            //文件執(zhí)行完畢 然后走下面else if
        } else if ([_base isQueueMessageURL:url]) {
            //url所以變成了 kadios://__KAD_QUEUE_MESSAGE__
            // evaluateJavascript:@"WebViewJavascriptBridge._fetchQueue();"
            //_fetchQueue() 取出消息字典里的內容 在js里面把 字典變成字符串
            //?? [webView stringByEvaluatingJavaScriptFromString:@"document.location.href"]    //獲取當前頁面的url。
            //如果不是js調用的 messageQueueString = [];
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        } else {
            [_base logUnkownMessage:url];
        }
        return NO;
    } else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
        return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    } else {
        return YES;
    }
}

[_base injectJavascriptFile]加載的js文件

//js這邊 先把方法名字、參數(shù)、處理方法保存成一個字典在轉成json字符串,在通過UIWebview調用js中某個方法把這個json字符串傳到Native中去,同時把這個處理的方法以key-value形式放到一個js的字典中。
// UIWebView在收到這個json之后,進行數(shù)據(jù)處理、還有js的回掉的處理方法(就是那個callbackId)處理完成后也會拼成一個key-value字典通過調用js傳回去(可以直接調用js)。
// js在接到這個json后,根據(jù)responseId讀取responseCallbacks中處理方法進行處理Native code返回的數(shù)據(jù)。

;(function() {
    if (window.WebViewJavascriptBridge) { return }
    var messagingIframe
    //發(fā)送的消息隊列
    var sendMessageQueue = []
    //接收消息隊列
    var receiveMessageQueue = []
    //回調映射
    var messageHandlers = {}
    //改變 wvjbscheme 和 __WVJB_QUEUE_MESSAGE__ 這連個組合標識 給webview 的 delegate判斷用的
    var CUSTOM_PROTOCOL_SCHEME = 'kadios'
    var QUEUE_HAS_MESSAGE = '__KAD_QUEUE_MESSAGE__'
    //js 調用 OC 如果有回調會加入這里
    var responseCallbacks = {}
    var uniqueId = 1
    //獲取iframe 
    function _createQueueReadyIframe(doc) {
        messagingIframe = doc.createElement('iframe')
        messagingIframe.style.display = 'none'
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
        //在iframe 最加 src(路徑)屬性
        doc.documentElement.appendChild(messagingIframe)
    }

    function init(messageHandler) {
        if (WebViewJavascriptBridge._messageHandler) { throw new Error('WebViewJavascriptBridge.init called twice') }
        WebViewJavascriptBridge._messageHandler = messageHandler
        var receivedMessages = receiveMessageQueue
        receiveMessageQueue = null
        for (var i=0; i<receivedMessages.length; i++) {
            _dispatchMessageFromObjC(receivedMessages[i])
        }
    }

    function send(data, responseCallback) {
        _doSend({ data:data }, responseCallback)
    }
    //oc 調用 js
    function registerHandler(handlerName, handler) {
        messageHandlers[handlerName] = handler
    }
    //js調用oc
    function callHandler(handlerName, data, responseCallback) {
        _doSend({ handlerName:handlerName, data:data }, responseCallback)
    }
    //js需要觸發(fā)oc必須調用該方法
    function _doSend(message, responseCallback) {
        if (responseCallback) {
            //callbackId 該事件的id  把回調方法與id綁定
            var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
            responseCallbacks[callbackId] = responseCallback
            message['callbackId'] = callbackId
        }
        //把字典放到消息隊列中
        sendMessageQueue.push(message)
        //產生一個url 執(zhí)行URL重定向
        messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
    }

    //sendMessageQueue 里面是消息字典  內容有 handlerName data  callbackId
    //這方法是取出 sendMessageQueue的內容
    function _fetchQueue() {
        var messageQueueString = JSON.stringify(sendMessageQueue)
        //獲取完后吧發(fā)送消息隊列清空 防止重復調用
        sendMessageQueue = []
        return messageQueueString
    }
    //js中處理oc的消息 判斷oc獲取到js端的消息
    function _dispatchMessageFromObjC(messageJSON) {
        setTimeout(function _timeoutDispatchMessageFromObjC() {
            var message = JSON.parse(messageJSON)
            var messageHandler
            var responseCallback
            //判斷oc的回調是 response 還是 callback 
            if (message.responseId) {
                responseCallback = responseCallbacks[message.responseId]
                if (!responseCallback) { return; }
                responseCallback(message.responseData)
                delete responseCallbacks[message.responseId]
            } else {
                //js 調用 oc后  oc返回數(shù)據(jù)
                if (message.callbackId) {
                    var callbackResponseId = message.callbackId
                    responseCallback = function(responseData) {
                        _doSend({ responseId:callbackResponseId, responseData:responseData })
                    }
                }
                //這段代碼 觸發(fā)js調用 responseCallback
                var handler = WebViewJavascriptBridge._messageHandler
                if (message.handlerName) {
                    handler = messageHandlers[message.handlerName]
                }
                
                try {
                    handler(message.data, responseCallback)
                } catch(exception) {
                    if (typeof console != 'undefined') {
                        console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception)
                    }
                }
            }
        })
    }
    //OC調用js的方法通過這方法
    function _handleMessageFromObjC(messageJSON) {
        if (receiveMessageQueue) {
            receiveMessageQueue.push(messageJSON)
        } else {
            _dispatchMessageFromObjC(messageJSON)
        }
    }
    //js定義 WebViewJavascriptBridge對象
    window.WebViewJavascriptBridge = {
        init: init,
        send: send,
        registerHandler: registerHandler,
        callHandler: callHandler,
        _fetchQueue: _fetchQueue,
        _handleMessageFromObjC: _handleMessageFromObjC
    }

    var doc = document
    _createQueueReadyIframe(doc)
    var readyEvent = doc.createEvent('Events')
    readyEvent.initEvent('WebViewJavascriptBridgeReady')
    readyEvent.bridge = WebViewJavascriptBridge
    doc.dispatchEvent(readyEvent)
})();

OC調用JS的原理

OC調用JS方法使用callHandler方法,該方法把需要的(data,handlerName,callbackId) 封裝成字典,然后把字典轉為String,然后把該String寫入到webView中,實現(xiàn)與js交互。

- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
    //轉換成字典
    NSMutableDictionary* message = [NSMutableDictionary dictionary];
    
    if (data) {
        message[@"data"] = data;
    }
    
    if (responseCallback) {
        NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
        self.responseCallbacks[callbackId] = [responseCallback copy];
        message[@"callbackId"] = callbackId;
    }
    
    if (handlerName) {
        message[@"handlerName"] = handlerName;
    }
    [self _queueMessage:message];
}


//webView 寫入js  OC調用JS方法
- (void)_dispatchMessage:(WVJBMessage*)message {
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
    //調用js中的_handleMessageFromObjC方法
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];

    } else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容