上一篇原理篇,咱们已经详细地阐述了 Hybrid App 的基础原理,了解了 Native端 和 H5端 是如何通讯的,还有 bridge 的设计和接入。而本篇文章将开始把这些缘由进一步实践,用代码真正地去实现一套完整且稳定的 Hybrid 方案。若是对原理还有疑问的小伙伴,请移步Hybrid App技术解析 -- 原理篇,只有在理解了理论的基础上,进一步与实践相结合,才能真正地去深刻一项技术。前端
若是你们有什么更好的方案或建议,能够到 github.com/xd-tayde 上与我进行讨论哈!git
说了那么一大堆理论知识,可能有小伙伴会说:“ 你是否是吹流弊啊。”。😅。那就先来简单介绍下咱们已经使用这套方案落地的项目之一。github
这是一个彻底内置在 App 里的 Hybrid 模块,由 Native 与 H5 深度协做完成,总共有 4个页面,其中首页和制做页由 H5 制做,而相机页和保存页是复用Native页面。ajax
项目上线一年累积使用次数已经超过10亿次。这套方案经受住了考验,并在过程当中仍然在不断的优化和拓展。算法
使用这套实现方案是基于如下几点考虑:后端
简单看完项目,咱们接下来开始 bridge.js 的构建。因为本系列文章主要面向前端童鞋,所以咱们主要展开 H5 的部分,即会注入到每一个页面头部的 bridge.js 的实现,客户端中的 SDK 部分就不详细解构了,只会提到一些细节。跨域
基于上篇文章阐述的结构,咱们进一步去完善细节部分,先整理成下面这样的流程结构图,你们先看下图,有个大体的概念:缓存
nativeCall
与 postMessage
这两个主体 API 桥接了 Native端 和 H5端服务器
接下来咱们会细看里面各个部分的代码实现。网络
首先,咱们先看下在这套方案中,业务方是如何使用的,下面以获取网络状态为例:
接下来直接来看 nativeCall
的内部实现:
里面能够解构成下面4个步骤:
addEvent()
,储存的目的主要是为了事件解绑时使用;send()
;Native:
getParam
从参数池中获取对应的参数。这样即走通了 H5 --> Native 的这个流程,在客户端完成了对应的功能后,既开始回传执行结果。
Native:
Bridge.postMessage(handler, data)
,将 执行结果 和 以前 nativeCall
传过来的 标识 回传给 H5;H5:
fireEvent
这个函数:这样,咱们就已经完成了双端之间的双向交互机制了,梳理出了整个 bridge.js 的核心代码了,包含了:
nativeCall
与 postMessage
;getParam
;addEvent
和 fireEvent
;send
。若是看过上一篇原理篇的童鞋,这时可能会有个疑问:在 Android 4.4如下时,使用的 loadUrl
进行 js 函数的调用,而此时是没法获取函数的返回值的,也就是说4.4- 时,安卓并没有法经过 getParam
这个函数来获取到协议的参数,这里须要作兼容性的处理,而咱们这里可使用一个曲线救国的骚操做,使用到的原理就是上一篇文章中有提到的另外一种 H5 -> Native 的方案:
WebView 中的 prompt 拦截
方案以下:
loadUrl
执行 js:Bridge.getParam(handler)
,直接将返回值直接经过 js 中的 prompt
发出:onJsPrompt
这个方法,拦截上一步发出的 prompt 的内容,并解析出相应的参数;经过这样的方式,安卓全平台均可以完成参数的获取,而且方式统一,不须要分平台兼容,这就很是的skrskr啦。~~🤘🏻🤘🏻
如今看下来,是否是以为炒鸡简单?。分分钟能写100个。😂。没错!其实核心的原理就是这么的简单,但这只是一个最基础的地基而已,而基于地基之上,咱们就能够开始一层一层建造咱们的大楼了!
在完成最基础的架构后,咱们就能够开始来进一步完成一些上层建筑了,制定一系列真正开放给业务方使用的协议 API,完善整套方案。
首先咱们能够将这些协议分红 功能协议 和 业务协议。
这类协议是指用于完善整套方案的基础功能的一些通用协议,以command://
做为通用头,封装在 SDK 之中,能够在全线 App、全线 WebView 中使用:
上篇文章有提到因为 bridge.js 注入的异步性,咱们须要由客户端在注入完成后通知 H5。
这里咱们能够约定一个通用的初始化事件,这里咱们约定为 _init_
,所以前端就能够进行入口的监听, 相似于咱们经常使用的 DOMContentLoaded
:
你们能够看到,这里用了个标记位用于避免事件被重复触发,这是因为客户端中是经过监听 WebView 的生命周期钩子来触发的,而 iframe 之类的操做会致使这些钩子的屡次触发,所以须要双方各作一层防护性措施。
接下来,咱们能够经过该事件,直接初始化传给H5一些环境参数和系统信息等,下面是咱们使用到的:
一样的,咱们能够约定更多的页面生命周期事件,例如由于App很常常性的隐藏到后台,所以在被激活时,咱们能够设置个生命周期: _resume_
,能够用于告知 H5 页面被激活。
Tips:
这里就能体现出咱们经过事件机制来做为回调系统的优点了,咱们能够以最习惯的方式进行事件的监听,而客户端能够直接使用 bridge.fireEvent('_init_', data)
触发事件,这样即可以优雅地实现 Native -> H5 的单方向交互。
Hybrid模块 的其中一种方式是将前端代码打包后内置于 App 本地,以便拥有最快的启动性能和离线访问能力。而这种方式最大的麻烦点,就是代码的更新,咱们不可能每次有修改时就手动从新打包给客户端童鞋替换,并且这样也失去了咱们的热更新机制。
所以这里就须要一套新的热更新机制,这套机制须要由客户端/前端/服务端 三端的童鞋提供对应的资源,共同协做完成整套流程。
资源:
流程:
拥有这样的机制后,H5在开发后,就能够直接打包将包上传到对应的服务器上,这样在 App 中打开页面后,便可以实时的热更新。
一般,咱们会将项目分红多个不一样的环境,相互隔离。而因为 Hybrid 模块是置于 App 中的,所以环境须要与 App 进行匹配,这里就能够直接使用上面第一点提到的,经过 _init_
中携带的数据data.env
来匹配:
env: 0 - 正式环境; 1 - 测试环境; 2 - 开发环境;
同理, 多语言也能够直接使用 e.data.language
直接进行匹配;
Tips:
环境机制咱们一般主要用于匹配后端的环境,正式环境和测试环境对应不一样的接口。而这里还有一点特别的,就是须要注意代码包的更新,上述的包更新条件要包含三个方面: 版本号、环境和 App版本,在不一样环境不一样 App 版本下,也应该更新到相应的最新代码包。
因为页面是 H5 开发,而 Native 可能须要控制 H5 页面,例如最经常使用的场景:
当页面中有弹窗或者SPA切换页面时,安卓的返回实体键应该能完成对应的回退,而不是由于 WebView 没有 history 就直接关闭。
相似于这类需求,这里就能够定制一个事件中心(_eventListeners_
),用于监听客户端的实体返回键:
在业务中,不少场景须要作到 Native 与 H5 保持数据的同步,此时就可使用相似上面的原理,制定一套数据传递协议:
Tips:
Hybrid模块一般须要从对应的入口进入,所以这里有一种能够优化的方式:
由 App 在启动时先去获取线上数据,在进入 WebView 后直接经过 _init_
或者触发 getData
直接发送给 H5,这样能减小请求数量,优化用户体验。
H5中最经常使用的就是请求,一般咱们能够直接使用ajax,可是这里有几个问题比较棘手:
而客户端的请求便不会出现这些问题,所以咱们能够由客户端代理咱们发出的请求,能够定制4个协议: getProxy
,postProxy
, getProxyLogined
,postProxyLogined
,其中带有 Logined
的协议表明着在请求时会自动携带已登陆用户的 token 和 uid 等参数,使用在一些须要登陆信息的接口上。这样作的好处是
除了这些重要的功能外,咱们还能够很是自由地定制不少协议,让 H5 拥有更多更强大的功能,下面是咱们所定制的一些功能:
getNetwork
:获取网络状态;openApp
:唤起其它 App;setShareInfo
与callShare
:分享内容到第三方平台;link
:使用新的 WebView 打开页面;closeWebview
:关闭 WebView;setStorage
与 getStorage
:设置与获取缓存数据;loading
:调用客户端通用 Loading;setWebviewTitle
:设置 WebView 标题;saveImage
:保存图片到本地;这里能够定义更多的通用性协议,这里有个原则能够遵照,即这部分协议应该是基础性功能,应该是纯净的,适用于全部的业务方。根据上篇文章提到的理念,这部分是当成通用 SDK 进行维护与升级的,所以不该该耦合业务层的任何逻辑。
而有时咱们会遇到须要定制一些业务上的逻辑,例如上面提到的项目中,咱们要将用户图片经过算法处理成卡通画。这样的需求就是很是的业务化,不适用于其它项目,所以咱们应该定制成业务协议。
这类协议区别于功能协议,它们会杂合必定程度的业务逻辑,而这些逻辑只是针对于特定的项目。其实对于 H5 的使用上,差异并不大,只是使用对应特殊的协议头用于区分,例如:
这类协议一般不包含在 SDK 中,所以须要由客户端的童鞋针对项目的 WebView 进行定制,使用 bridge.js 提供的基础功能实现对应的复杂功能。而在其它的项目入口中,就没法使用这些协议。
看到总结两个字,有没有长舒了一口气。。😅。经过这两篇文章,咱们终于将 Hybrid 方案的前端部分彻底的解构清楚了,是否是有种神清气爽的感受,彻底能够立刻开启大家的 Hybrid 之旅了。鼓掌鼓掌!!👏👏👏!!
但这也远非终点,或者说这永无终点。~大楼建成后,离真正的摩天大楼仍是差着一步 --- 内部装修,其实接下来咱们还须要作不少的优化措施,来解决一些仍然存在的问题,这部分其实咱们也一直还在努力的阶段。
受篇幅所限,有时间会将这部分再写一篇优化篇,主要来与你们探讨下咱们所能想到的一些优化方案,很是期待大佬们也能给咱们提供更多的建议和解决办法。感恩~~😇
更多文章 摸我 阅读。。😻😻😻