为何要使用Hybrid技术?

  • Hybrid开发效率高、跨平台、低层本 Hybrid从业务开发上讲,没有版本问题,有BUG能及时修复
  • Hybrid是有缺点的,Hybrid体验就肯定比不上Native,所以使用有其场景,但是对于需要快速试错、快速占领市场的团队来说,Hybrid一定是不二的选择,团队生存下来后还是需要做体验更好的原生APP。

Hybrid开发方具体方案

Native与前端分工

在做Hybrid架构设计之前需要分清Native与前端的界限,首先Native提供的是一宿主环境,要合理的利用Native提供的能力,要实现通用的Hybrid平台架构,站在前端视角,我认为需要考虑以下核心设计问题。

交互设计

Hybrid架构设计第一个要考虑的问题是如何设计与前端的交互,如果这块设计的不好会对后续开发、前端框架维护造成深远的影响,并且这种影响往往是不可逆的,所以这里需要前端与Native好好配合,提供通用的接口,比如:

  • NativeUI组件,header组件、消息类组件
  • 通讯录、系统、设备信息读取接口
  • H5与Native的互相跳转,比如H5如何跳到一个Native页面,H5如何新开Webview做动画跳到另一个H5页面

资源访问机制

Native首先需要考虑如何访问H5资源,做到既能以file的方式访问Native内部资源,又能使用url的方式访问线上资源;需要提供前端资源增量替换机制,以摆脱APP迭代发版问题,避免用户升级APP。这里就会涉及到静态资源在APP中的存放策略,更新策略的设计,复杂的话还会涉及到服务器端的支持。

账号信息设计

账号系统是重要并且无法避免的,Native需要设计良好安全的身份验证机制,保证这块对业务开发者足够透明,打通账户信息。

Hybrid开发调试

功能设计完并不是结束,Native与前端需要商量出一套可开发调试的模型,不然很多业务开发的工作将难以继续,这个很多文章已经接受过了,本文不赘述。

至于Native还会关注的一些通讯设计、并发设计、异常处理、日志监控以及安全模块因为不是我涉及的领域便不予关注了(事实上是想关注不得其门),而前端要做的事情就是封装Native提供的各种能力,整体架构是这样的:
Hybrid开发交互设计总结 - 图1

真实业务开发时,Native除了会关注登录模块之外还会封装支付等重要模块,这里视业务而定。

Hybrid交互设计

Hybrid的交互无非是Native调用前端页面的JS方法,或者前端页面通过JS调用Native提供的接口,两者交互的桥梁皆Webview:
Hybrid开发交互设计总结 - 图2

app自身可以自定义urlschema,并且把自定义的url注册在调度中心, 例如

  • ctrip://wireless 打开携程App
  • weixin:// 打开微信

我们JS与Native通信一般就是创建这类URL被Native捕获处理,后续也出现了其它前端调用Native的方式,但可以做底层封装使其透明化,所以重点以及是如何进行前端与Native的交互设计。

JS to Native

Native在每个版本会提供一些API,前端会有一个对应的框架团队对其进行封装,释放业务接口。比如糯米对外的接口是这样的:
Hybrid开发交互设计总结 - 图3
Hybrid开发交互设计总结 - 图4

前端框架定义了一个全局变量BNJS作为Native与前端交互的对象,只要引入了糯米提供的这个JS库,并且在糯米封装的Webview容器中,前端便获得了调用Native的能力,我揣测糯米这种设计是因为这样便于第三方团队的接入使用,手机百度有一款轻应用框架也走的这种路线:

clouda.mbaas.account //释放了clouda全局变量

这样做有一个前提是,Native本身已经十分稳定了,很少新增功能了,否则在直连情况下就会面临一个尴尬,因为web站点永远保持最新的,就会在一些低版本容器中调用了没有提供的Native能力而报错。
Hybrid开发交互设计总结 - 图5

API式交互

手白、糯米底层如何做我们无从得知,但我们发现调用Native API接口的方式和我们使用AJAX调用服务器端提供的接口是及其相似的:Hybrid开发交互设计总结 - 图6

这里类似的微薄开放平台的接口是这样定义的:
Hybrid开发交互设计总结 - 图7

我们要做的就是通过一种方式创建ajax请求即可:
https://api.weibo.com/2/statuses/public_timeline.json

所以我在实际设计Hybrid交互模型时,是以接口为单位进行设计的,比如获取通讯录的总体交互是:
Hybrid开发交互设计总结 - 图8

格式约定
交互的第一步是设计数据格式,这里分为请求数据格式与响应数据格式,参考ajax的请求模型大概是:
Hybrid开发交互设计总结 - 图9

所以我这边与Native约定的请求模型是:
Hybrid开发交互设计总结 - 图10

这个方法执行会形成一个URL,比如:
hybridschema://hybridapi?callback=hybrid_1446276509894&param=%7B%22data1%22%3A1%2C%22data2%22%3A2%7D

这里提一点,APP安装后会在手机上注册一个schema,比如淘宝是taobao://,Native会有一个进程监控Webview发出的所有schema://请求,然后分发到“控制器”hybridapi处理程序,Native控制器处理时会需要param提供的参数(encode过),处理结束后将携带数据获取Webview window对象中的callback(hybrid_1446276509894)调用之
数据返回的格式约定是:
Hybrid开发交互设计总结 - 图11

真实的数据在data对象中,如果errno不为0的话,便需要提示msg,这里举个例子如果错误码1代表该接口需要升级app才能使用的话:
Hybrid开发交互设计总结 - 图12

代码实现
这里给一个简单的代码实现,真实代码在APP中会有所变化:
Hybrid开发交互设计总结 - 图13

因为Native对于H5来是底层,框架&底层一般来说是不会关注业务实现的,所以真实业务中Native调用H5场景较少,这里不予关注了。

常用交互API

良好的交互设计是成功的第一步,在真实业务开发中有一些API一定会用到。

跳转

跳转是Hybrid必用API之一,对前端来说有以下跳转:

  • 页面内跳转,与Hybrid无关
  • H5跳转Native界面
  • H5新开Webview跳转H5页面,一般为做页面动画切换

如果要使用动画,按业务来说有向前与向后两种,forward&back,所以约定如下,首先是H5跳Native某一个页面
Hybrid开发交互设计总结 - 图14

比如携程H5页面要去到酒店Native某一个页面可以这样:
Hybrid开发交互设计总结 - 图15

比如H5新开Webview的方式跳转H5页面便可以这样:
Hybrid开发交互设计总结 - 图16

back与forward一致,我们甚至会有animattype参数决定切换页面时的动画效果,真实使用时可能会封装全局方法略去tagname的细节,这时就和糯米对外释放的接口差不多了。

Header 组件的设计

最初我其实是抵制使用Native提供的UI组件的,尤其是Header,因为平台化后,Native每次改动都很慎重并且响应很慢,但是出于两点核心因素考虑,我基本放弃了抵抗:

  • 其它主流容器都是这么做的,比如微信、手机百度、携程
  • 没有header一旦网络出错出现白屏,APP将陷入假死状态,这是不可接受的,而一般的解决方案都太业务了

PS:Native吊起Native时,如果300ms没有响应需要出loading组件,避免白屏

因为H5站点本来就有Header组件,站在前端框架层来说,需要确保业务的代码是一致的,所有的差异需要在框架层做到透明化,简单来说Header的设计需要遵循:

  • H5 header组件与Native提供的header组件使用调用层接口一致
  • 前端框架层根据环境判断选择应该使用H5的header组件抑或Native的header组件

一般来说header组件需要完成以下功能:

  • header左侧与右侧可配置,显示为文字或者图标(这里要求header实现主流图标,并且也可由业务控制图标),并需要控制其点击回调
  • header的title可设置为单标题或者主标题、子标题类型,并且可配置lefticon与righticon(icon居中)
  • 满足一些特殊配置,比如标签类header

所以,站在前端业务方来说,header的使用方式为(其中tagname是不允许重复的):
Hybrid开发交互设计总结 - 图17

因为Header左边一般来说只有一个按钮,所以其对象可以使用这种形式:
Hybrid开发交互设计总结 - 图18

为完成Native端的实现,这里会新增两个接口,向Native注册事件,以及注销事件:
Hybrid开发交互设计总结 - 图19

Native Header组件的实现:见原文

请求类

虽然get类请求可以用jsonp的方式绕过跨域问题,但是post请求却是真正的拦路虎,为了安全性服务器设置cors会仅仅针对几个域名,Hybrid内嵌静态资源是通过file的方式读取,这种场景使用cors就不好使了,所以每个请求需要经过Native做一层代理发出去。
Hybrid开发交互设计总结 - 图20

这个使用场景与Header组件一致,前端框架层必须做到对业务透明化,业务事实上不必关心这个请求是由浏览器发出还是由Native发出:
Hybrid开发交互设计总结 - 图21

真实的业务场景,会将之封装到数据请求模块,在底层做适配,在H5站点下使用ajax请求,在Native内嵌时使用代理发出,与Native的约定为:
Hybrid开发交互设计总结 - 图22

常用NativeUI组件

最后,Native会提供几个常用的Native级别的UI,比如loading加载层,比如toast消息框:
Hybrid开发交互设计总结 - 图23

Native UI与前端UI不容易打通,所以在真实业务开发过程中,一般只会使用几个关键的Native UI。
**

账号系统的设计

根据上面的设计,我们约定在Hybrid中请求有两种发出方式:

  • 如果是webview访问线上站点的话,直接使用传统ajax发出
  • 如果是file的形式读取Native本地资源的话,请求由Native代理发出

因为静态html资源没有鉴权的问题,真正的权限验证需要请求服务器api响应通过错误码才能获得,这是动态语言与静态语言做入口页面的一个很大的区别。

以网页的方式访问,账号登录与否由是否带有秘钥cookie决定(这时并不能保证秘钥的有效性),因为Native不关注业务实现,而每次载入都有可能是登录成功跳回来的结果,所以每次载入后都需要关注秘钥cookie变化,以做到登录态数据一致性。

以file的方式访问内嵌资源的话,因为API请求控制方为Native,所以鉴权的工作完全由Native完成,接口访问如果没有登录便弹出Native级别登录框引导登录即可,每次访问webview将账号信息种入到webview中,这里有个矛盾点是Native种入webview的时机,因为有可能是网页注销的情况,所以这里的逻辑是:

  • webview载入结束
  • Native检测webview是否包含账号cookie信息
  • 如果不包含则种入cookie,如果包含则检测与Native账号信息是否相同,不同则替换自身
  • 如果检测到跳到了注销账户的页面,则需要清理自身账号信息

如果登录不统一会就会出现上述复杂的逻辑,所以真实情况下我们会对登录接口收口。

简单化账号接口

平台层面觉得上述操作过于复杂,便强制要求在Hybrid容器中只能使用Native接口进行登录和登出,前端框架在底层做适配,保证上层业务的透明,这样情况会简单很多:

  • 使用Native代理做请求接口,如果没有登录直接Native层唤起登录框
  • 直连方式使用ajax请求接口,如果没有登录则在底层唤起登录框(需要前端框架支持)

简单的登录登出接口实现:
Hybrid开发交互设计总结 - 图24

账号信息获取

在实际的业务开发中,判断用户是否登录、获取用户基本信息的需求比比皆是,所以这里必须保证Hybrid开发模式与H5开发模式保持统一,否则需要在业务代码中做很多无谓的判断,我们在前端框架会封装一个User模块,主要接口包括:
Hybrid开发交互设计总结 - 图25

这个代码的底层实现分为前端实现,Native实现,首先是前端的做法是:
当前端页面载入后,会做一次异步请求,请求用户相关数据,如果是登录状态便能获取数据存于localstorage中,这里一定不能存取敏感信息

前端使用localstorage的话需要考虑极端情况下使用内存变量的方式替换localstorage的实现,否则会出现不可使用的情况,而后续的访问皆是使用localstorage中的数据做判断依据,以下情况需要清理localstorage的账号数据:

  • 系统登出
  • 访问接口提示需要登录
  • 调用登录接口

这种模式多用于单页应用,非单页应用一般会在每次刷新页面先清空账号信息再异步拉取,但是如果当前页面马上就需要判断用户登录数据的话,便不可靠了;处于Hybrid容器中时,因为Native本身就保存了用户信息,封装的接口直接由Native获取即可,这块比较靠谱。

Hybrid的资源

目录结构
Hybrid技术既然是将静态资源存于Native,那么就需要目录设计,经过之前的经验,目录结构一般以2层目录划分:
Hybrid开发交互设计总结 - 图26

如果我们有两个频道酒店与机票,那么目录结构是这样的:
Hybrid开发交互设计总结 - 图27

最初设计的forward跳转中的topage参数规则是:频道/具体页面=>channel/page,其余资源会由index.html这个入口文件带出。

增量机制

真实的增量机制需要服务器端的配合,我这里只能简单描述,Native端会有维护一个版本映射表:
Hybrid开发交互设计总结 - 图28

这个映射表是每次大版本APP发布时由服务器端生成的,如果酒店频道需要在线做增量发布的话,会打包一个与线上一致的文件目录,走发布平台发布,会在数据库中形成一条记录:
Hybrid开发交互设计总结 - 图29

当APP启动时,APP会读取版本信息,这里发现hotel的本地版本号比线上的小,便会下载md5对应的zip文件,然后解压之并且替换整个hotel文件,本次增量结束,因为所有的版本文件不会重复,APP回滚时可用回到任意想去的版本,也可以对任意版本做BUG修复。

结语

github上代码会持续更新,现在界面反正不太好看,大家多多包涵吧,这里是一些效果图:
Hybrid开发交互设计总结 - 图30

Hybrid方案是快速迭代项目,快速占领市场的神器,希望此文能对准备接触Hybrid技术的朋友提供一些帮助,并且再次感谢明月同学的配合。