特别说明
首先说明本文并非原创,原文在此:https://www.cnblogs.com/dailc/p/5931324.html#callback-format。相关代码会放在https://github.com/Samuel2306/JSBridge。
什么是JSBridge
小伙伴如果如果对于Hybrid并没有什么概念,请先阅读笔者的另外一篇文章《Hybrid开发——Android与H5的亲密接触》。JSBridge就是一个简化Native与JS通信的框架。JSBridge定义了统一的通信过程:Native只通过一个固定的桥对象调用JS,JS也只通过固定的桥对象调用Native,具体流程如下图所示:
说明:JS触发原生的回调函数的过程其实也是触发一个url schema
url scheme介绍
• url scheme是一种类似于url的链接,是为了方便app直接互相调用设计的
可以用系统的OpenURI打开一个类似于url的链接(可拼入参数),然后系统会进行判断,如果是系统的url scheme,则打开系统应用,否则找看是否有app注册这种scheme,打开对应app需要注意的是,这种scheme必须原生app注册后才会生效,如微信的scheme为(weixin://)
• 本文JSBridge中的url scheme则是仿照上述的形式的一种方式
app不会注册对应的scheme,而是由前端页面通过某种方式触发scheme(如用iframe.src),然后Native用某种方法捕获对应的url触发事件,然后拿到当前的触发url,根据定义好的协议,分析当前触发了那种方法。简而言之就是url => 方法(对应关系是JS开发与Android开发商量定义好的)。
为什么要用JSBridge
在笔者的《Hybrid开发——Android与H5的亲密接触》一文中,我们知道Android已经可以跟JS进行交互,那为什么还要这种通过url scheme的JSBridge方式呢,原因大致如下:
• Android4.2以下,addJavascriptInterface方式有安全漏掉
• iOS7以下, JS无法调用Native
url scheme交互方式是一套现有的成熟方案,可以完美兼容各种版本,不存在上述问题。而且JSBridge将Native与JS的交互方式进行解耦,提供了一个可扩展的,高可用的,稳定的解决方案。
实现一个JSBridge
实现步骤大致如下:
第一步: 设计出一个Native与JS交互的全局桥对象
第二步: JS调用Native功能实现
第三步: Native监听api调用
第四步: Native分析url-参数和回调的格式
第五步: Native调用JS功能实现
第六步: H5中api方法的注册
第一步: 设计出一个Native与JS交互的全局桥对象
我们规定,JS和Native之间的通信必须通过一个H5全局对象JSBridge来实现,该全局对象有如下特点:
• 该对象名为“JSBridge”,是H5页面中全局对象window的一个属性
window.JSBridge = JSBridge
• 该对象有如下方法
1、registerHandler( String handlerName, Function handler):供H5调用。用来注册JS方法,Native可通过JSBridge调用注册的JS方法。调用registerHandler方法后,被注册的方法 handler 会被保存到本地变量 messageHandlers 中。
2、callHandler( String handlerName, JSON data, Function callback):供H5调用。H5调用原生开放的api,调用后实际上还是本地通过url scheme触发。调用时会将回调 callback 的 id(由callHandler函数生成)存放到本地变量responseCallbacks中。
3、_handleMessageFromNative( JSON ):供Native调用。原生调用H5页面注册的方法,或者通知H5页面执行回调方法(H5调用原生方法时,会将回调函数作为参数进行传递,就是为了让原生能在适当的时候通过JSBridge调用回调函数,也就是responseCallbacks里面对应的函数)。
第二步: JS调用Native功能实现
上一步我们已经定义好了JSBridge对象,H5可以通过callHandler方法来调用原生的API,那么callHandler内部经历了一个怎么样的过程呢?接下来我们就来说说callHandler( String handlerName, JSON data, Function callback)函数内部实现过程。
在执行callHandler( String handlerName, JSON data, Function callback)时,内部经历了以下步骤:
(1)首先判断H5调用该方法的时候有没有传入回调函数。如果有回调函数,则生成一个回调函数id, 并将id和对应的回调函数添加进入回调函数的集合 responseCallbacks 中。
(2)通过特定的参数转换方法,将传入的数据(data), 方法名(handlerName)一起,拼接成一个url scheme
// url scheme的格式如下,基本有用信息就是后面的callbackId,handlerName与data
// 原生捕获到这个scheme后会进行分析
var uri = CUSTOM_PROTOCOL_SCHEME://API_Name:callbackId/handlerName?data
(3)使用内部早就创建好的一个隐藏iframe来触发scheme
//创建隐藏iframe过程
var messagingIframe = document.createElement('iframe');
messagingIframe.style.display = 'none';
document.documentElement.appendChild(messagingIframe);
//触发scheme
messagingIframe.src = uri;
注意1:正常来说是可以通过window.location.href达到发起网络请求的效果的,但是有一个很严重的问题,就是如果我们连续多次修改window.location.href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。所以JS端发起网络请求的时候,需要使用iframe,这样就可以避免这个问题。
注意2:H5调用Native时,Native处理完毕后一定要及时通知H5进行回调,要不然这个回调函数不会自动销毁,多了后会引发内存泄漏
第三步: Native监听api调用
在上一步中,我们已经成功在H5页面中触发scheme,那么Native如何捕获scheme被触发呢?接下来我们以Android为例,说说Android如何捕获url scheme,不过在此之前我们先来简单了解几个东西:WebView、WebViewClient、WebChromeClient。
在Android的Webview设计中,不是所有功能都由WebView类来实现的,部分功能由其他的类(WebViewClient、WebChromeClient)来实现,这样一来WebView主要专心干好解析、渲染工作就行了。
WebViewClient帮助WebView处理各种通知、请求事件的,具体来说包括:onLoadResource、onPageStart、onPageFinish、onReceiveError、onReceivedHttpAuthRequest;
WebChromeClient辅助WebView处理Javascript的对话框,网站图标,网站title,加载进度。具体来说包括onCloseWindow(关闭WebView)、onCreateWindow()、onJsAlert (WebView上alert是弹不出来东西的,需要定制你的WebChromeClient处理弹出)、onJsPrompt、onJsConfirm、onProgressChanged、onReceivedIcon、onReceivedTitle;
在Android中通过 WebViewClient 的 shouldoverrideurlloading 可以捕获到url scheme的触发:
// WebViewClient主要帮助WebView处理各种通知、请求事件
private WebViewClient webViewClient = new WebViewClient() {
@Override // 重写shouldOverrideUrlLoading()方法,使得打开网页时不调用系统浏览器, 而是在本WebView中显示 public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 原生通过解析url进行相应处理
}
};
另外,Android中也可以不通过iframe.src来触发scheme,android中可以通过window.prompt(uri, "");来触发scheme,然后Native中通过重写WebViewClient的onJsPrompt来获取uri
第四步:分析url-参数和回调的格式
Native接收到Url后,可以按照这种格式将回调参数id、api名、参数提取出来, 然后按如下步骤进行:
(1) 根据api名,在本地找寻对应的api方法, 并且记录该方法执行完后的回调函数id
(2) 根据提取出来的参数,根据定义好的参数进行转化
(3) 原生本地执行对应的api功能方法
(4) 功能执行完毕后,找到这次api调用对应的回调函数id,然后连同需要传递的参数信息,组装成一个JSON格式的参数
JSON格式为: {
responseId: 回调id,
responseData: 回调数据
}
• responseId(String型)H5页面中对应需要执行的回调函数的id,在H5中生成url scheme时就已经产生
• responseData(JSON型) Native需要传递给H5的回调数据,是一个JSON格式: {
code:(整型,调用是否成功,1成功,0失败),
result:具体需要传递的结果信息,可以为任意类型,
msg:一些其它
}
(5) 通过JSBridge通知H5页面回调
第五步: Native调用JS功能实现
到了这一步,就该Native通过JSBridge调用H5的JS方法或者通知H5进行回调了,具体如下:
JSBridge._handleMessageFromNative(messageJSON);
messageJSON数据格式根据两种不同的类型,有所区别:
Native通知H5页面进行回调,messageJSON数据格式如下:
{
responseId: 回调id,
responseData: 回调数据
}
Native主动调用H5方法,messageJSON数据格式如下:
{
handlerName: 需要调用的h5 api的名称
data: 需要传递的数据,固定为JSON格式
callbackId: 原生生成的回调函数id,h5执行完毕后通过url scheme通知原生api成功执行并传递参数
}
第六步: H5中api方法的注册
//注册一个测试函数
JSBridge.registerHandler('testH5Func', function(data, callback){
alert('测试函数接收到数据:'+JSON.stringify(data));
callback&&callback('测试回传数据...');
});
data: 原生传过来的数据;
callback: 内部封装过一次的, 执行callback后会触发url scheme, 通知原生获取回调信息。
结语
自此,我们已经将JSBridge的整体架构和实现思路都讲明白了,本篇文章是关于JS部分的实现。