作者:何瑾(潇珺)

本文为《Cube 技术解读》系列第四篇文章,往期文章欢送大家回顾。

  • 《Cube 技术解读 | Cube 小程序技术详解》
  • 《Cube 技术解读 | 支付宝新一代动态化技术架构与选型综述》
  • 《Cube 技术解读 | Cube 卡片技术栈详解》

阿里是个重经营的公司,前端开发者居多,2016-2017年,在Weex还是1.0时代,React Native开源还没多久,Flutter还没诞生的时候,如何在贴合前端开发环境的前提下,疾速铺到android/iOS双平台是个大热点,支付宝外部孵化一个动态化跨平台计划趁势而生。

后面三篇文章别离介绍了Cube以后架构,Cube卡片和Cube小程序技术产品状态。这篇文章次要探讨Cube的渲染设计,帮忙大家理解Cube卡片渲染技术的前世今生。

Native原生渲染的问题

咱们都晓得一个原生view渲染上屏须要几个步骤,以android举例:create、measure、layout、draw,这些须要在主线程实现,当实现原生列表时,即便完满复用item,对不同数据渲染时,也须要measure、layout、draw几步缺一不可,而且随着view嵌套层级越深,对主线程资源耗费越大,当列表fly起来当前,帧率疾速降落,造成页面卡顿,基于这个问题,cube在调研期间,如何解决渲染效率是重要的一part。

通常来说优化列表滚动帧率,也就是view层级、布局复杂度、去掉不必要背景色,解决适度绘制,图片懒加载、item复用等方面下手,但基本还是绕不过measure、layout、draw。彼时的weex和RN,也都还是将html中的标签映射到平台层view,在某些场景下,开发者又不能像原生开发一样自行优化,在渲染性能上饱受诟病。因而cube调研期间渲染指标是:优化渲染效率+跨平台。

跨平台异步渲染计划

异步渲染

基于下面提到的背景和需要,那么咱们就想,是否有一种形式,把关键步骤移除出线程呢,即异步渲染。在列表滚动时根本只有零碎手势和列表自身滚动算法、动画须要占用主线程,将大大提高帧率。视图内元素绘制的产物是一个像素缓存(Cube采纳的设计是Bitmap),回到主线程给视图进行刷新显示。

跨平台架构

另一个指标跨平台,是要做到能够疾速扩大其余平台,cube将波及平台的局部分离出来,造成platform 层。

platform

这里提供了各平台通用的规范c++原子接口,在不同平台用平台语言实现,初步只实现了android、iOS两个平台,android通过jni调用java办法,iOS在实现文件中c++、OC混编。如果将来须要扩大其余平台例如macOS,只需实现platform层定义的接口即可,能够达到疾速扩大其余平台的指标。

core

library是基于platform原子接口用c++实现的是根底库,例如文件IO、UI控件、图片下载、音讯通信等,供下层引擎应用。library之上,就是cube渲染的外围实现,渲染局部包含数据模型和渲染逻辑,组件库指cube外部反对的一些零碎实体控件,或者开发者可外接的实体组件。

下图是第一版cube渲染架构图。

cube渲染架构图

异步渲染技术选型

后面提到了,异步渲染计划里异步绘制的“产物”是一张bitmap交给“容器”View,为什么是bitmap呢,看起来对内存很不敌对,View又是个什么View,有没有特殊性,上面聊聊cube调研期间都钻研过哪些计划,最终为什么选型bitmap。

Android平台技术选型

android的选型之路崎岖起伏,最先能想到的反对独立渲染线程的textureView、GLSurfaceView做为容器,但有显著缺点,是不能用于常见业务的列表场景的,只能利用于特定场景。

SurfaceView、GLSurfaceView

SurfaceView从android1.0开始就有,次要特点是它的渲染能够在子线程中实现,因而存在的问题是,尽管它继承View,然而它领有独立的Surface,不在View hierachy中,它的显示也不受View的属性管制,因而不能像一般view一样缩放平移,更不能作为item放在listView/RecycleView中当作一般view应用,滚动起来会有不同步的问题。

GLSurfaceView继承SurfaceView,它自带GLThread,有和GLSurfaceView雷同的问题,总之,这两个view更适宜单个视频渲染或者像地图类渲染场景。

有人可能要问,整个页面都用SurfaceView/GLSurfaceView不就行了,连列表也在render线程实现?这里两个问题:

1、如果列表容器也在render线程实现,正如当初的flutter一样,那么列表滑动手势解决须要本人实现,比方drag,fling,各种列表滚动个动画,以及滚动加速度计算等,老本很高。并且,touch事件捕捉依然依赖平台层,而处理事件须要切换到render线程,这两头肯定有线程切换老本造成的不跟手的体验问题。当初很多基于flutter引擎革新的渲染引擎,正面临着这些问题;

2、在过后cube团队的次要指标是疾速验证 ,列表的实现这种老本过高,不是主要矛盾所在。

TextureVIew

textureView是google从android4.0开始提供的,它的呈现很大水平上是为了补救SurfaceView、GLSurfaceView与原生View交融的有余,基于下面一节形容的这两个view与原生view一起动画的问题,textureView仿佛更适宜咱们的场景,既能反对独立render线程,又能保障与原生view完满交融。

然而,在理论的调研过程中发现,textureView的渲染机制,不适用于长列表,如果每个列表的item是一个textureView,那么就波及到出屏回收,进屏创立,否则会带来内存问题。而回收和创立SurfaceTexture是异步过程,呈现了闪黑屏问题。除此之外,进一步发现textureView的数量和容量(每个view的尺寸累计)存在某个下限,而且不同手机下限也差别很大。简略说,这是一个看起来很美妙,然而兼容性坑有数的技术路线。

Bitmap+一般View

最终抉择了bitmap看起来并不完满的计划,尽管这被大多数android开发认为bitmap带来大量内存耗费,视为不可承受,但随着cube的利用范畴越来越广,这逐步被证实是在过后,最普适的一个计划。

每一个layer对应一个零碎view,每个view的绘制内容在子线程通过CanvasAPI异步绘制在bitmap上,当view上屏时,零碎onDraw绘制这个bitmap“产物”。

BitmapCache

尽管用了Bitmap绘制计划,但必须要思考内存过载的问题,这里咱们采纳了BitmapCache,次要针对列表类型场景,依赖零碎的item回收回调告诉,将bitmap画布放入Cache,item上屏渲染时,优先从cache取bitmap画布应用,优先取雷同大小的,如果不存在,则取width、height大于指标width、height,让view只绘制bitmap部分,达到正确渲染的目标

iOS平台技术选型

iOS的实现原理与android大致相同,区别是,iOS异步线程绘制实现的“产物”,不会在UIView的drawRect里利用CoreGraphics进行渲染,这种形式效率很低,页面卡顿显著,最终采纳的是将画布赋值给UIView的layer,托管给零碎渲染layer。

渲染技术的演进

下面讲了cube异步渲染大体计划和关键技术选型,事实上,从19年初上线答答星球,到当初,cube在支付宝内利用越来越宽泛,这两头也随同着cube团队依据理论业务场景一直摸索、优化的过程,渲染链路经验了两次重构。须要强调的,这个演进过程是在严格的内存/性能下实现的,而且要对Android兼容性做出斗争。一些看起来不那么优雅或者先进的设计,事实上是不得不这么做,比方抉择Bitmap作为像素缓冲,比方接入三方组件的设计等。从某种意义上,抛开束缚议论技术优劣也意义不大。咱们已经借鉴flutter的局部,但Cube最终还是沿着适宜本身场景的技术路线往前走。

常见术语

  • LayoutTree:DomApi通过add、update、remove构建的通过yoga布局的,用来形容节点父子关系,蕴含布局信息的原始树型构造;
  • RenderTree:用来形容绘制节点父子关系,蕴含绘制信息的树型构造,与layoutTree的区别举例:一个layoutNode visible为gone,则该节点不会在RenderTree中呈现;
  • Layer:个别状况下,根节点及其子节点绘制在同一个画布上,定义为一个layer,对应平台层一个view,当子节点有动画属性,或者超出父节点范畴,则须要独立出一个layer;
  • LayerTree:下面提到的layer节点,构建的树型构造,一个layer对应平台层一个view,咱们叫ContainerView;
  • 实体节点:须要独立layer的节点为实体节点;
  • 虚构节点:除了实体节点以外,其余节点均会被绘制在父容器的画布上,这些是虚构节点。

演进过程

调研初期——1.0验证计划的可行性

调研期间验证计划可行性,场景比较简单,以支付宝内敌人动静页面为验证场景,每条状态(一个item/cell)作为一个渲染单元,这里只思考了layerTree只有一个layer的状况,头像、昵称、工夫、配图、“赞”、“赏”,“评”等元素均绘制在root节点对应的layer上,“赞”、“赏”,“评”文本旁边的小图标则作为外接实体组件,通过addSubView增加在rootLayer的View上。

数据模型

如下图所示,依据layoutTree构建RenderTree,但非渲染节点不在renderTree上,layerTree只有一个自绘制layer(rootLayer),和其余自定义组件X,最终除自定义组件外,其余所有节点都绘制在rootLayer上。

渲染流程

bridge线程通过DomApi构建layoutTree,当主线程触发渲染时,主线程依据layoutTree构建RenderTree,构建过程中遇到外接实体组件,创立实例并addSubView,之后切换子线程绘制RenderTree,即rootLayer上的所有虚构节点,绘制实现后切换主线程贴图(bitmap“产物”)。

毛病

  • 不能反对多layer构造
  • 实体view没有复用,也就是敌人动静列表中有多少item/cell,就会有多少“赞”、“赏”,“评”实体组件

但这个调研验证了异步渲染的可行性,在列表滚动时帧率大幅晋升。

产品化期间——2.0反对多layer

后面验证了可行性,在进行产品化设计时,就必须要满足多layer构造了,即理论的一张卡片中,会有一个或几个不同的节点被设置为layer,这些节点及其子节点,别离绘制在不同画布上,供不同的layer渲染。

数据模型

改良之处时layerTree里有个多layer节点,layer节点上面的子虚构节点,将绘制在该layer的bitmap“产物”上。

渲染流程

brige线程构建layoutTree的过程中,每个指令(addNode、removeNode……)都会相应散发到render模块的主线程,render依据指令构建RenderTree,并用指令信息生成task入队,当VSync信号来时,触发工作出队并去重,构建layerTree,不同layer散发到不同draw线程绘制,绘制实现后切主线程贴图(bitmap“产物”)。

毛病

  • 主线程计算量大,可能造成卡顿
  • render节点既蕴含绘制信息,是绘制对象,还蕴含逻辑,例如display:"none"节点疏忽不显示,职责不清晰。

优化期间——3.0舍短取长

下面能够看到renderTree的构建以及layerTree的构建,都是在UI线程,在节点数比拟多活简单的状况下会造成UI的卡顿,为了谋求极致滚动帧率,尽可能减少主线程计算内容,优化3.0版本将renderObject构建layer、以及计算节点变更导致的绘制影响范畴,的局部改在子线程实现,造成了当初线上运行的版本。

数据模型

新增了PaintTree这个构造,它挂载在Layer节点上,款式和属性值从RenderTree拷贝而来,但不波及任何逻辑解决,单纯的是一个绘制对象,每个绘制工作只绘制paintTree上的paint节点,与layerTree和renderTree没有并发问题。

渲染流程

layout线程构建layoutTree,切换到render线程构建renderTree,当平台层触发渲染,切换到renderTree构建layerTree,并计算影响范畴等,切换到主线程将layer对应的实体化View增加在容器View上,生成绘制工作在paint线程执行,绘制完结后切换主线程贴图(bitmap产物)。

毛病

  • render线程忙碌时造成的闪白率升高

以上就是cube渲染从诞生到当初线上计划的演进,目前在支付宝端内卡片状态接入业务超过20+,线上运行的卡片模版个数达到500多个,显示PV过百亿,禁受住了各业务方的考验。

但在技术支持中也发现了一些问题,例如渲染工作过多时,render线程阻塞排队,不能及时生产导致白屏概率变大,最近cube也在持续钻研优化计划。

存在的问题

两端一致性问题

  • cube目前的绘制api,采纳的零碎平台层提供的CanvasApi(iOS是CoreGraphics),这就导致了两个平台在绘制点线面的细节上必须两端人工代码对齐,否则就会产生成果差别,当新增一些feature,例如反对点划线,须要两个平台各自实现DrawDottedLine接口,但这个问题,cube团队正调研自绘制,即应用skia api将绘制接口下沉到c++,实现跨平台自绘制;
  • 文本也是容易产生差别的一个点,利用平台层api对文本进行布局,在绘制时调用布局的api进行绘制,因而可能会产品平台差别,但cube团队目前曾经在Cube小程序上把文本布局,布局算法下沉在c++层,不依赖平台api,实现双平台统一;限于内存/性能的束缚尚未在Cube卡片上利用。

闪白问题

因为滚动采纳的异步渲染,所以必然会产生主线程卡片曾经上屏,异步绘制还未实现造成的闪白问题,线程切换有老本,这个闪白实践上肯定存在,只是工夫长短问题,cube团队致力于进步渲染效率,将线程切换带来的损耗降到最低,使用户在列表滚动中体验晋升。

将来布局

针对目前已知的问题,cube团队致力于继续优化,次要优化点包含但不限于以下:

  • 渲染快照,进步冷启的渲染效率,缩小闪白工夫;
  • 渲染策略,例如预渲染、同异步绘制自适应、线程模型优化、组件缓存和预加载等,缩小闪白率,晋升渲染效率;
  • 用于Cube卡片的yoga布局引擎优化,晋升layout布局效率;
  • skia自绘制实现,实现双端一致性;

cube的渲染技术的利用蕴含卡片和小程序两种技术状态,场景包含支付宝端内、端外、IOT等多样化场景,团队成员将继续在渲染性能、用户体验、以及工具链等方向继续发力,致力把产品打磨好,把开发者服务好,成长为具备竞争力的跨平台动态化渲染计划。

关注【阿里巴巴挪动技术】,阿里前沿挪动干货&实际给你思考!