docs: Hybrid、领域驱动设计与 BFF、项目复盘

This commit is contained in:
LiuBinPeng
2021-08-24 09:51:28 +08:00
parent d819a88652
commit 55a89dc4a5
13 changed files with 237 additions and 66 deletions

View File

@@ -1,15 +1,15 @@
# 一个 Hybrid SDK 设计与实现
随着移动浪潮的兴起,各种 App 层出不穷,极速发展的业务拓展提升了团队对开发效率的要求,这个时候纯粹使用 Native 开发技术成本难免会更高一点。而 H5 的低成本、高效率、跨平台等特性马上被利用起来了,形成一种新的开发模式: Hybrid App
作为一种混合开发的模式Hybrid App 底层依赖于 Native 提供的容器Webview上层使用各种前端技术完成业务开发现在三足鼎立的 Vue、React、Angular底层透明化、上层多样化。这种场景非常有利于前端介入非常适合业务的快速迭代。于是 Hybrid 火了。
大道理谁都懂,但是按照我知道的情况,还是有非常多的人和公司在 Hybrid 这一块并没有做的很好,所以我将我的经验做一个总结,希望可以帮助广大开发者的技术选型有所帮助
> 随着移动浪潮的兴起,各种 App 层出不穷,极速发展的业务拓展提升了团队对开发效率的要求,这个时候纯粹使用 Native 开发技术成本难免会更高一点。而 H5 的低成本、高效率、跨平台等特性马上被利用起来了,形成一种新的开发模式: Hybrid App
>
> 作为一种混合开发的模式Hybrid App 底层依赖于 Native 提供的容器Webview上层使用各种前端技术完成业务开发现在三足鼎立的 Vue、React、Angular底层透明化、上层多样化。这种场景非常有利于前端介入非常适合业务的快速迭代。于是 Hybrid 火了。
>
> 大道理谁都懂,但是按照我知道的情况,还是有非常多的人和公司在 Hybrid 这一块并没有做的很好,所以我将我的经验做一个总结,希望可以帮助广大开发者的技术选型有所帮助
## Hybrid 的一个现状
## 一、Hybrid 现状
可能早期都是 PC 端的网页开发随着移动互联网的发展iOS、Android 智能手机的普及,非常多的业务和场景都从 PC 端转移到移动端。开始有前端开发者为移动端开发网页。这样子早期资源打包到 Native App 中会造成应用包体积的增大。越来越多的业务开始用 H5 尝试,这样子难免会需要一个需要访问 Native 功能的地方,这样子可能早期就是懂点前端技术的 Native 开发者自己封装或者暴露 Native 能力给 JS 端,等业务较多的时候者样子很明显不现实,就需要专门的 Hybrid 团队做这个事情;量大了,就需要规矩,就需要规范。
@@ -27,12 +27,12 @@ Hybrid 在大量应用的时候就需要一定的规范,那么本文将讨论
## Native 与前端分工
## 二、Native 与前端分工
在做 Hybird 架构设计之前我们需要分清 Native 与前端的界限。首先 Native 提供的是宿主环境,要合理利用 Native 提供的能力,要实现通用的 Hybrid 架构,站在大前端的视觉,我觉得需要考虑以下核心设计问题。
### 交互设计
### 1. 交互设计
Hybrid 架构设计的第一要考虑的问题就是如何设计前端与 Native 的交互,如果这块设计不好会对后续的开发、前端框架的维护造成深远影响。并且这种影响是不可逆、积重难返。所以前期需要前端与 Native 好好配合、提供通用的接口。比如
@@ -43,14 +43,14 @@ Hybrid 架构设计的第一要考虑的问题就是如何设计前端与 Native
### 账号信息设计
### 2. 账号信息设计
账号系统是重要且无法避免的Native 需要设计良好安全的身份验证机制,保证这块对业务开发者足够透明,打通账户体系
### Hybrid 开发调试
### 3. Hybrid 开发调试
功能设计、编码完并不是真正结束Native 与前端需要商量出一套可开发调试的模型,不然很多业务开发的工作难以继续。
@@ -67,7 +67,7 @@ Android 调试技巧:
## Hybrid 交互设计
## 三、Hybrid 交互设计
Hybrid 交互无非是 Native 调用 H5 页面JS 方法,或者 H5 页面通过 JS 调 Native 提供的接口。2者通信的桥梁是 Webview。
业界主流的通信方法1.桥接对象时机问题不太主张这种方式2.自定义 Url scheme
@@ -82,7 +82,7 @@ weixin:// 可以打开微信。
### JS to Native
### 1. JS to Native
Native 在每个版本都会提供一些 Api前端会有一个对应的框架团队对其封装释放业务接口。举例
@@ -116,7 +116,7 @@ SDGHybridReady(function(arg){
### Api 交互
### 2. Api 交互
调用 Native Api 接口的方式和使用传统的 Ajax 调用服务器,或者 Native 的网络请求提供的接口相似
![Api交互](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HybridApi.jpg)
@@ -128,7 +128,7 @@ SDGHybridReady(function(arg){
### 格式约定
### 3. 格式约定
交互的第一步是设计数据格式。这里分为请求数据格式与响应数据格式,参考 Ajax 模型:
```
@@ -295,13 +295,13 @@ typedef NS_ENUM(NSInteger){
## 常用交互 Api
## 四、常用交互 Api
良好的交互设计是第一步,在真实业务开发中有一些 Api 一定会由应用场景。
### 跳转
### 1. 跳转
跳转是 Hybrid 必用的 Api 之一,对前端来说有以下情况:
- 页面内跳转,与 Hybrid 无关
- H5 跳转 Native 界面
@@ -361,8 +361,7 @@ back 与 forward 一致,可能会有 animatetype 参数决定页面切换的
## Header 组件的设计
### 2. Header 组件的设计
Native 每次改动都比较“慢”,所以类似 Header 就很需要。
1. 主流容器都是这么做的,比如微信、手机百度、携程
@@ -590,8 +589,7 @@ define([], function () {
## 请求类
### 3. 请求类
虽然 get 类请求可以用 jsonp 方式绕过跨域问题,但是 post 请求是一个拦路虎。为了安全性问题服务器会设置 cors 仅仅针对几个域名Hybrid 内嵌静态资源可能是通过本地 file 的方式读取,所以 cors 就行不通了。另外一个问题是防止爬虫获取数据,由于 Native 针对网络做了安全性设置(鉴权、防抓包等),所以 H5 的网络请求由 Native 完成。可能有些人说 H5 的网络请求让 Native 走就安全了吗?我可以继续爬取你的 Dom 节点啊。这个是针对反爬虫的手段一。想知道更多的反爬虫策略可以看看我这篇文章 [Web反爬虫方案](https://github.com/FantasticLBP/Anti-WebSpider)
@@ -628,7 +626,7 @@ requestHybrid({
## 常用 NativeUI 组件
## 五、常用 NativeUI 组件
一般情况 Native 通常会提供常用的 UI比如 加载层loading、消息框toast
@@ -663,7 +661,7 @@ Native UI与前端UI不容易打通所以在真实业务开发过程中
## 账号系统的设计
## 六、账号系统的设计
Webview 中跑的网页,账号登录与否由是否携带密钥 cookie 决定(不能保证密钥的有效性)。因为 Native 不关注业务实现,所以每次载入都有可能是登录成功跳转回来的结果,所以每次载入都需要关注密钥 cookie 变化,以做到登录态数据的一致性。
@@ -704,7 +702,7 @@ HybridUI.logout = function () {
## Hybrid 资源管理
## 七、Hybrid 资源管理
Hybrid 的资源需要 `增量更新` 需要拆分方便,所以一个 Hybrid 资源结构类似于下面的样子
@@ -738,7 +736,7 @@ WebApp
## 增量更新
## 八、增量更新
每次业务开发完毕后都需要在打包分发平台进行部署上线,之后会生成一个版本号。
@@ -760,9 +758,9 @@ WebApp
## 一些零散的解决方案
## 九、体验优化
1. 静态直出
### 1. 静态直出
“直出”这个概念对前端同学来说,并不陌生。为了优化首屏体验,大部分主流的页面都会在服务器端拉取首屏数据后通过 NodeJs 进行渲染,然后生成一个包含了首屏数据的 Html 文件,这样子展示首屏的时候,就可以解决内容转菊花的问题了。
当然这种页面“直出”的方式也会带来一个问题,服务器需要拉取首屏数据,意味着服务端处理耗时增加。
@@ -771,18 +769,16 @@ WebApp
我们可以做一个类似的事情,自动同步最新的代码和数据,然后生成新的含首屏 Html并发布到 CDN 上面去
2. 离线预推
### 2. 离线预推
页面发布到 CDN 上面去后,那么 WebView 需要发起网络请求去拉取。当用户在弱网络或者网速比较差的环境下,这个加载时间会很长。于是我们通过离线预推的方式,把页面的资源提前拉取到本地,当用户加载资源的时候,相当于从本地加载,即使没有网络,也能展示首屏页面。这个也就是大家熟悉的离线包。
手 Q 使用 7Z 生成离线包, 同时离线包服务器将新的离线包跟业务对应的历史离线包进行 BsDiff 做二进制差分生成增量包进一步降低下载离线包时的带宽成本下载所消耗的流量从一个完整的离线包253KB降低为一个增量包3KB
https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247488218&idx=1&sn=21afe07eb642162111ee210e4a040db2&chksm=f951a799ce262e8f6c1f5bb85e84c2db49ae4ca0acb6df40d9c172fc0baaba58937cf9f0afe4&scene=27#wechat_redirect
[手Q开源Hybrid框架VasSonic介绍极致的页面加载速度优化](https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247488218&idx=1&sn=21afe07eb642162111ee210e4a040db2&chksm=f951a799ce262e8f6c1f5bb85e84c2db49ae4ca0acb6df40d9c172fc0baaba58937cf9f0afe4&scene=27#wechat_redirect)
3. 拦截加载
### 3. 拦截加载
事实上,在高度定制的 wap 页面场景下,我们对于 webview 中可能出现的页面类型会进行严格控制。可以通过内容的控制,避免 wap 页中出现外部页面的跳转,也可以通过 webview 的对应代理方法,禁掉我们不希望出现的跳转类型,或者同时使用,双重保护来确保当前 webview 容器中只会出现我们定制过的内容。既然 wap 页的类型是有限的,自然想到,同类型页面大都由前端采用模板生成,页面所使用的 html、css、js 的资源很可能是同一份,或者是有限的几份,把它们直接随客户端打包在本地也就变得可行。加载对应的 url 时,直接 load 本地的资源。
对于 webview 中的网络请求,其实也可以交由客户端接管,比如在你所采用的 Hybrid 框架中为前端注册一个发起网络请求的接口。wap 页中的所有网络请求都通过这个接口来发送。这样客户端可以做的事情就非常多了举个例子NSURLProtocol 无法拦截 WKWebview 发起的网络请求,采用 Hybrid 方式交由客户端来发送,便可以实现对应的拦截。
@@ -792,25 +788,46 @@ https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247488218&idx=1&sn=21afe0
NSURLProtocol能够让你去重新定义苹果的URL加载系统(URL Loading System)的行为URL Loading System里有许多类用于处理URL请求比如NSURLNSURLRequestNSURLConnection和NSURLSession等。当URL Loading System使用NSURLRequest去获取资源的时候它会创建一个NSURLProtocol子类的实例你不应该直接实例化一个NSURLProtocolNSURLProtocol看起来像是一个协议但其实这是一个类而且必须使用该类的子类并且需要被注册。                                       
4. WKWebView 网络请求拦截
方法一Native 侧):
原生 WKWebView 在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此在 WKWebView 上直接使用 NSURLProtocol 是无法拦截请求的。
但是由于 mPaas 的离线包机制强依赖网络拦截所以基于此mPaaS 利用了 WKWebview 的隐藏 api去注册拦截网络请求去满足离线包的业务场景需求参考代码如下
### 4. WKWebView 网络请求拦截
```Objective-c
[WKBrowsingContextController registerSchemeForCustomProtocol:@"https"]
```
但是因为出于性能的原因WKWebView 的网络请求在给主进程传递数据的时候会把请求的 body 去掉,导致拦截后请求的 body 参数丢失。
- 方法一Native 侧):
原生 WKWebView 在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此在 WKWebView 上直接使用 NSURLProtocol 是无法拦截请求的。
在离线包场景,由于页面的资源不需要 body 数据,所以离线包可以正常使用不受影响。但是在 H5 页面内的其他 post 请求会丢失 data 参数。
但是由于 mPaas 的离线包机制强依赖网络拦截所以基于此mPaaS 利用了 WKWebview 的隐藏 api去注册拦截网络请求去满足离线包的业务场景需求参考代码如下
为了解决 post 参数丢失的问题mPaas 通过在 js 注入代码hook 了 js 上下文里的 XMLHTTPRequest 对象解决。
```Objective-c
[WKBrowsingContextController registerSchemeForCustomProtocol:@"https"]
```
通过在 JS 层把方法内容组装好,然后通过 WKWebView 的 messageHandler 机制把内容传到主进程,把对应 HTTPBody 然后存起来,随后通知 JS 端继续这个请求,网络请求到主进程后,在将 post 请求对应的 HttpBody 添加上,这样就完成了一次 post 请求的处理。整体流程可以参考如下:
![ajax-时序图](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2021-05-28-WKWebViewRequestHook)
通过上面的机制,既满足了离线包的资源拦截诉求,也解决了 post 请求 body 丢失的问题。但是在一些场景还是存在一些问题,需要开发者进行适配。
但是因为出于性能的原因WKWebView 的网络请求在给主进程传递数据的时候会把请求的 body 去掉,导致拦截后请求的 body 参数丢失。
方法二JS 侧):
通过 AJAX 请求的 hook 方式,将网络请求的信息代理到客户端本地。能拿到 WKWebView 里面的 post 请求信息,剩下的就不是问题啦。
AJAX hook 的实现可以看这个 [Repo](https://github.com/wendux/Ajax-hook).
在离线包场景,由于页面的资源不需要 body 数据,所以离线包可以正常使用不受影响。但是在 H5 页面内的其他 post 请求会丢失 data 参数。
为了解决 post 参数丢失的问题mPaas 通过在 js 注入代码hook 了 js 上下文里的 XMLHTTPRequest 对象解决。
通过在 JS 层把方法内容组装好,然后通过 WKWebView 的 messageHandler 机制把内容传到主进程,把对应 HTTPBody 然后存起来,随后通知 JS 端继续这个请求,网络请求到主进程后,在将 post 请求对应的 HttpBody 添加上,这样就完成了一次 post 请求的处理。整体流程可以参考如下:
![ajax-时序图](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2021-05-28-WKWebViewRequestHook)
通过上面的机制,既满足了离线包的资源拦截诉求,也解决了 post 请求 body 丢失的问题。但是在一些场景还是存在一些问题,需要开发者进行适配。
- 方法二JS 侧):
通过 AJAX 请求的 hook 方式,将网络请求的信息代理到客户端本地。能拿到 WKWebView 里面的 post 请求信息,剩下的就不是问题啦。
AJAX hook 的实现可以看这个 [Repo](https://github.com/wendux/Ajax-hook).
## 十、离线包
传统的 H5 技术容易受到网络环境影响,因而降低 H5 页面的性能。通过使用离线包,可以解决该问题,同时保留 H5 的优点。
**离线包** 是将包括 HTML、JavaScript、CSS 等页面内静态资源打包到一个压缩包内。预先下载该离线包到本地,然后通过客户端打开,直接从本地加载离线包,从而最大程度地摆脱网络环境对 H5 页面的影响。
使用 H5 离线包可以给您带来以下优势:
- **提升用户体验**:通过离线包的方式把页面内静态资源嵌入到应用中并发布,当用户第一次开启应用的时候,就无需依赖网络环境下载该资源,而是马上开始使用该应用。
- **实现动态更新**:在推出新版本或是紧急发布的时候,您可以把修改的资源放入离线包,通过更新配置让应用自动下载更新。因此,您无需通过应用商店审核,就能让用户及早接收更新。
![离线包下载流程](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Hybrid-OfflinePackageDownload.png)
![离线包加载流程](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Hybrid-OfflinePackageLoad.png)