Hybrid框架之交互通信篇
Posted aspook
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hybrid框架之交互通信篇相关的知识,希望对你有一定的参考价值。
前言
虽然有些应用在使用React Native或Weex开发,但综合来看,业内还是以混合开发模式为主,从我们自家的App来看,H5业务所占比重越来越高,目前大概占到35%左右,因此一套好的Hybrid开发框架必不可少。
混合开发的一般原则为交互较少、上线周期短、展示性质的页面使用H5开发,如节日活动页、商品秒杀页面等。Hybrid框架要考虑的事情非常多,如页面加载速度、预加载及缓存机制、与原生交互通信、不同机型兼容等问题。
本文的关注点仅在于Hybrid框架交互通信的设计与实现,框架的其他方面日后分享,感兴趣的也可以留言探讨。
交互通信类型
从大的范围来看,可以分为三类:
- H5向Native通信,以下为常见场景:
- H5打开一个原生界面,H5只作为一个入口,后续逻辑均在原生界面处理
- H5唤起一个原生界面执行一些逻辑,需要将结果返回给H5
- Native向H5通信
- 通常表现原生界面将某些结果传给H5页面或触发某些H5的行为
- H5页面之间通信
- 如果H5项目有多个独立页面,页面之间可能需要通信
设计与实现
设计原则
- 为了安全,尽量避免javascript注入
- 原生埋点,H5开发不依赖于原生版本
交互通信实现
H5向Native通信
如果不用JavaScript注入的方式,最常见的做法是利用自定义的交互协议,通过public boolean shouldOverrideUrlLoading(WebView view, String url)
拦截并进行原生处理。因此为了适应上述提到的一些场景,协议的格式就尤为重要。
H5打开一个原生界面,H5只作为一个入口,后续逻辑均在原生界面处理,无需返回值给H5
对于这种需求,通常使用路由协议来实现,如下:
sample://route/module ID/sub ID
Native端接收到H5触发的此种协议,就会根据模块ID及子ID跳转到某个原生页面。上述协议示例只是一种最通用的情况,还可按一定规则扩展协议以应对其他逻辑。关于模块路由的设计可参考之前的一篇文章:一种Android客户端架构设计分享。
H5唤起一个原生界面执行一些逻辑,执行完成后需要将结果返回给H5
常见的示例如H5中调用原生的登录模块,用户登录成功后重新返回到H5页面,同时需要将登录状态、用户Urid等返回;另一种场景如H5调用原生地址管理模块,选择一个地址之后,将地址数据返回给H5。这两个例子虽然也可以完全用H5实现,从而避免交互,但采用H5与原生交互的方式优点如下:
- 原生已封装实现此功能,可供H5直接调用,不必重复造轮子,节省H5开发成本
- 提升用户体验,原生的体验毕竟要比H5好一些
对于这种需求,其实就是一种需要带回调的通信,即H5跳转原生界面执行完某操作后,需要将结果返回给H5。
以H5调用原生登录并返回H5刷新为例,可设计协议如下:
sample://login/callback
Native端接收到此类协议则会调用原生登录界面,同时保存下callback。当原生界面登录完成,可使用消息分发机制(一种Android客户端架构设计分享 中也有说明)通知WebView调用此JavaScript回调,所需的传值如登录状态、用户ID可封装为JSON格式,通过callback参数方式返回给H5。H5中处理此回调提取返回值,如登录成功,接下来可执行用户登录后相关的刷新操作。
Native向H5通信
普通的单向交互
此种通信方式比较简单,就是通过WebView去加载JavaScript的方法或回调函数,传值可通过参数方式,跟上文的callback相同。
Native容器(Activity或ViewController(ios))向H5暴露生命周期状态
在混合开发中,往往为了模拟原生任务栈的效果,每打开一个新的H5总会开一个新的包含WebView的Activity(或者WebView也可直接位于Fragment中,Fragment位于Activity中)。这样用户体验就接近原生效果,返回时会返回上一级包含H5页面的Activity。
一种场景如A页面跳转到B页面,从B页面返回A页面,此时纯H5对页面状态不易监测或者监测不准确,有时H5端会有此种需求,因此可利用原生页面的生命周期获取页面状态。在原生代码中根据原生页面的生命周期触发一个JS回调,有需要监听页面生命周期的H5自行实现该回调即可,实现后JS即可获取原生页面状态从而进行对应处理。
由于H5没必要知道原生界面的所有生命周期状态,可能仅仅对如下两种状态感兴趣:
- PAUSED(暂停状态,如A页面打开B页面,A页面被覆盖了,A处于PAUSED状态)
- RESUME (页面恢复,如A页面打开B后,又从B返回A。第一次打开A时其实没必要触发,仅处理从其他页面返回的情况,以减少交互开销,提升性能)
因此协议可定义为:
sample://lifecircle/status
status状态值为PAUSED或RESUME 。
注意事项:
- 由于android和iOS的生命周期可能不完全一致,而混合开发框架基本是两端通用的,因此各平台需要从各自的生命周期中抽象出PAUSED和RESUME状态 。
- 需要监听页面生命周期的H5必须在JS中自行实现lifecircle回调处理。
H5页面之间的通信
对于SPA(Single Page Application),此为单页面应用,多个链接的跳转都在同一个WebView中,上述需求是不必要的,此种实现体验不太好。
对于MPA(Multiple Page Application),此为多页面应用,为了追求原生体验,往往每一个H5总会新开一个Activity页面,前文也有提到,MPA是我起的名字,理解意思即可。在这种情况下,不同的H5页面实际上是处于不同的Activity或Fragment中,也是在不同的WebView中,因此H5页面间的通信成为必要。
假设一种场景如A页面打开B页面,B页面打开C页面,C页面需要向A页面通信,之间隔着B页面。理论上两个通信的页面间可以有0~N个中间页,都能够支持。
由于是不同的Activity和不同的WebView,因此H5页面之间无法直接通信。有了上文讲到的H5和Native之间互相通信的基础,此处可以换一个思路,将H5与H5的通信转化为两个Native页面的通信。
下面是一个原理图:
原理说明:
前提条件为模拟原生页面回退栈的效果,每个Web页都用单独的WebView打开。
上图例子中有3个Web页面,逻辑为A页面打开B页面,B页面打开C页面,以C页面的H5向A页面的H5通信为例。
H5页面间通信原理本质是利用原生页面间的通信方式。H5页面的直接容器为WebView,再往外一层的容器为Activity(Android)或ViewController(iOS),为了兼容嵌入式的WebView,H5最好向WebView注册事件而不要向Activity注册。在实现上应抽象封装一个公共的WebView(相信大家日常开发中都是这样做的),统一接收H5事件注册及事件触发通知。
首先A页面的H5需要向容器注册事件,JS需要调用的注册协议为:
sample://register/custom_event_name
**注意:**custom_event_name为开发者自定义的事件名称,不同页面可注册相同的事件名,那么多个页面都会响应事件;一个页面中若注册多个相同的事件名,则会覆盖,仅响应一次,也不推荐如此使用。
注册完成后,每个容器实例会维护一个注册过的事件名列表。
当C页面要向A页面通信时,C页面中JS需要触发的协议为:
sample://notify/custom_event_name/callback/params
**注意:**custom_event_name要跟注册事件名一致,Native端用来匹配决定哪个页面响应事件;callback则为A页面收到消息后要执行的回调;params为H5间通信需要传递的参数,即为A页面执行callback回调时的参数,其格式根据需求可自定义,复杂的数据可使用JSON串。事件名称、回调名称、回调参数可自由定义,均跟Native端无关。
协议
sample://notify/custom_event_name/callback/params
中的callback需要在A页面的H5中实现,否则无法响应C中H5对A中H5的通信。上述过程中,原生容器C向A通信也是利用一种Android客户端架构设计分享 中介绍的消息分发机制。当然也可以用EventBus之类的事件总线替代,在iOS中可使用广播实现。
总结
Hybrid框架中的交互通信主要是以上几种,这些基本能够满足日常开发需求。交互原理也比较简单,如有其它特殊交互也可扩展,相信大家能够根据上述设计原理自己实现,有时间的话后面会提供一个框架实现的demo。
以上是关于Hybrid框架之交互通信篇的主要内容,如果未能解决你的问题,请参考以下文章
C/S通信交互之Http篇Cocos2dx(Client)使用Curl与Jetty(Server)实现手机网游Http通信框架(内含解决curl.h头文件找不到问题)