大咖直播:如何打造 VUCA 时代的 10 倍速 IT 团队?直播预约>> 了解详情
写点什么

提升海量用户极致体验的 Hybrid 架构设计(原理篇)

2020 年 2 月 21 日

提升海量用户极致体验的Hybrid架构设计(原理篇)

一、引言

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


大家可以到 github (https://github.com/xd-tayde/blog/blob/master/hybrid-1.md)上和作者进行讨论哈!


二、现有混合方案

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:



三、方案选型

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


  • 需求 Web 技术 快速迭代、灵活开发的特点和线上热更新的机制。

  • 产品的核心能力是强大的拍照与底层图片处理能力,因此单纯的 H5 技术能做的事非常有限,不能满足需求,通过 Hybrid 技术来强化 H5 ,便是一种必需。

  • 公司业务上,并没有非常复杂的 UI 渲染需求,而且 App 中的一系列原生 UI 组件 已经非常成熟,因此我们并不强需类似 RN 这样的方案。


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


四、Hybrid 技术原理

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



4.1 JavaScript 通知 Native


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


  • API 注入,原理其实就是 Native 获取 JavaScript 环境上下文,并直接在上面挂载对象或者方法,使 js 可以直接调用,Android 与 IOS 分别拥有对应的挂载方式。

  • WebView 中的 prompt/console/alert 拦截,通常使用 prompt ,因为这个方法在前端中使用频率低,比较不会出现冲突;

  • WebView URL Scheme 跳转拦截;


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


4.1.1 实现原理

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


4.1.2 协议的定制

我们需要制定一套 URL Scheme 规则,通常我们的请求会带有对应的协议开头,例如常见的 https://xxx.com 或者 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) 通常考虑到安全性,需要在客户端中设置域名白名单或者限制,避免公司内部业务协议被第三方直接调用。


4.1.3 协议的拦截

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


  • IOS 上: shouldStartLoadWithRequest

  • Android: shouldOverrideUrlLoading


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


4.1.4 协议回调

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


  • 1.发送协议时,通过协议的唯一标识注册自定义事件,并将回调绑定到对应的事件上。

  • 2.客户端完成对应的功能后,调用 Bridge 的 dispatch API ,直接携带 data 触发该协议的自定义事件。

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

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


4.1.5 参数传递方式

由于 WebView 对 URL 会有长度的限制,因此常规的通过 search 参数 进行传递的方式便具有一个问题,既 当需要传递的参数过长时,可能会导致被截断,例如传递 base64 或者传递大量数据时。


因此我们需要制定新的参数传递规则,我们使用的是函数调用的方式。这里的原理主要是基于:Native 可以直接调用 JS 方法并直接获取函数的返回值。


我们只需要对每条协议标记一个唯一标识,并把参数存入参数池中,到时客户端再通过该唯一标识从参数池中获取对应的参数即可。


4.2 Native 通知 Javascript

由于 Native 可以算作 H5 的宿主,因此拥有更大的权限,上面也提到了 Native 可以通过 WebView API 直接执行 Js 代码。这样的权限也就让这个方向的通讯变得十分的便捷。


  • IOS: stringByEvaluatingJavaScriptFromString

  • Android: loadUrl (4.4-)

  • Tips: 当系统低于 4.4 时,evaluateJavascript 是无法使用的,因此单纯的使用 loadUrl 无法获取 JS 返回值,这时我们需要使用前面提到的 prompt 的方法进行兼容,让 H5 端 通过 prompt 进行数据的发送,客户端进行拦截并获取数据。

  • Android: evaluateJavascript (4.4+)

  • 基于上面的原理,我们已经明白 JSBridge 最基础的原理,并且能实现 Native <=> H5 的双向通讯机制了。


4.3 JSBridge 的接入

接下来,我们来理下代码上需要的资源。实现这套方案,从上图可以看出,其实可以分为两个部分:


  • JS 部分(bridge): 在 JS 环境中注入 bridge 的实现代码,包含了协议的拼装/发送/参数池/回调池等一些基础功能。

  • Native 部分(SDK): 在客户端中 bridge 的功能映射代码,实现了 URL 拦截与解析/环境信息的注入/通用功能映射等功能。


我们这里的做法是,将这两部分一起封装成一个 Native SDK,由客户端统一引入。客户端在初始化一个 WebView 打开页面时,如果页面地址在白名单中,会直接在 HTML 的头部注入对应的 bridge.js。这样的做法有以下的好处:


  • 双方的代码统一维护,避免出现版本分裂的情况。有更新时,只要由客户端更新 SDK 即可,不会出现版本兼容的问题;

  • App 的接入十分方便,只需要按文档接入最新版本的 SDK ,即可直接运行整套 Hybrid 方案,便于在多个 App 中快速的落地;

  • H5 端无需关注,这样有利于将 bridge 开放给第三方页面使用。


这里有一点需要注意的是,协议的调用,一定是需要确保执行在 bridge.js 成功注入后。由于客户端的注入行为属于一个附加的异步行为,从 H5 方很难去捕捉准确的完成时机,因此这里需要通过客户端监听页面完成后,基于上面的事件回调机制通知 H5 端,页面中即可通过 window.addEventListener(‘bridgeReady’, e => {})进行初始化。


4.4 App 中 H5 的接入方式

将 H5 接入 App 中通常有两种方式:在线 H5 和内置包 H5。


(1) 在线 H5,这是最常见的一种方式。我们只需要将 H5 代码部署到服务器上,只要把对应的 URL 地址 给到客户端,用 WebView 打开该 URL,即可嵌入。该方式的好处在于:


  • 独立性强,有非常独立的开发/调试/更新/上线能力;

  • 资源放在服务器上,完全不会影响客户端的包体积;

  • 接入成本很低,完全的热更新机制。


但相对的,这种方式也有对应的缺点:


  • 完全的网络依赖,在离线的情况下无法打开页面;

  • 首屏加载速度依赖于网络,网络较慢时,首屏加载也较慢;


通常,这种方式更适用在一些比较轻量级的页面上,例如一些帮助页、提示页、使用攻略等页面。这些页面的特点是功能性不强,不太需要复杂的功能协议,且不需要离线使用。在一些第三方页面接入上,也会使用这种方式,例如我们的页面调用微信 JS-SDK 。


(2) 内置包 H5,这是一种本地化的嵌入方式,我们需要将代码进行打包后下发到客户端,并由客户端直接解压到本地储存中。通常我们运用在一些比较大和比较重要的模块上。其优点是:


  • 由于其本地化,首屏加载速度快,用户体验更为接近原生;

  • 可以不依赖网络,离线运行;


但同时,它的劣势也十分明显:


  • 开发流程/更新机制复杂化,需要客户端,甚至服务端的共同协作;

  • 会相应的增加 App 包体积;


这两种接入方式均有自己的优缺点,应该根据不同场景进行选择。


五、总结

本文主要解析了现在 Hybrid App 的发展现状和其基础原理,包含了


  • JavaScript 通知 Native

  • Native 通知 Javascript

  • JSBridge 的接入

  • H5 的接入


只有在了解了其最本质的实现原理后,才能对这套方案进行实现以及进一步的优化。接下来,我们将基于上面的理论,继续探讨如何把这套方案的真正代码实现以及方案优化方案,欢迎大家一起讨论!


作者介绍:郭晓东,美图前端工程师,一只有梦想、爱技术的前端程序猿。


本文转载自美图技术公众号。


原文链接:https://mp.weixin.qq.com/s/W6I0iF2oPSAJARxGVqtq6w


2020 年 2 月 21 日 22:51368

评论

发布
暂无评论
发现更多内容

HTML中实现合并单元格

JDoe

html

最香远程开发解决方案!手把手教你配置VS Code远程开发工具,工作效率提升N倍

程序员柠檬

Linux 后台开发 vscode 后端

“新基建”方兴未艾,Smartbi Mining如何为产业数字化转型赋能?

infoq小陈

一款开源且具有交互视图界面的实时 Web 日志分析工具!

JackTian

开源 GoAccess 实时 Web 日志分析工具 交互式查看器

Python deepcopy一个优化

么么哒

Python

GitHub 上十个好用的软件

彭宏豪95

GitHub 效率 工具

为什么要学习 Markdown?究竟有什么用?

JackTian

markdown markdown语法 markdown编辑器

只用CSS实现响应式Full-Width img 2种方法

寇云

CSS css3

自定义列表样式

寇云

CSS css3

前端工程化之创建项目

春生

前端 前端工程 前端架构 全栈工程师

做好领路人——写给技术新人的导师建议

南方

管理 新人

重学 Java 设计模式:实战原型模式

小傅哥

Java 设计模式 小傅哥 复杂代码优化 重构

解决版权难题,“豪横”字体自己做

zhoo299

设计 CG

如何通过样本数据推断其分布

张利东

Python

为什么第三方联调应该先行?

大伟

纯CSS“返回顶部”特效

寇云

CSS css3

写给产品经理的信(5):谈谈项目管理(青铜-王者)

夜来妖

产品 极客时间,项目管理 项目管理 产品经理 项目

偏头疼告诉我的,我想告诉每一个人

zkback

地铁上看书的老外引发的思考

小天同学

写作 读书 个人感想 日常思考

git | IDEA 中如何压缩提交(压缩commit后再push 图文演示)

YoungZY

开发者工具 IDEA 开发工具

互联网省份数据大揭秘,看看哪些地方是互联网的戈壁滩?

非著名程序员

程序员 互联网 IT

自定义构造python白名单__builtins__

么么哒

Python

不懂送女朋友什么牌子的口红?没关系!Python 数据分析告诉你。

JackTian

Python 程序员 数据分析 python 爬虫 口红

《中国互联网简史》系列笔记之P2P

dongh11

读书笔记

MySQL死锁系列-常见加锁场景分析

程序员历小冰

MySQL

时序数据库

pydata

团队与领导力健康检查 | 体检表

Bob Jiang

团队建设

认识数据产品经理(四 与互联网产品经理的区别)

马踏飞机747

大数据 互联网 产品经理 职业规划

Eureka 实例注册状态保持 STARTING 的问题排查

张晓辉

spring Spring Cloud netflix

机器学习项目是如何开发和部署的?

陆道峰

人工智能 学习

python实现·十大排序算法之基数排序(Radix Sort)

南风以南

Python 排序算法 基数排序

打造 VUCA 时代的 10 倍速 IT 团队

打造 VUCA 时代的 10 倍速 IT 团队

提升海量用户极致体验的Hybrid架构设计(原理篇)-InfoQ