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ù)據
    
[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ù)據
        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ù)據處理筐摘、還有js的回掉的處理方法(就是那個callbackId)處理完成后也會拼成一個key-value字典通過調用js傳回去(可以直接調用js)卒茬。
// js在接到這個json后,根據responseId讀取responseCallbacks中處理方法進行處理Native code返回的數(shù)據咖熟。

;(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ù)據
                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)系作者
  • 序言:七十年代末躬存,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子舀锨,更是在濱河造成了極大的恐慌,老刑警劉巖宛逗,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坎匿,死亡現(xiàn)場離奇詭異,居然都是意外死亡雷激,警方通過查閱死者的電腦和手機替蔬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屎暇,“玉大人承桥,你說我怎么就攤上這事「浚” “怎么了凶异?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長挤巡。 經常有香客問我剩彬,道長,這世上最難降的妖魔是什么矿卑? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任喉恋,我火速辦了婚禮,結果婚禮上母廷,老公的妹妹穿的比我還像新娘轻黑。我一直安慰自己,他們只是感情好琴昆,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布氓鄙。 她就那樣靜靜地躺著,像睡著了一般椎咧。 火紅的嫁衣襯著肌膚如雪玖详。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天勤讽,我揣著相機與錄音蟋座,去河邊找鬼。 笑死脚牍,一個胖子當著我的面吹牛向臀,可吹牛的內容都是我干的。 我是一名探鬼主播诸狭,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼券膀,長吁一口氣:“原來是場噩夢啊……” “哼君纫!你這毒婦竟也來了?” 一聲冷哼從身側響起芹彬,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蓄髓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后舒帮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體会喝,經...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年玩郊,在試婚紗的時候發(fā)現(xiàn)自己被綠了肢执。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡译红,死狀恐怖预茄,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情侦厚,我是刑警寧澤耻陕,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站假夺,受9級特大地震影響淮蜈,放射性物質發(fā)生泄漏。R本人自食惡果不足惜已卷,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一梧田、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侧蘸,春花似錦裁眯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晌坤,卻和暖如春逢艘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骤菠。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工它改, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人商乎。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓央拖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鲜戒,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內容