本文介绍quick hybrid
框架的核心JSBridge
的实现javascript
因为在最新版本中,已经没有考虑iOS7
等低版本,所以在选用方案时没有采用url scheme
方式,而是直接基于WKWebView
实现前端
具体H5和Native的交互原理能够参考前文的H5和Native交互原理
java
交互原理图以下:ios
若是一步一步来分析,最后再看效果,可能会很枯燥,甚至还有点化简为繁的样子。(感受直接看代码应该是最简单的,奈何每次写成文章时都得加一大堆的描述)git
所以,先来看看最终完成后应该是什么样的。github
// 调用ui中alert的示例 callHandler({ // 模块名,本文中的API划分了模块 module: 'ui', // 方法名 name: 'alert', // 须要传递给native的请求参数 data: { message: 'hello', }, callback: function(res) { /** * 调用后的回调,接收原生传递的回调数据 * alert若是成功,能够点击后再回调 { // 1成功/0失败 code: 1, message: '描述', // 数据 data: {}, } */ } });
从头开始实现一个JSBridge,很容易两眼一抹黑,无从下手。web
所以咱们须要先从大方向上把功能交互肯定好,而后再开始构建细节,编码实现json
根据核心架构,规划须要实现的功能:api
H5桥接对象的设计(JSBridge)数组
原生桥接对象的设计
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(...); }
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中的核心就是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 { // 定义一个注册的模块别名,方便查找,譬如ui static RegisterName = "ui"; // 模块中的某个API,譬如alert public static void alert(..., Callback callback) { // 接下来就是在这个API中实现对于的逻辑 ... // 最后,经过触发callback通知H5便可 callback.apply(...); } }
最后能够看到,在webview
中,从新了WebChromeClient
的onJsPrompt
来接收H5的调用
而且在webview
加载时就会调用JSBridge
的register
public class XXXWebChromeClient extends WebChromeClient { @Override public boolean onJsPrompt(..., JsPromptResult result) { // 内部触发JSBridge.callJava result.confirm(JSBridge.callJava(...)); return true; } }
以上几个就是Andorid
中JSBridge核心实现,其余的如长期回调,短时间回调,细节实现等优化不是核心逻辑,就列举,详情能够参考最后的源码
这里仍然是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调用时,底层会调用excuteMessage if ([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回调数据); // 而后执行这个handler handler(message[@"data"], responseCallback); }
接下来是API的定义
定义API模块以前,须要先了解RegisterBaseClass
,全部模块必须实现的基类,定义了如何注册
@implementation RegisterBaseClass #pragma mark - 注册api的统一方法 - (void)registerHandlers { // 子类重写改方法实现自定义API注册 } #pragma mark - handler存取 - (void)registerHandlerName:(NSString *)handleName handler:(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
加载时就会调用WKWebViewJavascriptBridge
的registerModuleFrameAPI
,对于模块名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中的长期回调等其它一些非核心内容也暂时隐藏了
按照上述的实现,能够构建出一个完整的JSBridge交互流程,H5和Native的交互已经通了
接下来就是设计API真正给外界调用
准确的来讲,API的设计已经脱离了JSBridge交互内容,属于混合框架框架应用层次,所以后续会有单独的章节介绍quick hybrid
中的API
API如何实现?能够参考上文中Android的继承IBridgeImpl
法以及iOS的继承RegisterBaseClass
而后重写registerHandlers
至于该规划些什么API,这与实际的需求有关,不过通常状况下,像ui.alert
等等通常都是必须的
更多详情请待后续章节
最后再来一张图巩固下把
至此,整个JSBridge交互就已经完成了
其实在总结文章时,考虑过不少种形式,发现,
若是是全文字描述,十分枯燥,很难坚持读下来,
若是是各类原理都用绘图+描述,发现会化简为繁,硬生生把难度提升了几个level,
因此最终采用的是伪代码(半伪半真)展现形式(剔除一些无效信息,提取关键,并且还不和最终的代码冲突)
虽说,这整套流程都没有特别难的地方,涉及的知识点都不是特别深。可是却包含了前端,Android,iOS三个领域。
所以若是要将整套工做作的比较好的化最好仍是有分工的好,比较一我的的精力有限,真正专精多个领域的人仍是比较少的,
并且后续各个优化的内容也很多(API,优化,等等...)
github
上这个框架的实现