“ 此前,咱们上线了《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 篇挪动干货&实际给你思考!