Hybrid框架之交互通信篇

前言

虽然有些应用在使用React Native或Weex开发,但综合来看,业内还是以混合开发模式为主,从我们自家的App来看,H5业务所占比重越来越高,目前大概占到35%左右,因此一套好的Hybrid开发框架必不可少。

混合开发的一般原则为交互较少、上线周期短、展示性质的页面使用H5开发,如节日活动页、商品秒杀页面等。Hybrid框架要考虑的事情非常多,如页面加载速度、预加载及缓存机制、与原生交互通信、不同机型兼容等问题。

本文的关注点仅在于Hybrid框架交互通信的设计与实现,框架的其他方面日后分享,感兴趣的也可以留言探讨。

交互通信类型

从大的范围来看,可以分为三类:

  • H5向Native通信,以下为常见场景:
    1. H5打开一个原生界面,H5只作为一个入口,后续逻辑均在原生界面处理
    2. H5唤起一个原生界面执行一些逻辑,需要将结果返回给H5
  • Native向H5通信
    1. 通常表现原生界面将某些结果传给H5页面或触发某些H5的行为
  • H5页面之间通信
    1. 如果H5项目有多个独立页面,页面之间可能需要通信

设计与实现

设计原则

  1. 为了安全,尽量避免JavaScript注入
  2. 原生埋点,H5开发不依赖于原生版本

交互通信实现

H5向Native通信

如果不用JavaScript注入的方式,最常见的做法是利用自定义的交互协议,通过public boolean shouldOverrideUrlLoading(WebView view, String url)拦截并进行原生处理。因此为了适应上述提到的一些场景,协议的格式就尤为重要。

  1. H5打开一个原生界面,H5只作为一个入口,后续逻辑均在原生界面处理,无需返回值给H5

    对于这种需求,通常使用路由协议来实现,如下:

    sample://route/module ID/sub ID

    Native端接收到H5触发的此种协议,就会根据模块ID及子ID跳转到某个原生页面。上述协议示例只是一种最通用的情况,还可按一定规则扩展协议以应对其他逻辑。关于模块路由的设计可参考之前的一篇文章:一种Android客户端架构设计分享

  2. H5唤起一个原生界面执行一些逻辑,执行完成后需要将结果返回给H5

    常见的示例如H5中调用原生的登录模块,用户登录成功后重新返回到H5页面,同时需要将登录状态、用户Urid等返回;另一种场景如H5调用原生地址管理模块,选择一个地址之后,将地址数据返回给H5。这两个例子虽然也可以完全用H5实现,从而避免交互,但采用H5与原生交互的方式优点如下:

    1. 原生已封装实现此功能,可供H5直接调用,不必重复造轮子,节省H5开发成本
    2. 提升用户体验,原生的体验毕竟要比H5好一些

    对于这种需求,其实就是一种需要带回调的通信,即H5跳转原生界面执行完某操作后,需要将结果返回给H5。

    以H5调用原生登录并返回H5刷新为例,可设计协议如下:

    sample://login/callback

    Native端接收到此类协议则会调用原生登录界面,同时保存下callback。当原生界面登录完成,可使用消息分发机制(一种Android客户端架构设计分享 中也有说明)通知WebView调用此JavaScript回调,所需的传值如登录状态、用户ID可封装为JSON格式,通过callback参数方式返回给H5。H5中处理此回调提取返回值,如登录成功,接下来可执行用户登录后相关的刷新操作。

Native向H5通信
  1. 普通的单向交互

    此种通信方式比较简单,就是通过WebView去加载JavaScript的方法或回调函数,传值可通过参数方式,跟上文的callback相同。

  2. Native容器(Activity或ViewController(iOS))向H5暴露生命周期状态

    在混合开发中,往往为了模拟原生任务栈的效果,每打开一个新的H5总会开一个新的包含WebView的Activity(或者WebView也可直接位于Fragment中,Fragment位于Activity中)。这样用户体验就接近原生效果,返回时会返回上一级包含H5页面的Activity。

    一种场景如A页面跳转到B页面,从B页面返回A页面,此时纯H5对页面状态不易监测或者监测不准确,有时H5端会有此种需求,因此可利用原生页面的生命周期获取页面状态。在原生代码中根据原生页面的生命周期触发一个JS回调,有需要监听页面生命周期的H5自行实现该回调即可,实现后JS即可获取原生页面状态从而进行对应处理。

    由于H5没必要知道原生界面的所有生命周期状态,可能仅仅对如下两种状态感兴趣:

    1. PAUSED(暂停状态,如A页面打开B页面,A页面被覆盖了,A处于PAUSED状态)
    2. RESUME (页面恢复,如A页面打开B后,又从B返回A。第一次打开A时其实没必要触发,仅处理从其他页面返回的情况,以减少交互开销,提升性能)

    因此协议可定义为:

    sample://lifecircle/status

    status状态值为PAUSED或RESUME 。

    注意事项:

    1. 由于Android和iOS的生命周期可能不完全一致,而混合开发框架基本是两端通用的,因此各平台需要从各自的生命周期中抽象出PAUSED和RESUME状态 。
    2. 需要监听页面生命周期的H5必须在JS中自行实现lifecircle回调处理。
H5页面之间的通信
  1. 对于SPA(Single Page Application),此为单页面应用,多个链接的跳转都在同一个WebView中,上述需求是不必要的,此种实现体验不太好。

  2. 对于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页面的通信。

    下面是一个原理图:

    这里写图片描述

    原理说明:

    1. 前提条件为模拟原生页面回退栈的效果,每个Web页都用单独的WebView打开。

    2. 上图例子中有3个Web页面,逻辑为A页面打开B页面,B页面打开C页面,以C页面的H5向A页面的H5通信为例。

    3. H5页面间通信原理本质是利用原生页面间的通信方式。H5页面的直接容器为WebView,再往外一层的容器为Activity(Android)或ViewController(iOS),为了兼容嵌入式的WebView,H5最好向WebView注册事件而不要向Activity注册。在实现上应抽象封装一个公共的WebView(相信大家日常开发中都是这样做的),统一接收H5事件注册及事件触发通知。

    4. 首先A页面的H5需要向容器注册事件,JS需要调用的注册协议为:

      sample://register/custom_event_name

      **注意:**custom_event_name为开发者自定义的事件名称,不同页面可注册相同的事件名,那么多个页面都会响应事件;一个页面中若注册多个相同的事件名,则会覆盖,仅响应一次,也不推荐如此使用。

      注册完成后,每个容器实例会维护一个注册过的事件名列表。

    5. 当C页面要向A页面通信时,C页面中JS需要触发的协议为:

      sample://notify/custom_event_name/callback/params

      **注意:**custom_event_name要跟注册事件名一致,Native端用来匹配决定哪个页面响应事件;callback则为A页面收到消息后要执行的回调;params为H5间通信需要传递的参数,即为A页面执行callback回调时的参数,其格式根据需求可自定义,复杂的数据可使用JSON串。事件名称、回调名称、回调参数可自由定义,均跟Native端无关。

    6. 协议sample://notify/custom_event_name/callback/params中的callback需要在A页面的H5中实现,否则无法响应C中H5对A中H5的通信。

    7. 上述过程中,原生容器C向A通信也是利用一种Android客户端架构设计分享 中介绍的消息分发机制。当然也可以用EventBus之类的事件总线替代,在iOS中可使用广播实现。

总结

Hybrid框架中的交互通信主要是以上几种,这些基本能够满足日常开发需求。交互原理也比较简单,如有其它特殊交互也可扩展,相信大家能够根据上述设计原理自己实现,有时间的话后面会提供一个框架实现的demo。

相关文章
相关标签/搜索