seeder系统加速(seed加速器安卓安装)

得到 APP 是一个三年多的产品,最初采用纯 Native 的方式开发,在 18 年初,我们开始了 Hybyid 开发技术方案的探索和实践, 目前得到 APP 共包含了 ReactNative 和 Webview 两套 Hybrid 方案。本文从时间维度上,重点回顾一下 Webview Hybrid 方案在得到 APP 从 0 到 1 的过程,也希望我们的经历可以给一些想落地 Hybrid 方案的团队一点启发。

1. 背景和动机

得到是一个重运营场景的产品,APP 内大部分的功能都会有分享功能。18 年初时,开发一个功能,基本需要三端三个人。部分业务使用了内嵌 Webview 、类浏览器式的方案,虽然满足了跨端,但体验较差。所以最初的目的是希望有一套跨平台方案,一套代码可以三端执行,并且有较好的体验,这是当时 Hybrid 的架构图:

seeder系统加速(seed加速器安卓安装)

服务端包含 Seeder 和 CDN 两部分,CDN 部分主要是用来承接资源包的下载。Seeder 则拆分为 Updater 服务和 Manager 服务:

  1. Updater 服务:主要是承接处理客户端的更新请求;
  2. Manager 服务:主要进行资源包及相关配置的管理,包括生成 diff 包等等;

通过合理的拆分,Updater 服务在我们后续的压力测试中,2 台 8C16G 机器可以稳定承载 6000QPS;

2.3. 关键实现点 – Package 定义

既然是对资源包进行管理,我们需要定义资源包的格式和约束。

格式方面,我们选择了 tgz 格式,即使用 tar 进行归档,用 gzip 进行压缩的格式,以减少传输体积。

文件结构方面,在原有资源目录结构下的根目录,增加了一个 info.json 格式的文件,用来描述包的信息。

![Package 结构(https://piccdn.luojilab.com/fe-oss/default/image-20200114171319102.png)

我们来看下一 package.json 的结构:

seeder系统加速(seed加速器安卓安装)

相较于普通包,多了一个 update.json 文件,这个文件描述了整个包是如果变化的,基于这个文件和包内的其他文件,便可以 Patch 到最先的版本,看一下 update.json 的结构:

seeder系统加速(seed加速器安卓安装)

完成了基础设施的建设之后,客户端的离线资源也具备的动态更新的能力,但普通的 Web 离线包还有以下的限制:

  1. 每个 Webview 只能有一个页面,无法实现复杂的功能(为了跟客户端保持一致的页面交互体验,每个 Webview 只有一个页面,这样前进后退、导航条的表现是一致的);
  2. 无法控制导航条,一些需要定制导航条的功能依赖客户端;
  3. 没有体系化的框架,无法统一处理异常、缓存等;

为了解决以上问题,我们决定开发一个应用层的框架。

3.1. 目标和分解

我们整个团队最熟悉的技术栈是 Vue,因此 Adam 肯定是基于 Vue 做封装,在设计 Adam 之前,需要我们先确认目标:

  1. 功能上:一个 Package 可以作为一个完整的 Application,能够完整地实现一个功能模块,包括多页面的功能等;
  2. 技术上:实现标准化的解决方案,由框架处理缓存、异常页面等通用逻辑;

对目标进一步做分解:

  1. 需要客户端将界面全部交给 Webview 处理;
  2. 需要 Router,并且像客户端一样,支持栈式管理页面的路由;
  3. 页面要实现客户端相同的前进和后退动效,要支持滑动返回上一个页面;
  4. 需要抽象缓存和异常页面等到框架层;

seeder系统加速(seed加速器安卓安装)

每一个 Web Package 就是一个应用,每个 Application 实例对应一个 Global Store 和 vue-stack-router 的实例,对应多个 Page 实例。

每一个页面都由 Page Componets 和 Page Store 组成。其中 Page Store 的生命周期跟页面保持一致。

3.3. 关键实现点 – Router

最初的 Router 方案我们是选了我们常用的 vue-router,但在实现过程中,遇到了以下问题:

  1. 实现类似栈式的路由较为困难。客户端内的页面大部分都具有栈式的特点,页面实例的存活取决于是否在栈中。而 vue-router 中,组件实例的存活则是取决与是否使用了 kee-alive 组件;
  2. 实现两个页面间的、类似 Native 的滑动返回较为困难;
  3. 无法实现多例页面。Native 中,A 页面跳转 A 页面,会产生一个新的 A 页面的实例。vue-router 中,A 页面跳转 A 页面会重新渲染现有的 A 页面,也就是 A 页面始终是单例的;

为了解决这些问题,我们开发了 vue-stack-router (已开源,具体实现细节,感兴趣的可以直接看 github 代码,内容较多,这里不展开),相较于 vue-router,有以下新功能:

  1. 栈式的路由管理;
  2. 路由间数据传递;
  3. 支持更细粒度、可定制的路由过渡效果;
  4. 支持预渲染;

基于预渲染模式,我们实现了手势滑动返回的功能,即触发手势时,预渲染后一个页面,此时同时存在两个叠加在一起的页面,通过 JS 控制两个页面的动画,便可以实习类似 Native 的滑动返回的效果。

3.4. 关键实现点 – Store

提到状态管理工具,共识都是简单的项目无需使用 Store,复杂项目才能体现出 Store 的价值,其实无非是引入 Store 带来了成本。我们分析一下移动端页面的特点:

  1. 展示为主
  2. 页面间耦合性低
  3. 数据流简单

因此,在移动端页面,我们追踪状态变化的收益可能不会很高,如果去掉状态追踪,Store 可以变的很精简, 看一下我们自己精简的 Store,原理如下:

classMyStore{public name:string='';public updateName(name:string):void{this.name = name;}}const store =Vue.observable(newMyStore());

没有状态追踪,只是最精简的将状态抽离到一个类中进行管理。

聊完了 Store 实现,再看看关于 Store 的组织形态,我们常用 Vuex 和 Redux 都是单一组件树,连 MobX 也有 mobx-state-tree 这种单一组件树的社区方案。但是结合移动端业务的特点,单一组件树会有些问题,对多页面实例的支持,实现比较复杂。再一个,优秀的单一组件树的组织通常是跟页面分离的,经过单独设计的,因此会带来了额外的心智负担。

基于以上死牢,最后我们没有采用单一组件树,而是实现了多状态的 Store 方案:一个页面对应一个 Store,Store 和页面的生命周期保持一致的方案。逻辑跟展现分离,页面间又不耦合,最重要的是简单;

3.5. 缓存

数据缓存是体验优化的一大利器,通过先渲染缓存数据,在更新正式数据的方式,我们可以立刻展现出一个页面而无需等待。Adam 实现了三级缓存:

seeder系统加速(seed加速器安卓安装)

至此我们可以将每个 Package 当做一个独立的 Application 来更新和迭代。

5. 总结和思考

5.1. 成果

功能方面,我们接入了讲座、电子书、评测、训练营、得到大学、活动系统、帮助中心等模块,接入了 90+的页面(其中 ReactNative 占 30+,Web 占 60+);

seeder系统加速(seed加速器安卓安装)

Adam + Seeder 方案:

seeder系统加速(seed加速器安卓安装)

基本可以看到,稳定性和效率都有较好的改善。

5.2. 思考

Hybrid 落地过程中,我们踩了很多坑,也有很多收货,简单谈两点。

第一个,如何评价一个技术方案的好坏?我们有太多的标准:站在业务角度,是不是能满足需求及低成本的满足潜在的后续需求;站在运维角度,是不是带来了新的部署运维成本。站在技术角度,我们甚至可以掏出一本设计模式大谈一番。但是我们很少有注意到技术方案的用户体验,这里的用户指的是使用你框架、库的开发同学。站在业务开发同学的角度会发现,提供的方案确实解决了问题,但是使用这个方案过程中,可能有 30% 工作是不属于方案部分,但是属于方案部分必须的,比如方案的入参是 A,开发者需要花大力气才能得到 A,才能使用这个方案。所以作为框架、库的开发者,要考虑清楚整个方案的使用场景,技术部分是不是可以覆盖整个场景,覆盖不了要怎么解决,是否需要提供自动化工具等等。

第二个,Hybrid 不是一个端的事情,而是三端一起的事情,而作为推动方,要尽可能的了解三端,不了解可以多跟各端同学沟通交流,不要做成一方推动两方配合,要让大家感觉是在一起干一件事情,这样才能做好。

5.3 后续的规划

后续的规划主要是有两大方面:

  1. Adam 的多环境多端的支持,覆盖得到业务“端”的场景;
  2. Seeder 更加灵活的更新场景,比如支持 Lazy 加载等;
(0)
小多多的头像小多多创始人

相关推荐

发表回复

登录后才能评论