图片 12

Hybrid App技术解析 — 原理篇

Posted by

Hybrid App技术解析 — 原理篇

2018/07/25 · JavaScript
· Hybrid

原文出处: 郭东东   

 

Hybrid App技术解析 — 实战篇

2018/08/13 · 基础技术 ·
Hybrid

原文出处: 郭东东   

引言

随着 Web 技术和移动设备的快速发展,Hybrid
技术已经成为一种最主流最常见的方案。一套好的 Hybrid架构方案 能让 App
既能拥有极致的体验和性能,同时也能拥有 Web技术
灵活的开发模式、跨平台能力以及热更新机制,想想是不是都鸡冻不已。。😄。本系列文章是公司在这方面实践的一个总结,包含了原理解析、方案选型与实现、实践优化等方面。

大家可以到github上和我进行讨论哈!

引言

上一篇原理篇,我们已经详细地阐述了 Hybrid App
的基础原理,了解了 Native端 和 H5端 是如何通信的,还有 bridge
的设计和接入
。而本篇文章将开始把这些原因进一步实践,用代码真正地去实现一套完整且稳定的
Hybrid 方案。如果对原理还有疑问的小伙伴,请移步Hybrid App技术解析 —
原理篇,只有在理解了理论的基础上,进一步与实践相结合,才能真正地去深入一项技术。

如果大家有什么更好的方案或建议,可以到 github.com/xd-tayde 上与我进行讨论哈!

现有混合方案

Hybrid App,俗称混合应用,即混合了 Native技术 与 Web技术
进行开发的移动应用。现在比较流行的混合方案主要有三种,主要是在UI渲染机制上的不同:

  1. 基于 WebView UI 的基础方案,市面上大部分主流 App
    都有采用,例如微信JS-SDK,通过 JSBridge 完成 H5 与 Native
    的双向通讯,从而赋予H5一定程度的原生能力。
  2. 基于 Native UI 的方案,例如 React-Native、Weex。在赋予 H5
    原生API能力的基础上,进一步通过 JSBridge
    将js解析成的虚拟节点树(Virtual DOM)传递到 Native 并使用原生渲染。
  3. 另外还有近期比较流行的小程序方案,也是通过更加定制化的
    JSBridge,并使用双 WebView
    双线程的模式隔离了JS逻辑与UI渲染,形成了特殊的开发模式,加强了 H5 与
    Native 混合程度,提高了页面性能及开发体验。

以上的三种方案,其实同样都是基于 JSBridge
完成的通讯层,第二三种方案,其实可以看做是在方案一的基础上,继续通过不同的新技术进一步提高了应用的混合程度。因此,JSBridge
也是整个混合应用最关键的部分,例如我们在设置微信分享时用到的
JS-SDK,wx对象 便是我们最常见的 JSBridge:

图片 1

摩天大楼

说了那么一大堆理论知识,可能有小伙伴会说:“
你是不是吹流弊啊。”。。那就先来简单介绍下我们已经使用这套方案落地的项目之一。

图片 2

这是一个完全内置在 App 里的 Hybrid 模块,由 Native 与 H5
深度协作完成,总共有 4个页面,其中首页和制作页由 H5
制作,而相机页和保存页是复用Native页面。

项目上线一年累积使用次数已经超过10亿次。这套方案经受住了考验,并在过程中仍然在不断的优化和拓展。

使用这套实现方案是基于以下几点考虑:

  • 整个模块的风格多变,整体UI是与妆容所搭配的,而整个模块一直都在持续不断的迭代之中;
  • 项目逻辑流程的可变性大,需要H5强大的热更新能力,及时应对数据的变化,快速的试错和纠正
  • 拍摄页与保存页是客户端已经有的模块,可以略微定制后直接复用
  • 需要由客户端协助接入多套SDK,例如使用算法SDK进行复杂的图像处理。

简单看完项目,我们接下来开始 bridge.js
的构建。由于本系列文章主要面向前端童鞋,因此我们主要展开 H5
的部分,即会注入到每个页面头部的 bridge.js 的实现,客户端中的 SDK
部分就不详细解构了,只会提到一些细节。

方案选型

任何技术方案的选型,其实都应该基于使用场景和现有条件。基于公司现有情况的几点考虑,在方案一上进一步优化,更加适合我们的需求。

  • 需求 Web技术 快速迭代、灵活开发的特点和线上热更新的机制。
  • 产品的核心能力是强大的拍照与底层图片处理能力,因此单纯的
    H5技术能做的事非常有限,不能满足需求,通过 Hybrid
    技术来强化H5,便是一种必需。
  • 公司业务上,并没有非常复杂的UI渲染需求,而且 App 中的一系列原生
    UI组件 已经非常成熟,因此我们并不强需类似 RN 这样的方案。

因此,如何既能利用 H5 强大的开发和迭代能力,又能赋予 H5
强大的底层能力和用户体验,同时能复用现有的成熟
Native组件
,便成为了我们最大的需求点 — 一套完整又强大的
Hybrid技术架构方案。😠

搭建地基 — bridge.js 架构

基于上篇文章阐述的结构,我们进一步去完善细节部分,先整理成下面这样的流程结构图,大家先看下图,有个大致的概念:

nativeCall与 postMessage这两个主体 API 桥接了 Native端 和 H5端

图片 3

接下来我们会细看里面各个部分的代码实现。

Hybrid技术原理

Hybrid App的本质,其实是在原生的 App 中,使用 WebView 作为容器直接承载
Web页面。因此,最核心的点就是 Native端 与 H5端
之间的双向通讯层,其实这里也可以理解为我们需要一套跨语言通讯方案,来完成
Native(Java/Objective-c/…) 与 JavaScript 的通讯。这个方案就是我们所说的
JSBridge,而实现的关键,便是作为容器的 WebView,一切的原理都是基于
WebView 的机制。

图片 4

(一) 业务方使用姿势

首先,我们先看下在这套方案中,业务方是如何使用的,下面以获取网络状态为例:

图片 5

(一) JavaScript 通知 Native

基于 WebView 的机制和开放的 API, 实现这个功能有三种常见的方案:

  • API注入,原理其实就是 Native 获取
    JavaScript环境上下文,并直接在上面挂载对象或者方法,使 js
    可以直接调用,Android 与 IOS 分别拥有对应的挂载方式。
  • WebView 中的 prompt/console/alert 拦截,通常使用
    prompt,因为这个方法在前端中使用频率低,比较不会出现冲突;
  • WebView URL Scheme 跳转拦截

第二三种机制的原理是类似的,都是通过对 WebView
信息冒泡传递的拦截,从而达到通讯的,接下来我们主要从
原理-定制协议-拦截协议-参数传递-回调机制 5个方面详细阐述下第三种方案
— URL拦截方案。

(二) H5 –> Native

接下来直接来看 nativeCall 的内部实现:

图片 6

里面可以解构成下面4个步骤:

  1. 生成唯一 handler 标识,从 0 开始累加;
  2. 将参数按 handler 值的规则存入参数池(_paramsStore)中;
  3. 以 handler 注册自定义事件,绑定 callback,并将 callback也存入
    _callbackStore
    中,addEvent(),储存的目的主要是为了事件解绑时使用;
  4. 以 iframe 的形式发送协议,并携带唯一标识 handler,send()

图片 7

Native:

  • 客户端接收到请求后,会使用 handler
    调用 getParam 从参数池中获取对应的参数图片 8

  • 执行协议对应的功能

这样即走通了 H5 –> Native
的这个流程,在客户端完成了对应的功能后,既开始回传执行结果。

1. 实现原理

在 WebView 中发出的网络请求,客户端都能进行监听和捕获

(二) Native –> H5

Native:

  • Native
    完成功能后,直接调用 Bridge.postMessage(handler, data),将 执行结果 和
    之前 nativeCall 传过来的 标识 回传给
    H5;图片 9

H5:

  • H5
    在接收到唯一标识后初始化对应的自定义事件,挂载数据后触发,这里涉及的就是 fireEvent 这个函数: 图片 10

这样,我们就已经完成了双端之间的双向交互机制了,梳理出了整个 bridge.js
的核心代码了,包含了:

  • 最重要的开放API: nativeCall 与 postMessage ;
  • 客户端获取参数函数: getParam ;
  • 事件回调系统中的 addEvent 和 fireEvent ;
  • 用于发送协议的 send

2. 协议的定制

我们需要制定一套URL
Scheme
规则,通常我们的请求会带有对应的协议开头,例如常见的
或者
file://1.jpg,代表着不同的含义。我们这里可以将协议类型的请求定制为:

xxcommand://xxxx?param1=1&param2=2

这里有几个需要注意点的是:

(1) xxcommand://
只是一种规则,可以根据业务进行制定,使其具有含义,例如我们定义
xxcommand:// 为公司所有App系通用,为通用工具协议:

xxcommand://getProxy?h=1

而定义 xxapp:// 为每个App单独的业务协议。

xxapp://openCamera?h=2

不同的协议头代表着不同的含义,这样便能清楚知道每个协议的适用范围。

(2) 这里不要使用 location.href
发送,因为其自身机制有个问题是同时并发多次请求会被合并成为一次,导致协议被忽略,而并发协议其实是非常常见的功能。我们会使用创建
iframe 发送请求
的方式。

(3)
通常考虑到安全性,需要在客户端中设置域名白名单或者限制,避免公司内部业务协议被第三方直接调用。

安卓兼容性:

如果看过上一篇原理篇的童鞋,这时可能会有个疑问:在
Android 4.4以下时,使用的 loadUrl 进行 js
函数的调用,而此时是无法获取函数的返回值的,也就是说4.4-
时,安卓并无法通过 getParam 这个函数来获取到协议的参数,这里需要做兼容性的处理,而我们这里可以使用一个曲线救国的骚操作,使用到的原理就是上一篇文章中有提到的另一种
H5 -> Native 的方案:

WebView 中的 prompt 拦截

方案如下:

  • 当安卓接受到协议,并拿到 handler 值;
  • 使用无兼容性问题的 loadUrl 执行
    js:Bridge.getParam(handler) ,直接将返回值直接通过 js
    中的 prompt 发出:图片 11

  • 通过重写 onJsPrompt 这个方法,拦截上一步发出的 prompt
    的内容,并解析出相应的参数;图片 12

通过这样的方式,安卓全平台都可以完成参数的获取,并且方式统一,不需要分平台兼容,这就非常的skrskr啦。~~

现在看下来,是不是觉得炒鸡简单?。分分钟能写100个。。没错!其实核心的原理就是这么的简单,但这只是一个最基础的地基而已,而基于地基之上,我们就可以开始一层一层建造我们的大楼了!

3.协议的拦截

客户端可以通过 API 对 WebView 发出的请求进行拦截:

  • IOS上: shouldStartLoadWithRequest
  • Android: shouldOverrideUrlLoading

当解析到请求 URL
头为制定的协议时,便不发起对应的资源请求,而是解析参数,并进行相关功能或者方法的调用,完成协议功能的映射。

建造大楼 — 协议的定制

在完成最基础的架构后,我们就可以开始来进一步完成一些上层建筑了,制定一系列真正开放给业务方使用的协议
API,完善整套方案。

首先我们可以将这些协议分成 功能协议 和 业务协议

4.协议回调

由于协议的本质其实是发送请求,这属于一个异步的过程,因此我们便需要处理对应的回调机制。这里我们采用的方式是JS的事件系统,这里我们会用到
window.addEventListenerwindow.dispatchEvent这两个基础API;

    1. 发送协议时,通过协议的唯一标识注册自定义事件,并将回调绑定到对应的事件上。
    1. 客户端完成对应的功能后,调用 Bridge 的dispatch API,直接携带
      data 触发该协议的自定义事件。

图片 13

通过事件的机制,会让开发更符合我们前端的习惯,例如当你需要监听客户端的通知时,同样只需要在通过
addEventListener 进行监听即可。

Tips:
这里有一点需要注意的是,应该避免事件的多次重复绑定,因此当唯一标识重置时,需要removeEventListener对应的事件。

功能协议

这类协议是指用于完善整套方案的基础功能的一些通用协议,以command://作为通用头,封装在
SDK 之中,可以在全线 App、全线 WebView 中使用:

相关文章

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注