【quickhybrid】JSBridge的實現

前言

本文介紹quick hybrid框架的核心JSBridge的實現

由于在最新版本中,已經沒有考慮iOS7等低版本,因此在選用方案時沒有采用url scheme方式,而是直接基于WKWebView實現

交互原理

具體H5和Native的交互原理可以參考前文的H5和Native交互原理

交互原理圖如下:

jsbridge_principle.png

預計的最終效果

如果一步一步來分析,最后再看效果,可能會很枯燥,甚至還有點化簡為繁的樣子。(感覺直接看代碼應該是最簡單的,奈何每次寫成文章時都得加一大堆的描述)

因此,先來看看最終完成后應該是什么樣的。

// 調用ui中alert的示例
callHandler({// 模塊名,本文中的API劃分了模塊module: 'ui',// 方法名name: 'alert',// 需要傳遞給native的請求參數data: {message: 'hello',},callback: function(res) {/*** 調用后的回調,接收原生傳遞的回調數據* alert如果成功,可以點擊后再回調{// 1成功/0失敗code: 1,message: '描述',// 數據data: {},}*/}
});

架構

從頭開始實現一個JSBridge,很容易兩眼一抹黑,無從下手。

因此我們需要先從大方向上把功能交互確定好,然后再開始構建細節,編碼實現

jsbridge_structure.png

功能分析與確認

根據核心架構,規劃需要實現的功能:

  • H5橋接對象的設計(JSBridge)

    • 短期回調池,需自動回收

    • 長期回調池,可多次使用

    • 調用Native方法的通道,橋接對象上原生注冊的接收方法

    • 接收Native調用的通道,橋接對象上H5注冊的接收方法

    • H5可以注冊主動給原生調用的方法

  • 原生橋接對象的設計

    • 長期方法池,每一個長期調用都會存儲在回調池中,可以多次使用

    • 短期立即執行,每一個短期調用都是立即執行

    • 調用H5方法的通道,橋接對象上H5注冊的接收方法

    • 接收H5調用的通道,橋接對象上原生注冊的接收方法,底層自動解析,然后執行對應API

    • 回調對象,底層基于調用H5的通道,每次執行完畢后都通過回調對象回調給H5

    • 主動調用H5,不同于回調對象只能被動響應,這個可以主動調用H5中注冊的方法

  • API的設計

    • H5中的API,供前端調用,底層通過調用Native方法的通道,然后將預處理后的參數發送給原生

    • Native中的API,真正的功能實現

接下來就是JSBridge的實現

全局通信對象的確認

最重要的,是先把H5和Native通信時的幾個全局橋接對象確定:

  • JSBridge,H5端的橋接對象,對象中綁定了接收原生調用的方法_handleMessageFromNative,以及內部有對回調函數等進行管理

  • webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage,iOS端的橋接對象,這個方法接收H5的調用

  • prompt,Android端的橋接對象,為了方便,直接重寫了WebChromeClient中的onJsPrompt

// H5端的內部邏輯處理
window.JSBridge = {...}// 接收原生的調用,有回調以及主動調用兩種
JSBridge._handleMessageFromNative = function() {...}
// H5主動調用原生
if (os.ios) {// ios采用window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(...);
} else {window.top.prompt(...);
}

JSBridge對象的實現

H5就依靠這個對象與Native通信,這里僅介紹核心的邏輯

JSBridge = {// 本地注冊的方法集合,原生只能主動調用本地注冊的方法messageHandlers: {},// 短期回調函數集合,在原生調用完對應的方法后會自動刪除回收responseCallbacks: {},// 長期存在的回調集合,可以多次調用responseCallbacksLongTerm: {},_handleMessageFromNative: function(messageJSON) {// 內部的處理:/**如果是回調函數:如果是短期回調responseCallbacks中查詢回調id,并執行,執行后自動銷毀如果是短期回調responseCallbacksLongTerm中查詢回調id,并執行*//**如果是Native的主動調用:去本地注冊的方法池messageHandlers中搜索,并執行*/},callHandler: function(...) {// 底層分別調用Android或iOS的原生接收方法// 如果是短期回調,會將回調添加到responseCallbacks中// 如果是長期回調,會將回調添加到responseCallbacksLongTerm中// 省略若干邏輯...if (os.ios) {// ios采用window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(...);} else {window.top.prompt(...);}},registerHandler: function(handlerName, handler) {// H5在本地注冊可供原生調用的方法},...
};

Android中橋接對象的實現

Android中的核心就是JSBridge,其余都是圍繞這個來的,以下是偽代碼,列舉主要的邏輯

public class JSBridge {// 緩存所有的API模塊(注冊時添加進去)static exposedAPIModlues =  new HashMap<>();static register(String apiModelName, Class<? extends IBridgeImpl> clazz) {// 注冊時會自動尋找所有的框架API模塊,然后添加到緩存exposedAPIModlues,每一個模塊中可以有若干API// 每一個模塊都需要實現IBridgeImpl接口...}static callAPI(...) {// 首先會解析參數(H5中傳遞的),解析出調用了哪一個API,傳遞了些什么,解析結果包括如下// port:H5傳遞的回調id,是responseCallbacks或responseCallbacksLongTerm中的key// moduleName:調用的API的模塊名,用來檢索exposedAPIModlues中注冊的模塊// name:調用的API的方法名,在對于找到的模塊中去查找API// 其他:包括傳遞的參數等等// 然后會根據H5的回調端口號,生成一個回調對象(用來回調通知H5)Callback callback = new Callback(port);// 之后,根據解析的參數尋找API方法// java.lang.reflect.Method;Method method = searchMethodBy(moduleName, name);// 沒有找到方法會回調對于錯誤信息// 否則執行對于的method,傳遞解析出的參數// 并且在method內部執行完畢后主動回調給H5對于信息method.invoke(..., callback);}
}

callback類偽代碼如下:

public class Callback {apply(...) {// 先解析拼裝參數,然后將參數組裝成javascript代碼,參數中包含Callback對于的port值(回調id)...String js = javascript:JSBridge._handleMessageFromNative(對于的json參數);callJS(js);}callHandler(...) {// 主動調用H5,封裝的參數中不再是回調id,而是handleName...callJS(js);}callJS(js) {// 底層通過loadUrl執行...webviewContext.loadUrl(js);}
}

IBridgeImpl接口是空的,只是一個抽象定義,以下以某個實現這個接口的API為例

// 為了清晰,以ui.alert為例
public class xxxApi implements IBridgeImpl {// 定義一個注冊的模塊別名,方便查找,譬如uistatic RegisterName = "ui";// 模塊中的某個API,譬如alertpublic static void alert(..., Callback callback) {// 接下來就是在這個API中實現對于的邏輯...// 最后,通過觸發callback通知H5即可callback.apply(...);}
}

最后可以看到,在webview中,重新了WebChromeClientonJsPrompt來接收H5的調用

并且在webview加載時就會調用JSBridgeregister

public class XXXWebChromeClient extends WebChromeClient {@Overridepublic boolean onJsPrompt(..., JsPromptResult result) {// 內部觸發JSBridge.callJavaresult.confirm(JSBridge.callJava(...));return true;}
}

以上幾個就是Andorid中JSBridge核心實現,其他的如長期回調,短期回調,細節實現等優化不是核心邏輯,就列舉,詳情可以參考最后的源碼

iOS中橋接對象的實現

這里仍然是OC實現的,主要參考的marcuswestin/WebViewJavascriptBridge實現

核心仍然是WKWebViewJavascriptBridge,其余一切都是通過它來分發代理

@implementation WKWebViewJavascriptBridge {// 內部基于一個WebViewJavascriptBridgeBase基類(基類中定義交互方法)WebViewJavascriptBridgeBase *_base;    
}
/*** API*/
- (void)callHandler:(NSString *)handlerName data:(id)data {// 主動調用H5的方法// 底層調用_base的sendData,發送數據給H5
}- (void)registerModuleFrameAPI {// 注冊模塊API,模塊用到了別名代理[self registerHandlersWithClassName:@"UIApi" moduleName:@"ui"];// 其中registerHandlersWithClassName就是將模塊示例化注冊到全局中的作用,不贅述
}- (void)excuteMessage:(NSString *)message {// 內部執行API的實現,這里會解析API解析出來的數據,如// module.name,port(callbackid)等...// 然后底層調用_base的excuteMsg(它內部會根據注冊的API,找到相對應的,然后執行原生功能,最后通過回調通知H5)
}#pragma mark - WKScriptMessageHandler其實就是一個遵循的協議,它能讓網頁通過JS把消息發送給OC
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {// 監聽到對于API調用時,底層會調用excuteMessageif ([message.name isEqualToString:@"WKWebViewJavascriptBridge"]) {[self excuteMessage:message.body];}
}

然后看看它基類WebViewJavascriptBridgeBase的實現

@implementation WebViewJavascriptBridgeBase- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {// 底層將接收到的數據組裝成js代碼執行...NSString* javascriptCommand = [NSString stringWithFormat:@"JSBridge._handleMessageFromNative('%@');", messageJSON];[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];
}- (void)excuteMsg:(NSString *)messageQueueString moduleName:(NSString *)moduleName {// 底層根據對于的模塊,API名,找到注冊的handler...// 然后創建一個回調對象WVJBResponseCallback responseCallback = (通過sendData通知H5回調數據);// 然后執行這個handlerhandler(message[@"data"], responseCallback);
}

接下來是API的定義

定義API模塊之前,需要先了解RegisterBaseClass,所有模塊必須實現的基類,定義了如何注冊

@implementation RegisterBaseClass
#pragma mark - 注冊api的統一方法
- (void)registerHandlers {// 子類重寫改方法實現自定義API注冊
}#pragma mark - handler存取
- (void)registerHandlerName:(NSString *)handleNamehandler:(WVJBHandler)handler {// 注冊某個模塊下的某個API
}- (WVJBHandler)handler:(NSString *)handlerName {// 通過名稱獲取對應的API
}

要定義一個API模塊,則需繼承RegisterBaseClass然后重寫registerHandlers(為了清晰,以ui.alert為例)

@implementation UIApi
- (void)registerHandlers {[self registerHandlerName:@"alert" handler:^(id data, WVJBResponseCallback responseCallback) {// 同樣,在接收到數據,并處理后,通過responseCallback通知H5...responseCallback(...);}
}

webview加載時就會調用WKWebViewJavascriptBridgeregisterModuleFrameAPI,對于模塊名ui與別名UIApi,可以在注冊時看到,它們之間是有一一對應關系的

然后在webview創建時,會進行監聽,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;
...// 代理
self.bridge = [WKWebViewJavascriptBridge bridgeForWebView: self.wv];
[self.bridge setWebViewDelegate: self];// 添加供js調用oc的橋梁。這里的name對應WKScriptMessage中的name,多數情況下我們認為它就是方法名。
[self.wv.configuration.userContentController addScriptMessageHandler: self.bridge name: @"WKWebViewJavascriptBridge"];

同樣,iOS中的長期回調等其它一些非核心內容也暫時隱藏了

API的設計

按照上述的實現,可以構建出一個完整的JSBridge交互流程,H5和Native的交互已經通了

接下來就是設計API真正給外界調用

準確的來說,API的設計已經脫離了JSBridge交互內容,屬于混合框架框架應用層次,因此后續會有單獨的章節介紹quick hybrid中的API

API如何實現?可以參考上文中Android的繼承IBridgeImpl法以及iOS的繼承RegisterBaseClass然后重寫registerHandlers

至于該規劃些什么API,這與實際的需求有關,不過一般情況下,像ui.alert等等一般都是必須的

更多詳情請待后續章節

結束語

最后再來一張圖鞏固下把

jsbridge_interact.png

至此,整個JSBridge交互就已經完成了

其實在總結文章時,考慮過很多種形式,發現,
如果是全文字描述,十分枯燥,很難堅持讀下來,
如果是各種原理都用繪圖+描述,發現會化簡為繁,硬生生把難度提高了幾個level,
所以最終采用的是偽代碼(半偽半真)展示形式(剔除一些無效信息,提取關鍵,而且還不和最終的代碼沖突)

雖然說,這整套流程都沒有特別難的地方,涉及的知識點都不是特別深。但是卻包含了前端,Android,iOS三個領域。
因此如果要將整套工作做的比較好的化最好還是有分工的好,比較一個人的精力有限,真正專精多個領域的人還是比較少的,
而且后續各個優化的內容也不少(API,優化,等等...)

返回根目錄

  • 【quickhybrid】如何實現一個Hybrid框架

源碼

github上這個框架的實現

quickhybrid/quickhybrid

附錄

參考資料

  • marcuswestin/WebViewJavascriptBridge

轉載于:https://www.cnblogs.com/dailc/p/8098597.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/390487.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/390487.shtml
英文地址,請注明出處:http://en.pswp.cn/news/390487.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mongodb atlas_如何使用MongoDB Atlas將MERN應用程序部署到Heroku

mongodb atlas簡介 (Introduction to MERN) In this article, well be building and deploying an application built with the MERN stack to Heroku.在本文中&#xff0c;我們將構建和部署使用MERN堆棧構建的應用程序到Heroku。 MERN, which stands for MongoDB, Express, R…

面試題 10.02. 變位詞組

編寫一種方法&#xff0c;對字符串數組進行排序&#xff0c;將所有變位詞組合在一起。變位詞是指字母相同&#xff0c;但排列不同的字符串。 注意&#xff1a;本題相對原題稍作修改 示例: 輸入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”], 輸出: [ [“ate”,…

智能合約設計模式

2019獨角獸企業重金招聘Python工程師標準>>> 設計模式是許多開發場景中的首選解決方案&#xff0c;本文將介紹五種經典的智能合約設計模式并給出以太坊solidity實現代碼&#xff1a;自毀合約、工廠合約、名稱注冊表、映射表迭代器和提款模式。 1、自毀合約 合約自毀…

如何使用1Password,Authy和Privacy.com外包您的在線安全性

Take some work off your plate while beefing up security with three changes you can make today.通過今天可以進行的三項更改來增強安全性&#xff0c;同時省下一些工作。 Unstable times are insecure times, and we’ve already got enough going on to deal with. When…

「CodePlus 2017 12 月賽」火鍋盛宴

n<100000種食物&#xff0c;給每個食物煮熟時間&#xff0c;有q<500000個操作&#xff1a;在某時刻插入某個食物&#xff1b;查詢熟食中編號最小的并刪除之&#xff1b;查詢是否有編號為id的食物&#xff0c;如果有查詢是否有編號為id的熟食&#xff0c;如果有熟食刪除之…

5815. 扣分后的最大得分

給你一個 m x n 的整數矩陣 points &#xff08;下標從 0 開始&#xff09;。一開始你的得分為 0 &#xff0c;你想最大化從矩陣中得到的分數。 你的得分方式為&#xff1a;每一行 中選取一個格子&#xff0c;選中坐標為 (r, c) 的格子會給你的總得分 增加 points[r][c] 。 然…

您有一個上云錦囊尚未領取!

前期&#xff0c;我們通過文章《確認過眼神&#xff1f;上云之路需要遇上對的人&#xff01;》向大家詳細介紹了阿里云咨詢與設計場景下的五款專家服務產品&#xff0c;企業可以通過這些專家服務產品解決了上云前的痛點。那么&#xff0c;當完成上云前的可行性評估與方案設計后…

怎么從運營轉到前端開發_我如何在16個月內從銷售人員轉到前端開發人員

怎么從運營轉到前端開發On August 18, 2015, I was on a one-way flight headed to Copenhagen from Toronto Pearson Airport. I was starting my two semester exchange at the Copenhagen Business school. 2015年8月18日&#xff0c;我乘坐單程飛機從多倫多皮爾遜機場前往哥…

Python os.chdir() 方法

概述 os.chdir() 方法用于改變當前工作目錄到指定的路徑。 語法 chdir()方法語法格式如下&#xff1a; os.chdir(path) 參數 path -- 要切換到的新路徑。 返回值 如果允許訪問返回 True , 否則返回False。 實例 以下實例演示了 chdir() 方法的使用&#xff1a; #!/usr/bin/pyth…

oracle認證考試_Oracle云認證–通過此3小時免費課程通過考試

oracle認證考試This Oracle Cloud Certification exam will take – on average – about one week of study to prepare for. Most people who seriously commit to their studies are ready to pass the exam within about four days.這項Oracle Cloud認證考試平均需要大約一…

git 修改遠程倉庫源

自己已經寫好了一個項目&#xff0c;想上傳到 github github 創建新項目 新建 README.md &#xff0c; LICENSE 本地項目添加 github 遠程倉庫源 不是git項目git remote add origin https://USERNAME:PASSWORDgithub.com/USERNAME/pro.git已是git項目&#xff0c;先刪除再添加 …

Docker 常用命令備忘錄

build鏡像docker build -t"name" . 復制代碼后臺運行docker run -d -i -t 14a21c118315 /bin/bash 復制代碼刪除鏡像docker image rmi -f 300de37c15f9 復制代碼停止運行的鏡像docker ps docker kill (id) 復制代碼進入鏡像docker attach 29f2ab8e517c(ps id) 復制…

mvp最小可行產品_最低可行產品–如何為您的項目建立MVP以及為什么要這樣做

mvp最小可行產品具有足夠功能的產品可以收集全面的定性反饋 (A product with just enough features to gather comprehensive qualitative feedback) Proof of concept, prototypes, wireframes, mockups… what actually constitutes a Minimum Viable Product (MVP)?概念驗證…

composer 更改為中國鏡像

composer 更改為中國鏡像 $ composer config -g repo.packagist composer https://packagist.phpcomposer.com 轉載于:https://www.cnblogs.com/love-snow/articles/8111410.html

人人都能學會的python編程教程(基礎篇)完整版

人人都能學會的python編程教程1&#xff1a;第一行代碼 人人都能學會的python編程教程2&#xff1a;數據類型和變量 人人都能學會的python編程教程3&#xff1a;字符串和編碼 人人都能學會的python編程教程4&#xff1a;關系運算符與循環 人人都能學會的python編程教程5&#x…

劍指 Offer 56 - I. 數組中數字出現的次數

一個整型數組 nums 里除兩個數字之外&#xff0c;其他數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。要求時間復雜度是O(n)&#xff0c;空間復雜度是O(1)。 示例 1&#xff1a; 輸入&#xff1a;nums [4,1,4,6] 輸出&#xff1a;[1,6] 或 [6,1] 示例 2&#xff1a…

表達愛意的程序_如何像程序員一樣表達愛意??

表達愛意的程序Today is Valentines Day! &#x1f60d; 今天是情人節&#xff01; &#x1f60d; How nice would it be if you sent a Romantic Message every hour to your loved one? But even better... 如果您每小時向您所愛的人發送一封浪漫的短信&#xff0c;那將有多…

工作中的小問題

1、a標簽的選擇問題 需要修改帶class的a標簽的hover的文字顏色&#xff0c;方式如下 <style>a.egHyperlink:hover{color:red;} </style> <a href"#" class"egHyperlink">smile</a> 復制代碼2、hr分割線 需要一條粉紅色的分割線&am…

More DETAILS! PBR的下一個發展在哪里?

最近幾年圖形學社區對PBR的關注非常高&#xff0c;也許是由于Disney以及一些游戲引擎大廠的助推&#xff0c;也許是因為它可以被輕松集成進實時渲染的游戲引擎當中&#xff0c;也許是因為許多人發現現在只需要調幾個參數就能實現具有非常精細細節的表面著色了。反正現在網絡上隨…

sql server 2008 身份驗證失敗 18456

雙擊打開后加上 ;-m 然后以管理員方式 打開 SQLSERVER 2008 就可以已window身份登錄 不過還沒有完 右鍵 屬性 》安全性 更改為 sql server 和 window身份驗證模式 沒有sql server登陸賬號的話創建一個 然后把-m去掉就可以用帳號登錄了 轉載于:https://www.cnblogs.com/R…