作者:zhiqiang、sunfei、wanglei,华为软件开发工程师
UI 框架简介以及业界发展趋势
UI,即用户界面,次要蕴含视觉(比方图像、文字、动画等可视化内容)以及交互(比方按钮点击、列表滑动、图片缩放等用户操作)。UI框架,则是为开发UI而提供的基础设施,比方视图布局,UI组件,事件响应机制等。
从操作系统平台反对形式来看,UI框架个别可分为原生UI框架和跨平台UI框架两种。
1.原生UI框架。这个个别是指操作系统自带的UI框架,典型的例子包含iOS的UI Kit,Android的View框架等。这些UI框架和操作系统深度绑定,个别只能运行在相应的操作系统上。性能,性能,开发调测等方面和相应的操作系统联合较好。
2.跨平台UI框架。这个个别是指能够在不同的平台(OS)上运行的独立的UI框架。典型例子包含HTML5以及基于HTML5延长进去的前端框架React Native, 以及Google 的Flutter等。 跨平台UI框架的指标是代码只需一次编写,通过大量批改甚至不批改,能够部署到不同的操作系统平台上。当然,实现跨平台也是有代价的,因为不同平台存在差异性(比方UI的出现形式差别,API差别等等),导致UI框架自身的架构实现,以及和不同平台的交融都有不小的挑战。
从编程形式上来看,UI框架个别可分为命令式UI框架和申明式UI框架两种:
1.命令式UI框架。过程导向 - 通知“机器”具体步骤,命令“机器”依照指定步骤去做。比方Android原生UI框架(View框架)或iOS的UIKit,提供了一系列的API让开发者间接操控UI组件-比方定位到某个指定UI组件,进行属性变更等。这种形式的长处是开发者能够管制具体的实现门路,经验丰富的开发者可能写出较为高效的实现。不过这种状况下,开发者需理解大量的API细节并指定好具体的执行门路,开发门槛较高。具体的实现成果上,也高度依赖开发者自身的开发技能。另外,因为和具体实现绑定较紧,在跨设施状况下,灵活性和扩展性绝对无限。
2.申明式UI框架。后果导向 - 通知“机器”你须要什么,机器负责怎么去做。比方Web前端框架Vue,或iOS的SwiftUI等,框架会依据申明式语法的形容,渲染出相应的UI,同时联合相应编程模型,框架会依据数据的变动来自动更新相应的UI。
这种形式的长处是开发者只需形容好后果,相应的实现和优化由框架来解决。另外,因为后果形容和具体实现拆散,实现形式绝对灵便同时容易扩大。不过这种状况下,对框架的要求较高,须要框架有齐备的直观的形容能力并可能针对相应的形容信息实现高效的解决。
UI框架是利用开发的外围组成部分。纵观业界UI框架,其次要发展趋势体现为:
1.从命令式UI往申明式UI倒退
比方iOS中的UIKit到SwiftUI, Android中的View到Jetpack Compose。
这样能够实现更加直观便捷的UI开发。
2.UI框架和语言运行时深度交融
SwiftUI,Jetpack Compose, Flutter都利用了各自的语言个性 - 比方在UI形容方面,SwiftUI中的Swift语言,Jetpack Compose中的Kotlin语言都精简了UI形容语法;在性能方面, Swift通过引入轻量化构造体等语言个性更好的实现内存疾速调配和开释,Flutter中Dart语言则在运行时专门针对小对象内存治理做相应优化等。
3.跨平台(OS)能力
跨平台(OS)能力能够让一套代码复用到不同的OS上,次要是为了晋升开发效率,升高开发成本。不过这外面也有一系列的挑战,比方运行在不同平台上的性能问题,能力和渲染成果的一致性问题等。业界在这方面也是一直的演进,次要有几种形式:
1.JS/Web计划,比方HTML5利用JS/Web的标准化生态,通过相应的Web引擎实现跨平台指标;
2.JS+Native混合形式,比方React Native、Weex等,联合JS桥接到原生UI组件的形式实现了一套利用代码可能运行到不同OS上;
3.平台无关的UI自绘制能力+新的语言,比方Flutter,整个UI基于底层画布由框架层来绘制,同时联合Dart语言实现残缺的UI框架。Flutter从设计之初就是将跨平台能力作为重要的竞争力去吸引更多的开发者。
另外,乏味的是,局部原生开发框架也开始往跨平台演进。比方,Android原生的开发框架Jetpack Compose也开始将跨OS反对作为其中的指标,打算将Compose拓展到桌面平台,比方Windows,MacOS等。
此外,随着智能设施的遍及,多设施场景下,设施的状态差别(屏幕大小、分辨率,形态, 交互模式等),以及设施的能力差异(从百K级内存到G级内存设施等),以及利用须要在不同设施间协同,这些都对UI框架以及利用开发带来了新的挑战。
ACE UI框架是什么
ACE全称是Ability Cross-platform Environment (元能力跨平台执行环境)。是华为设计的利用在HarmonyOS上的UI框架。ACE UI框架联合HarmonyOS的根底运行单元Ability,语言和运行时,以及各种平台(OS)能力API等独特形成HarmonyOS利用开发的根底,实现了跨设施散布式调度,以及原子化服务免装置等能力。
ACE提供两种开发语言以供不同开发者进行抉择,别离为Java语言和JavaScript语言,其中Java仅反对在内存较大的设施上应用如大屏、手机、平板等设施应用,而JavaScript反对在百K级到G级设施上应用。
在多设施场景下,因为不同的设施状态以及设施能力的微小差别,目前业界还没有任何一个UI框架可能较好的解决相应的问题。
所以ACE UI框架的整体设计思路是:
1.建设分层机制,引入高效的UI根底后端,并可能与OS平台解耦,造成统一化的UI体验
2.通过多前端的形式扩大利用生态,并联合申明式UI,在开发效率上继续演进
3.框架层对立联合语言以及运行时,分布式,组件化设计等,围绕跨设施,进一步晋升体验
ACE将利用的UI界面进行解析,通过创立后端具体UI组件、进行布局计算、资源加载等解决后生成具体绘制指令,并将绘制命令发送给渲染引擎,渲染引擎将绘制指令转换为具体屏幕像素,最终通过显示设施将利用程序转换为可见的界面成果展现给用户。
ACE UI框架的整体架构如下图所示,次要由前端框架层、桥接层、引擎层和平台形象层四大局部组成,上面咱们一一介绍。
1.前端框架层
该层次要包含相应的开发范式(比方支流的类Web开发范式),组件/API,以及编程模型MVVM(Model-View-ViewModel)。其中Model是数据模型层,代表了从数据源读取到的数据;View是视图UI层,通过肯定的模式把零碎中的数据向用户出现进去;ViewModel: 视图模型层,是数据和视图之间的桥梁。它双向绑定了视图和数据,使得数据的变更可能及时在视图上出现,用户在视图上的批改也可能及时传递给后盾数据,从而实现数据驱动的UI主动变更。
开发范式能够扩大,来反对生态倒退。不同的开发范式能够对立适配到底层的引擎层
2.桥接层
该层次要是作为一个中间层,实现前端开发范式到底层引擎(包含UI后端,语言&运行时)的对接
3.引擎层
该层次要蕴含两局部:UI后端引擎和语言执行引擎。
1.由C++构建的UI后端引擎,包含UI组件、布局视图、动画事件、自绘制渲染管线和渲染引擎 。
在渲染方面,咱们尽可能把这部分组件设计得小而灵便。这样的设计,为不同前端框架提供灵便的UI能力,这部分通过C++组件组合而成。通过底层组件的按需组合,布局计算和渲染并行化,并联合下层开发范式实现了视图变动最小化的部分更新机制,从而实现高效的UI渲染。
除此之外,引擎层还提供了组件的渲染管线、动画、主题、事件处理等根底能力。目前复用了Flutter引擎提供根底的图形渲染能力、字体治理、文字排版等能力,底层应用Skia或其余图形库实现,并通过OpenGL实现GPU硬件渲染减速
在多设施UI适配方面,通过多种原子化布局能力(主动折行、暗藏、等比缩放等),多态UI控件(形容对立,体现形式多样),以及对立交互框架(不同的交互方式归一到对立的事件处理)来满足不同设施的状态差异化需要。
另外,引擎层也蕴含了能力扩大基础设施,来实现自定义组件以及零碎API的能力扩大
2.语言&运行时执行引擎。可依据须要切换到不同的运行时执行引擎,满足不同设施的能力差异化需要
4.平台形象层
该层次要是通过平台形象,将平台依赖聚焦到底层画布,通用线程以及事件机制等多数必要的接口上,为跨平台打造了相应的基础设施,并可能实现统一化UI渲染体验。
相应的,配套的开发者工具(HUAWEI DevEco Studio)联合ACE UI的跨平台渲染基础设施,以及自适应渲染,可做到和设施比拟统一的渲染体验以及多设施上的UI实时预览。
另外,ACE UI框架还设计了可伸缩的架构,即前端框架、语言运行时、UI后端等都做理解耦,能够有不同的实现。这样就具备可部署到百K级内存的轻量级设施的能力,如下所示:
在ACE UI的轻量化实现中,通过前端框架外围下沉C++化,减小JS局部的内存占用,应用C++进行更为严格的内存调配与治理,并且采纳更为轻量的JS引擎,UI局部采纳轻量的UIKit并联合轻量图形引擎,达到内存十分轻量占用的指标。接口能力保障是全量能力的子集,这样能够保障轻量化设施上可执行的利用,能够在更高等级的设施上执行,而无需从新开发。这也就是采纳ACE JS开发范式的劣势所在,采纳对立的开发范式进行利用开发后,开发者无需关怀具体运行时的前端框架、JS引擎与后端UI组件,依据运行平台不同,采纳最佳的模块,保障了利用在不同平台都可具备最佳的运行性能。不过因为轻量级设施上的资源限度, 所反对的API 能力绝对无限,但公共局部的API是齐全共通的。
综上所述,ACE UI框架具备如下特点:
1.反对支流的语言生态 – JavaScript
2.反对类Web开发范式, MVVM机制。并在架构上可反对多前端开发范式,进一步简化开发
3.通过对立的UI后端,实现高性能以及跨平台统一化的渲染体验
4.通过多态UI、原子化布局、对立交互,以及可伸缩的运行时设计,进一步升高不同设施状态下的UI开发门槛,并可能通过对立的开发范式,实现一套代码跨设施部署(笼罩百K级到G级内存设施)
ACE UI框架渲染流程解析
接下来咱们通过一个手机侧ACE JS利用渲染流程的残缺流程来介绍ACE UI框架的具体渲染技术。
1)线程模型
ACE JS利用启动时会创立一系列线程,造成独立的线程模型,以实现高性能的渲染流程。
每个ACE JS利用的过程,蕴含惟一一个Platform线程和若干后盾线程组成的异步工作线程池:
•Platform线程:以后平台的主线程,也就是利用的主线程,次要负责平台层的交互、利用生命周期以及窗口环境的创立
•后盾线程池:一系列后台任务,用于一些低优先级的可并行异步工作,如网络申请、Asset资源加载等。除此之外,每个实例还包含一系列专有线程
•JS线程:JS前端框架的执行线程,利用的JS逻辑以及利用UI界面的解析构建都在该线程执行
•UI线程:引擎的外围线程,组件树的构建以及整个渲染管线的外围逻辑都在该线程:包含渲染树的构建、布局、绘制以及动画调度
•GPU线程:古代的渲染引擎,为了充分发挥硬件性能,都反对GPU硬件加速,在该线程上,会通过零碎的窗口句柄,创立GPU减速的OpenGL环境,负责将整个渲染树的内容光栅化,间接将每一帧的内容渲染合成到该窗口的Surface上并送显
•IO线程:次要为了异步的文件IO读写,同时该线程会创立一个离屏的GL环境,这个环境和 GPU线程的GL环境是同一个共享组,能够共享资源,图片资源解码的内容可间接在该线程上传生成GPU纹理,实现更高效的图片渲染
每个线程的作用,在后续的渲染流程中还会进一步提到。
2)前端脚本解析
ACE UI框架反对不同的开发范式,能够对接到不同的前端框架上。
以类Web开发范式为例,开发者开发的利用,通过开发工具链的编译,会生成引擎可执行的Bundle文件。利用启动时,会将Bundle文件在JS线程上进行加载,并且将该内容作为输出,供JS引擎进行解析执行,最终生成前端组件的结构化形容,并建设数据绑定关系。例如蕴含若干简略文本的利用会生成相似下图的树形构造,每个组件节点会蕴含该节点的属性及款式信息。
3)渲染管线构建
如上图,前端框架的解析后,依据具体的组件标准定义向前端框架对接层申请创立ACE渲染引擎提供的组件。
前端框架对接层通过ACE引擎层提供的Component组件实现前端组件定义的能力。Component是一个由C++实现的UI组件的申明式形容,形容了UI组件的属性及款式,用于生成组件的实体元素。每一个前端组件会对接到一个Composed Component,示意一个组合型的UI组件,通过不同的子Component组合,结构出前端对应的Composed组件。每个Composed组件是前后端对接的一个根底的更新单位。
以下面的前端组件树为例,每个节点会应用一组Composed组件进行组合形容,对应关系如下图,该对应关系只是一个示例,理论场景的对应关系可能会更简单。
有了每个前端节点对应的Component,就造成了一个实现Page的形容构造,告诉渲染管线挂载新的页面。
在Page挂载之前,渲染管线曾经提前创立了几个要害的外围构造,Element树和Render树:
Element树,Element是Component的实例,示意一个具体的组件节点,它造成的Element树负责维持界面在整个运行时的树形构造,不便计算部分更新算法。另外对于一些简单的组件,在该数据结构上会实现一些对子组件逻辑上的治理。
Render树,对于每个可显示的Element都会为其创立对应的RenderNode,它负责一个节点的显示信息,它造成的Render树保护着整个界面的渲染须要用到的信息,包含它的地位、大小、绘制命令等,界面后续的布局、绘制都是在Render树上进行的。
当利用启动时,最后造成的Element树只有几个根底的几节点,个别包含root、overlay、stage,别离作用如下:
RootElement:Element树的根节点,仅仅负责全局背景色的绘制
OverlayElement:一个全局的悬浮层容器,用于弹窗等全局绘制场景的治理
StageElement:一个Stack容器,作为全局的“舞台”,每个加载实现的页面都要挂载到这个“舞台”下,它治理利用的多个页面之间的转场动效等。
在Element树创立的过程中,也会同步的把Render树也创立起来,初始状态如下图:
以后端框架对接层告诉渲染管线筹备好了页面,在下一个帧同步信号(VSync)到来时,就会在渲染管线上进行页面的挂载,具体流程就是通过Component来实例化生成Element的过程,创立胜利的Element同步创立对应的RenderNode:
如上图所示,指标要将整个Page的Component形容挂载到StageElement上,如果以后Stage下还未有任何Element节点,就会递归一一节点生成Component对应的Element节点。对于组合类型的ComposedElement,则同时会把Element的援用记录到一个Composed Map中,不便后续更新时疾速查找。对于可见类型的容器节点或渲染节点,则会创立对应的RenderNode,并挂在Render树上。
当生成了以后页面的Element树和Render树,页面渲染构建的残缺过程就完结了。
4)布局绘制机制
接下来就进入了布局和绘制的阶段,布局和绘制都是在Render树上进行的。每个RenderNode都会实现本人的布局算法和绘制办法。
布局
布局的过程就是通过各类布局的算法计算出每个RenderNode在绝对空间上的实在大小和地位。
如下图所示,当某个节点的内容发生变化时,就会标记本人为needLayout,并始终向上标记到布局边界(ReLayout Boundary),布局边界是从新布局的一个范畴标记,个别状况下,如果一个节点的布局参数信息(LayoutParam)是强束缚的,例如它布局冀望的最大尺寸和最小尺寸是雷同的,那么它就能够作为一个布局边界。布局是个深度优先遍历的过程。从布局边界开始,父节点自顶向下将LayoutParam传给子节点,子节点自底向上据此计算失去尺寸大小和地位,
对于每个节点来说,布局分为三个步骤:
1.以后节点递归调用子节点的layout办法,并传递布局的参数信息(LayoutParam),蕴含了布局冀望的最大尺寸和最小尺寸等
2.子节点依据布局参数信息,应用本人定义的布局算法来计算本人的尺寸大小
3.以后节点获取子节点布局后的大小,再依据本人的布局算法来计算每个子节点的地位信息,并将绝对地位设置给子节点保留
根据上述的流程,一次布局遍历实现后,每个节点的大小和地位就都计算出来了,能够进行下一步的绘制。
绘制
同布局一样,绘制也是一个深度遍历的过程,遍历调用每个RenderNode的Paint办法,此时的绘制只是依据布局算进去的大小和地位,在以后绘制的上下文记录每个节点的绘制命令。
为什么是记录命令,而不是间接绘制渲染呢?在古代的渲染引擎中,为了充沛应用GPU硬件加速的能力,个别都会应用DisplayList的机制,绘制过程中仅仅将绘制的命令记录下来,在GPU渲染的时候对立转成OpenGL的指令执行,能最大限度的进步图形的解决效率。所以在下面提到的绘制上下文中,会提供一个能够记录绘制命令的画布(Canvas)。每一个独立的绘制上下文能够看作是一个图层。
为了进步性能,这里引入了图层(Layer)的概念。通常绘制会将渲染内容分为多个层进行减速。对于会频繁变动的内容,将其独自创立一个图层,那么这个独立图层的频繁刷新就不用导致其余内容从新绘制,从而达到晋升性能并缩小功耗的成果,同时还能够反对GPU缓存等优化。每个RenderNode都能够决定本人是否须要独自分层。
如下图所示,绘制流程会从须要绘制的节点中,筛选最近的且须要分层的节点开始,自顶向下的执行每个节点的Paint办法。
对每个节点,绘制分为四个步骤:
1.如果以后节点须要分层,那么须要创立一个新的绘制上下文,并提供能够记录绘制命令的画布
2.在以后的画布上记录背景的绘制命令
3.递归调用子节点的绘制办法,记录子节点的绘制命令
4.在以后的画布上记录前景的绘制命令
一次残缺的绘制流程完结后,咱们会失去一棵残缺的Layer树,Layer树上蕴含了这一帧残缺的绘制信息:包含每一层的地位、transform信息、Clip信息、以及每个元素的绘制命令。下一步就要通过光栅化和合成的过程,将这一帧的内容显示到界面。
5)光栅化合成机制
在下面的绘制流程完结后,会告诉GPU线程开始进行合成的流程。
如上图所示,UI线程(UI Thread)在渲染管线中的输入是LayerTree,它相当于一个生产者,将生产的LayerTree增加到渲染队列中。GPU线程(GPU Thread)的合成器(Compositor)相当于消费者,每个新的渲染周期中,合成器会从渲染队列中获取一个LayerTree进行合成生产。
对于须要缓存的Layer,还要执行光栅化生成GPU纹理,所谓光栅化就是将Layer外面记录的命令进行回放,生成每个实体的像素的过程。像素是存储在纹理的图形内存中。
合成器会从零碎的窗口中获取以后的Surface,将每个Layer生成的纹理进行合成,最终合成到以后Surface的图形内存(Graphic Buffer)中。这块内存中存储的就是以后帧的渲染后果内容。最终还须要将渲染后果提交到零碎合成器中合成显示。零碎的合成过程如下图所示:
当GPU线程的合成器实现一帧的合成后,会进行一次SwapBuffer的操作,将生成的Graphic Buffer提交到与零碎合成器建设的帧缓冲队列(Buffer Queue)中。零碎合成器会从各个生产端获取最新的内容进行最终的合成,以上图为例,零碎合成器会将以后利用的内容和零碎其它的显示内容,例如System UI的状态栏、导航栏,进行一次合成,最终写入到屏幕对应的帧缓冲区(Frame Buffer)中。液晶屏的驱动就会从缓冲区读取内容进行屏幕的刷新,最终将内容显示到屏幕上。
6)部分更新机制
通过下面1~5的流程,实现了首次残缺的渲染的流程,在后续的运行中,例如用户输出、动画、数据扭转都有可能造成页面的刷新,如果只是局部元素产生了变动,并不需要全局的刷新,只须要启动部分更新即可。那么部分更新是怎么做到的?上面咱们介绍一下部分 更新的流程。
以上图为例,JS在代码中更新了数据,通过数据绑定模块会主动触发前端组件属性的更新,而后通过JS引擎异步发动更新属性的申请。前端组件会依据变更的属性,构建一组新的Composed的补丁(Patch),作为渲染管线更新的输出。
如上图所示,在下一个VSync到来时,渲染管线会在UI线程开始更新的流程。通过Composed补丁的Id,在ComposedMap中查问到对应的ComposedElement在Element树上的地位。通过补丁对Element树进行更新。以ComposedElement为起始,逐层进行比照,如果节点类型统一则间接更新对应属性和对应的RenderNode,如果不统一则从新创立新的Element和RenderNode。并将相干的RenderNode标记为needLayout和needRender。
如上图所示,依据标记须要从新布局和从新渲染的RenderNode,从最近的布局边界和绘制图层进行布局和绘制的流程,生成新的Layer树,只须要从新生成变更RenderNode对应的Layer即可。
如上图所示,接下来,依据刷新后的Layer树作为输出,在GPU线程进行光栅化和合成。对于曾经缓存的Layer则不须要从新光栅化,合成器只须要将已缓存的Layer和未缓存或更新的Layer从新合成即可。最终通过零碎合成器的合成,就会将新一帧的内容显示。
以上就是一个ACE JS利用的渲染及更新的流程。最初,通过两张流程图回顾一下整体的流程:
理解完ACE JS利用的渲染及更新的流程,如果大家想理解更多对于HarmonyOS UI框架如何解决设施状态差别带来的开发挑战和利用示例,可参考咱们之前推出的内容:解密HarmonyOS UI框架:
https://mp.weixin.qq.com/s/0R...。
ACE UI框架目前的成熟度以及演进
截至目前,ACE UI框架已商用落地了华为静止手表,华为智能手表,华为智慧屏,华为手机,华为平板等一系列产品。应用场景包含日历、出行、健身、实用工具等各类利用,手机-设施碰一碰全品类的利用,以及往年六月份公布的HarmonyOS中各类的服务卡片-图库、相机等。另外,在开发调测方面,开发者工具(HUAWEI DevEco Studio)中也集成了ACE UI框架,反对在PC端(MacOS,Windows)上的开发调测,实时预览(包含实时多设施预览,组件级预览,双向预览等),实现了在PC上和设施上统一的渲染体验。
将来,面向开发者的极简开发,面向消费者的晦涩酷炫的体验,以及可能高效在不同设施不同平台上部署,ACE UI框架会持续沿着精简开发和高性能两个方面演进,联合语言更进一步简化开发范式,联合运行时在跨语言交互,类型优化等方面进一步加强性能体验,联合分布式能力将编程模型从MVVM演进到分布式MVVM(Distributed Model-View-ViewModel)等。采纳类自然语言的申明式UI形容进行界面搭建,编程语言也进一步凋谢,将来思考向TS进行演进,从动效、布局和性能方面进一步晋升用户应用体验。
当然,利用生态还会波及更多的方面,比方三方插件的凋敝,跨OS平台的扩大,更具翻新的分布式体验等等。ACE UI框架还很年老,期待和泛滥开发者一起,重点围绕着多设施组成的超级终端的新兴场景,一直打磨欠缺,独特构建当先的利用体验和生态!
目前HarmonyOS的线上开发体验已上架,欢送大家在线体验。ACE JS框架曾经进驻开源社区,欢送关注与共建,期待诸位一起共建咱们的开发框架,有开发过程中的疑难和对HarmonyOS开发的好的倡议欢送登录论坛,咱们一起探讨。论坛链接可拜访:
https://developer.huawei.com/...,
开源社区:https://gitee.com/openharmony...