“此前,咱们上线了《Cube 技术解读》系列首篇文章《支付宝新一代动态化技术架构与选型综述》,本文为 Cube 系列第二篇文章,针对 Cube 卡片技术栈做了深刻解读,欢送大家关注。”
动静卡片的背景
从 Windows 时代开始,应用程序图标就成了用户(流量)的主入口,并且始终继续到挪动端时代。图标即入口的形式,毛病是不直观,起码须要一次点击后能力接触到想要的信息。在此背景下,iOS 零碎和局部 Android 零碎实现了把内容和服务前置的卡片,举个例子,如下图 1 所示,苹果左一屏的卡片承载天气 & 股市内容的展现。此外,鸿蒙零碎也提出了相似的卡片场景,作为某种流量入口。实际上,在利用外部的卡片作为内容展现以及服务入口的场景则更为广泛,图 2 和图 3 别离是支付宝首页和招行银行的理财页面,其中每个小矩形都是一个卡片。对于经营来说,卡片款式和内容能够随时配置,不必期待利用版本升级,是某种刚需。
Cube 卡片概要
Cube 卡片是蚂蚁金服外部自研的一套跨平台动态化卡片解决方案,是服务于利用页面内的区域动态化技术,面向内容经营,帮忙产品技术进步开发效率和经营效率。每一个 Cube 卡片独立嵌在原生页面内的一个区域,区域内容通过卡片模版进行展现。卡片的定位大抵如下:
-
跨平台一致性
- 一套代码
- 成果对齐
-
动态化
- 界面构造 & 款式动态化
- 业务逻辑动态化
-
高性能
- 极致的性能
- 极致的内存
这里开展讲下高性能。Cube 卡片谋求的是靠近 native 原生体验。咱们定义了两个维度:
一个是极致的性能。在 Cube 小程序能力的根底上,咱们去掉了一些简单的 css 能力,例如伪类伪元素、inline/block 等,同时也对 js 的能力做了限度(Cube 卡片应用 quickjs 作为脚本引擎)。此外,咱们还对 quickjs 做了一些优化,包含不限于离线 atom 编译优化,异步 gc 优化等。咱们也引入了 wamr 作为 quickjs 的“协处理器”,反对用户应用 javascript 和 assemblyscript 混合开发。这样用户能够用 assemblyscript 一些热点函数或者模块。
另一个维度是极致的内存。在信息瀑布场景有限下拉,Cube 卡片的内存增长靠近 Native 卡片。咱们对卡片的能力做了比拟精密分级,通过在开发时配置,减小运行时的内存耗费。下图展现了一个简略卡片,如图所示 Cube 卡片的工程目录,以及钱包某个卡片的实在代码和运行成果。
Cube 卡片的生产 & 工作流程
研发期
- 本地开发
Cube 卡片配套独立的开发工具,反对卡片的编译、日志输入、实时预览等性能,vue 作为以后开发模版的 dsl 语言,反对 js、css 编辑卡片款式。
- 卡片治理
卡片本地开发实现后,通过卡片治理后盾将卡片编译产物上传公布,能够对卡片进行版本治理,卡片公布后就能够在客户端进行卡片的动静更新。
运行期
为了不便端上业务接入 Cube 卡片,咱们引入了一个 Cube 卡片容器(CardSDK)的概念。CardSDK 把一些和业务层 / 服务端分割严密的,且通用能力做了一些封装。例如咱们通过 CubeCardSdk 从服务端拉去卡片和业务数据。此外 CardSDK 也负责罕用的 JSAPI、第三方组件的接入。这样 Cube 卡片可能更专一于卡片产品自身。
外围零碎架构
Cube 卡片的零碎架构次要包含 JSEngine、CardEngine、RenderEngine 和 Platform 几局部,绝大部分代码都是 C ++ 实现。
JSEngine
次要负责卡片 js 逻辑执行和卡片数据变动监听,从而反对开发者在卡片外部写一些业务逻辑能力实现卡片内容和款式的动态变化。
因为卡片场景对性能要求较高,综合包大小和性能等方面思考,咱们抉择了 quickjs 作为咱们的 js 根底引擎库,同时实现了一个十分小的 js 响应式框架(JSFM),用来反对卡片内的逻辑代码能力。
CardEngine
次要负责卡片数据的解析和绑定、卡片逻辑渲染、构建 DOM 指令、JSAPI 治理、JSBinding、Native 事件通信等。
卡片 DOM 树的初始化构建过程,咱们并没有把它放在 js 运行时,而是在卡片实例初始化链路中间接通过 C ++ 进行指令生成和树构建,一方面是为了放弃 js 框架更小更快,另一方面 C ++ 的运行效率更高。
RenderEngine
后端渲染底座,负责卡片布局计算、款式解析、Layer 计算、自绘制组件、同层渲染、光栅化上屏等过程,以及手势、动效等交互成果。
Platform
平台相干接口,包含原子 view 封装、Canvas API、三方组件扩大协定、动画 api 等。
线程模型和数据模型
线程模型
Cube 卡片生命周期内的次要线程包含业务线程和引擎线程,业务线程是卡片数据的初始化阶段由业务发动执行,是卡片生命周期的 beforeCreate 阶段。引擎线程是所有卡片生命周期运行阶段的共有线程,次要包含 Bridge 线程、Render 线程、Paint 线程和 UI 主线程。
Bridge 线程
js 运行时线程,也是 Dom 节点数据查问和解决线程,因为基于 Cube 卡片小、快的定位,js 逻辑只是卡片一个辅助能力,不具备过于简单业务逻辑能力,所以 Bridge 线程绝对较轻,并设计为单线程模式。
Render 线程
渲染相干数据计算线程,包含渲染树构建、节点层级计算、Layer 分层绘制计算、手势数据计算以及渲染工作构建,Render 过程次要波及树的递归计算过程,绝对渲染过程耗时很短,设计为单线程模式。
Paint 线程
绘制线程,执行卡片节点分层绘制及光栅化工作。Paint 线程并不是一个固定的线程,依据当前任务模型,Paint 线程可能是主线程,也可能是一个线程池里的子线程;在同步渲染模式下,Paint 线程间接是主线程;而在异步渲染模式下,通过一个线程池来实现 Paint 工作的并发渲染,进步渲染效率,例如在列表滑动场景。
UI 主线程
UI 操作主线程,即为目前的平台线程,次要包含手势辨认、UI 上屏和三方扩大组件的数据更新等。
除了以上波及的次要线程外,还有埋点和监控相干的 playground 后盾线程,整体优先级比拟低。整体的线程模型设计,最大限度缩小 UI 主线程压力,进步卡片并发渲染效率。但目前还有一些有余,包含 UI 线程切换频繁、Bridge 线程越来越重等,前面会持续优化线程模型。
数据模型
和线程模型对应的数据模型次要包含三棵树:NodeTree、RenderTree、LayerTree,初此之外,还存在一个长期的 PaintTree;
NodeTree
卡片原始节点树,对应前端的 Dom 树,引擎会依据 NodeTree 做款式解析和布局计算;
RenderTree
渲染数据树,这是一颗变形树,很多状况下它的树层级构造和 NodeTree 是一样的,其实当初在设计定义引擎数据模型的时候,咱们探讨过到底要不要这棵树,有没有必要存在这样一颗和 NodeTree 层级一样的树,最终咱们还是保留了,起因是这棵树能够比拟灵便的调整树关系,如果把卡片分为布局阶段和渲染阶段,那么这颗树就是渲染阶段的源树。
事实证明咱们的决定是正确的,咱们后续反对的 zindex/static 等能力,都是因为这棵树的存在能够在引擎层很好的去反对,而不必在平台层去模仿实现这种层级变更能力从而导致很无限的场景反对,包含当前咱们做渲染快照技术也能够从这颗树去思考。
LayerTree
LayerTree 树,顾名思义就是一个分层树,在 RenderTree 根底上对节点进行分层,同一层的节点在同一个渲染工作管线内做绘制光栅化,不同层之间互相独立,能够并发渲染。
PaintTree
PaintTree 是一个长期树,其实严格的说是一个拷贝树,是通过 RenderTree 拷贝一个子树,每次产生渲染时长期生成,当然也会做些节点优化解决,例如被齐全盖住的节点会被优化调,防止反复渲染。每一个 layer 上存在一个 PaintTree,通过 PaintTree 进行节点绘制生成光栅化指令或位图。
高性能列表渲染
对于列表内应用卡片的场景,次要思考的是卡顿影响,尤其是中低端机设备。Cube 卡片反对异步渲染,所以在列表场景下能够很晦涩,同时因为反对多线程并发能力,能够多张卡片并发渲染,所以在异步渲染条件下也不会有显著的白屏成果。
Native 技术优化
咱们冀望卡片服务于页面内区域化内容动静展示和简略业务逻辑,更多的是面向挪动端开发者。即便咱们应用的卡片 DSL 语言形容是前端语言,咱们也心愿可能对 CSS 的应用做束缚、反对无限的 CSS 能力,但同时也心愿尽可能笼罩到一些开发者罕用的 CSS 能力。
所以咱们针对 CSS 能力做了一个专项工作,和前端团队技术同学一起做了 Cube 卡片 CSS 能力标准,对 CSS 能力做了束缚限度。即便如此,在 Native 渲染引擎下,想十分好的去反对这些能力,也是有很多艰难,包含 zindex 的反对、overflow 等,因而咱们也基于一些依赖的平台能力也做了优化解决。
Layer 容器
咱们引入 Layer 容器概念,后面介绍数据模型时,提到了 LayerTree,每一个 Layer 节点是一个独立的渲染容器,由平台 View 作为 Layer 容器来渲染其余虚构节点。如果依照惯例的做法是一个 View 对应一个渲染容器,咱们应用两个 View 组合为 Layer 容器(iOS 应用 CALayer),将内容层和逻辑层拆散,这样做的益处很多,例如 layer 节点的 shadow 绘制限度裁剪问题、内容层的画布切割优化等。
手势革新
手势的优化革新次要为了解决平台零碎手势散发能力的限度,不论是 Android 平台还是 iOS 平台,系统对手势的散发解决都有一些限度,例如兄弟节点不能散发事件(iOS)、超过父节点区域无奈接管事件(影响 overflow 能力)等,所以须要对手势进行革新。
因为卡片渲染反对三方组件扩大,为了不影响扩大组件的事件响应,咱们基于 Layer 容器接管革新零碎手势行为,外部进行容器节点的手势散发治理,而对于存在三方组件混合渲染的场景,Layer 容器和三方组件之间的手势散发放弃零碎行为。
光栅化
Cube 卡片渲染过程包含指令渲染和位图渲染两种渲染模式,这两种模式会在不同场景条件下切换,用来优化不同场景下性能,例如帧率和内存。位图渲染在 Android 上绝对比较复杂。默认是用 Bitmap 作为离线渲染的缓存,毛病是引入一次额定 cpu/gpu 内存拷贝并且无奈充分利用 GPU 资源,劣势是兼容性好。咱们尝试过应用 textureview 作为离线渲染缓冲,发现 6.0 以下设施存在重大的兼容性问题,而且不同设施之间的稳定性差异微小。
同时光栅化能力依赖平台零碎的 Canvas API,有些高阶办法会波及硬件加速的限度,包含 shadow api 以及系统对 glRender buffer 的限度 (Android 平台),咱们也对大画布场景做了视图切割分段渲染来保障渲染性能。咱们共事也在着手用 Skia Canvas api 代替平台层的 Canvas API。
同层渲染
Cube 卡片把三方组件当作独立一层 layer 独自进行数据更新,能够十分不便高效的接入扩大的三方组件。基于零碎的 UI 能力,使扩大组件在卡片内天生对立渲染。同时反对组件在不同卡片上的复用。在理论的业务场景中,同层渲染也带来了很多额定的问题。诸如地图 / 视频 / 动画等组件,个别会随同着较大的性能内存开销。这些开销对卡片的渲染会有负面影响,尤其在列表滚动时。对于地图 / 视频组件,咱们配合组件提供方 case by case 的解决问题,并且试图在卡片上线时设置卡点。对于动画组件,Cube 继续的在扩大属性动画 / 帧动画能力,并且内置 canvas 能力。
Cube 卡片的业务现状和将来布局
目前 Cube 卡片曾经服务钱包的首页、证券(股票)、卡包、出行等 20+ 的业务场景,日 pv 超过 100 亿。在将来相当长的一段时间内,咱们的次要精力还是会集中在钱包外部的业务场景,把存量的 native 卡片 /h5 卡片 cube 化。服务好钱包内的场景,一方面须要把开发者体验做好,诸如开发调试工具链条,另一方面要继续的优化根底性能,诸如谋求更小的包体积,更低的内存等。
卡片将来布局一个重点方向是商业化,即把 Cube 卡片输入到中小型互联网公司以及金融企业。这部分的工作曾经启动了一段时间,预计年底前会作为 mpaas https://tech.antfin.com/produ… 的一个扩大性能公布。
卡片将来布局的另一个方向是物联网设施(例如 RTOS)的利用开发栈。精确说不是 Cube 卡片,而是 Cube 卡片和小程序的某种两头状态。物联网设施的界面个别比较简单,近似卡片;然而又须要多个“卡片”之间的路由能力,更靠近于利用的状态。这样一个混合状态既能保留 Cube 卡片在内存 / 性能 / 包体积上的劣势,又能满足物联网设施利用开发的诉求。依据咱们的调研,大部分 RTOS 利用开发环境还是停留在传统的 c 语言,效力和动态性都不不现实。对于开发者来说,Cube 兴许是一个抉择。
预报
如你对该系列文章感兴趣,感激大家继续关注本公众号【阿里巴巴挪动技术】,下一篇 Cube 技术解读文章咱们再持续畅聊。
关注咱们,每周 3 篇挪动干货 & 实际给你思考!