天天看點

WebViewJavascriptBridge機制解析

衆所周知WebViewJavaScriptBridge是一個iOS/OSX 在UIWebViews/WebViews中obj-C和javascript發送消息的一個橋接。

WebViewJavaScriptBridge的開源項目在此:這裡。

下面我們來對WebViewJavaScriptBridge的機制來進行分析:(以UIWebView為例)

一、obj-c調用javascript的機制

UIWebView是iOS最常用的SDK之一,它有一個stringByEvaluatingJavaScriptFromString方法可以将javascript嵌入頁面中,通過這個方法我們可以在iOS中與UIWebView中的網頁元素互動。

使用stringByEvaluatingJavaScriptFromString方法,需要等UIWebView中的頁面加載完成之後去調用。 

使用例子介紹:

[webView stringByEvaluatingJavaScriptFromString:@"document.location.href"]     //擷取目前頁面的url。

[webview stringByEvaluatingJavaScriptFromString:@"document.title"]     //擷取頁面title:

[webView stringByEvaluatingJavaScriptFromString:@"document.forms[0].submit();"] //表單送出:

從以上可以看出stringByEvaluatingJavaScriptFromString不僅可以執行js來擷取資訊,還可以通過調用js對webView執行操作。 是以可以通過此函數來實作obj-c到js的互動。

備注: stringByEvaluatingJavaScriptFromString這個方法有個地方需要注意, 算不上bug, 但确實有問題, 需要注意!

如果stringByEvaluatingJavaScriptFromString執行的是帶參數的js函數, 這個參數裡面如果帶有(\r \n ')等等, js那邊收不到這個值, 這些帶\的需要轉義,

二、 javascript調用obj-c

UIWebView對URL的跳轉進行攔截,可以通過自定義協定來捕獲請求,在通過obj-c的stringByEvaluatingJavaScriptFromString來擷取js對于obj-c函數的調用。

UIWebView對于跳轉的攔截方法:

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {

}

實質上oc與js的通信互動就是發送消息,也即函數調用,隻要在互動的過程正确的指定好對方需要調用的函數和參數就ok

oc-->js   stringByEvaluatingJavaScriptFromString,其參數是一NSString 字元串内容是js代碼(這又可以是一個js函數、一句js代碼或他們的組合),當js函數有傳回值或一句js代碼有值傳回可通過stringByEvaluatingJavaScriptFromString的傳回值擷取

js-->oc 利用webView的重定向原理(即重新在js中指定document.location的值,此為一url),隻要在這個url字元串中按自定義的規則指定好所需調用oc中的函數和參數,然後通過OC中的shouldStartLoadWithRequest函數去捕獲處理請求,處理完最後,如果js還想擷取一些傳回參數的話,同樣讓oc去通過stringByEvaluatingJavaScriptFromString調用剛js傳過來的回調js函數就行,順道把參數也一起傳了。

三、WebViewJavaScriptBridge實作機制

webViewJavaScriptBridge 包含三個檔案:

WebViewJavascriptBridge.h

WebViewJavascriptBridge.m

WebViewJavascriptBridge.js.txt

很明顯:WebViewJavascriptBridge.js.txt主要用于銜接UIWebView中的web page,而WebViewJavascriptBridge.h/m則主要用于與ObjC的native code打交道。他們作為一個整體,其實起到了一個“橋梁”的作用,這三個檔案封裝了他們具體的互動處理方式,隻開放出一些對外的涉及到業務處理的API,是以你在需要UIWebView與Native code互動的時候,引入該庫,則無需考慮太多的互動上的問題。整個的Bridge對你來說都是透明的,你感覺程式設計的時候,就像是web程式設計的前端和後端一樣清晰。他們使用互動如圖所示:

WebViewJavascriptBridge機制解析

WebViewJavaScriptBridge互動圖

下面我們對WebViewJavaScriptBridge的實作做出分析。

3.1 WebViewJavaScriptBridge中的初始化

WebViewJavaScriptBridge的初始化過程如下:

1. 初始化需要使用的資料結果

2. 儲存registerHandler的函數名與block

3. webView 加載完成載人js代碼

具體流程圖如下:

WebViewJavascriptBridge機制解析

bridge初始化流程圖

3.1 obj-c與js的互動

obj-c與js互動使用的機制是一樣的,是以單項調用的處理是混在一起,是以現在按照流程單獨梳理,最後在以源碼為例來說明。

3.1.1 obj-c調用js過程:

1、obj-c儲存回調的block,使用唯一碼objc_cb_* 辨別

2、obj-c拼裝data(資料),handlerName(調用名),回調辨別等資訊

3、 對資訊進行json拼裝,及js一些字元的轉碼

4、 執行js端的分發消息的函數

5、js執行完成,執行函數塊,調用register的responseCallback,發送responseId和responseData

6、 js拼裝資訊到消息隊列中,URL重定向;obj-c端攔截請求,執行js代碼拉取資料

7、obj-c端根據responseId來取得儲存的block,執行block

大緻流程圖如下:

WebViewJavascriptBridge機制解析

obj-c調用js流程圖

3.1.2 js調用obj-c的過程:

1、js端儲存data與handlerName的映射

2、js端生成唯一碼callbackId儲存responseCallback映射,并封裝callbackId到發送消息中

3、js端封裝後的消息放入發送消息隊列中, 并執行URL重定向

4、obj-c端攔截請求,判定是自定義協定後,執行js的_fetchQueue()拉取發送的資訊

5、obj-c端重新封裝responcallback 的block,執行時調用_queueMessage進行消息分發

6、從obj-c端的注冊隊列中擷取handler的block,并執行

7、執行後回調block,封裝了responseId和responseData資料,并進行分發

8、資料的特殊字元處理,調用js的函數_handleMessageFromObjC()來傳遞消息

9、js擷取分發的responseId,擷取儲存在隊列中的function

大緻的流程圖如下:

WebViewJavascriptBridge機制解析

js調用obj-c流程圖

3.2 源碼解讀

3.2.1 obj-c端源碼

因js調用涉及到自定義協定的重定向,是以定義了協定及host

#define kCustomProtocolScheme @"wvjbscheme"

#define kQueueHasMessage@"__WVJB_QUEUE_MESSAGE__"

WebViewJavaScriptBridge的初始化因為涉及到OSX端WebView與iOS端UIWebView,故根據不同的系統來做變量的統一定義,如下:

WebViewJavascriptBridge機制解析

OS X與iOS變量重定義

定義block,WVJBResponseCallback定義了回調block,js傳回資料後回調。WVJBHandler定義了register的block,用作不同情況下的處理。

typedef   void(^WVJBResponseCallback)(id  responseData);

typedef   void(^WVJBHandler)(id   data,WVJBResponseCallback   responseCallback);

obj-c外部接口,用于native調用使用。其中register與callhandler函數封裝資料後調用send處理。

WebViewJavascriptBridge機制解析

obj-c與js互動函數

send函數發送消息到js端的源碼:

WebViewJavascriptBridge機制解析

send封裝到調用js代碼

參數data為傳遞給js函數的參數,可為NSString、NSDictionary等,responseCallback則為obj-C的回調,此回調函數執行流程簡述為“js注冊函數執行完畢後,會傳回帶有responseId的消息,最後在obj-c會取出(回調存儲在responCallbacks字典中),并執行”,handlerName則為js定義的函數名稱。

源碼中在_queueMessage方法進行邏輯判斷:若在H5頁面或者js資源并未加載完畢時,在obj-C的webview中就調用了js函數,則會把相關的操作(存儲為Message格式)存儲在startupMessageQueue,等待相關資源加載完畢(即在webview的webViewDidFinishLoad生命周期函數中執行存儲在startupMessageQueue的指令數組,執行完畢并清除改隊列)再調用js的函數;否則若startupMessageQueue隊列為空,則直接執行暴露在js端的webViewJavascriptBridge.handleMessageFromObjC函數,擷取被調用的函數名和傳人參數,以及在objC的sendData:responseCallback:handlerName中設定的回調函數id—callbackId,最終執行js注冊函數,并最終想obj-c端發送“doSend({ responseId:callbackResponseId, responseData:responseData })”格式的消息,待objC接收到消息,解析responseId,執行回調函數。

webView載入H5的delegate函數:

WebViewJavascriptBridge機制解析

finishLoad函數

H5 頁面載入完成後,載入js資源,在對開始消息隊列中的資料進行處理,并置nil。 在把delegate抛出。

WebViewJavascriptBridge機制解析

url攔截

有重定向請求時,首先判斷scheme及host是自定的,使用_flushMessageQueue函數處理。

WebViewJavascriptBridge機制解析

obj-c端對js端處理

這段代碼通過回傳消息的responseId來判斷是回調還是call,來進行不同的解封調用。

3.2.2 js端源碼

js端定義的WebViewJavascriptBridge對象:

WebViewJavascriptBridge機制解析

js端WebViewJavascriptBridge類

js端的互動函數,邏輯處理,一個registerHandler和一個callHandler主要封裝。_dosend函數進行消息封裝放入發送消息隊列,在産生一個src(url scheme),供obj-c端shouldStartLoadWithRequest捕捉

WebViewJavascriptBridge機制解析

消息互動函數

obj-c端通過_fetchQueue()擷取發送的消息

WebViewJavascriptBridge機制解析

js拉取發送的消息

js中處理來自objc的消息,判斷同obj-c擷取到js端的消息

WebViewJavascriptBridge機制解析

_dispatchMessageFromObjC