【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.mzph.cn/news/390487.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试题 10.02. 变位词组

编写一种方法&#xff0c;对字符串数组进行排序&#xff0c;将所有变位词组合在一起。变位词是指字母相同&#xff0c;但排列不同的字符串。 注意&#xff1a;本题相对原题稍作修改 示例: 输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”], 输出: [ [“ate”,…

智能合约设计模式

2019独角兽企业重金招聘Python工程师标准>>> 设计模式是许多开发场景中的首选解决方案&#xff0c;本文将介绍五种经典的智能合约设计模式并给出以太坊solidity实现代码&#xff1a;自毁合约、工厂合约、名称注册表、映射表迭代器和提款模式。 1、自毁合约 合约自毁…

「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;当完成上云前的可行性评估与方案设计后…

Python os.chdir() 方法

概述 os.chdir() 方法用于改变当前工作目录到指定的路径。 语法 chdir()方法语法格式如下&#xff1a; os.chdir(path) 参数 path -- 要切换到的新路径。 返回值 如果允许访问返回 True , 否则返回False。 实例 以下实例演示了 chdir() 方法的使用&#xff1a; #!/usr/bin/pyth…

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…

Java逆向基础之AspectJ的获取成员变量的值

注意&#xff1a;由于JVM优化的原因&#xff0c;方法里面的局部变量是不能通过AspectJ拦截并获取其中的值的&#xff0c;但是成员变量可以在逆向中&#xff0c;我们经常要跟踪某些类的成员变量的值&#xff0c;这里以获取ZKM9中的qs类的成员变量g为例进行说明在StackOverFlow上…

Spark 键值对RDD操作

https://www.cnblogs.com/yongjian/p/6425772.html 概述 键值对RDD是Spark操作中最常用的RDD&#xff0c;它是很多程序的构成要素&#xff0c;因为他们提供了并行操作各个键或跨界点重新进行数据分组的操作接口。 创建 Spark中有许多中创建键值对RDD的方式&#xff0c;其中包括…

Python高级网络编程系列之基础篇

一、Socket简介 1、不同电脑上的进程如何通信&#xff1f; 进程间通信的首要问题是如何找到目标进程&#xff0c;也就是操作系统是如何唯一标识一个进程的&#xff01; 在一台电脑上是只通过进程号PID&#xff0c;但在网络中是行不通的&#xff0c;因为每台电脑的IP可能都是不一…

剑指 Offer 67. 把字符串转换成整数

写一个函数 StrToInt&#xff0c;实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。 首先&#xff0c;该函数会根据需要丢弃无用的开头空格字符&#xff0c;直到寻找到第一个非空格的字符为止。 当我们寻找到的第一个非空字符为正或者负号时&#xff0c…

4.RabbitMQ Linux安装

这里使用的Linux是CentOS6.2 将/etc/yum.repo.d/目录下的所有repo文件删除 先下载epel源 # wget -O /etc/yum.repos.d/epel-erlang.repo http://repos.fedorapeople.org/repos/peter/erlang/epel-erlang.repo 修改epel-erlang.repo文件&#xff0c;如下图 添加CentOS 的下载源…

二、数据库设计与操作

一、 数据库设计仿QQ数据库一共包括5张数据表&#xff0c;每张数据表结构如下&#xff1a;1、 tb_User&#xff08;用户信息表&#xff09;这张表主要用来存储用户的好友关系与信息字段名数据类型是否Null值默认值绑定描述IDint否用户账号PwdVarchar(50)否用户密码Frie…

剑指 Offer 36. 二叉搜索树与双向链表

输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点&#xff0c;只能调整树中节点指针的指向。 为了让您更好地理解问题&#xff0c;以下面的二叉搜索树为例&#xff1a; 我们希望将这个二叉搜索树转化为双向循环链表。链表…

1736. 替换隐藏数字得到的最晚时间

给你一个字符串 time &#xff0c;格式为 hh:mm&#xff08;小时&#xff1a;分钟&#xff09;&#xff0c;其中某几位数字被隐藏&#xff08;用 ? 表示&#xff09;。 有效的时间为 00:00 到 23:59 之间的所有时间&#xff0c;包括 00:00 和 23:59 。 替换 time 中隐藏的数…

十 web爬虫讲解2—Scrapy框架爬虫—Scrapy安装—Scrapy指令

Scrapy框架安装 1、首先&#xff0c;终端执行命令升级pip: python -m pip install --upgrade pip2、安装&#xff0c;wheel(建议网络安装) pip install wheel3、安装&#xff0c;lxml(建议下载安装)4、安装&#xff0c;Twisted(建议下载安装)5、安装&#xff0c;Scrapy(建议网络…

阿里与珠海横琴新区达成战略合作,阿里云助力打造横琴智能岛

5月17日&#xff0c;阿里巴巴集团、蚂蚁金服集团与珠海横琴新区管理委员会签署战略合作协议&#xff0c;三方将围绕云计算、政务民生服务、城市治理、电子商务等领域展开深入合作&#xff0c;推动横琴产业发展&#xff0c;共同建设新型智慧城市。 &#xff08;阿里巴巴集团、蚂…

chrome 开发工具_我最喜欢的Chrome开发工具提示和技巧

chrome 开发工具Chrome Developer Tools are a super powerful suite of tools for developing web applications. They can do so much, from very basic operations like traversing the DOM, to checking out network requests or even profiling your applications perform…

三十四 Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解

信号一般使用信号分发器dispatcher.connect()&#xff0c;来设置信号&#xff0c;和信号触发函数&#xff0c;当捕获到信号时执行一个函数 dispatcher.connect()信号分发器&#xff0c;第一个参数信号触发函数&#xff0c;第二个参数是触发信号&#xff0c; 以下是各种信号 sig…