【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,一经查实,立即删除!

相关文章

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…