深入解析ui框架简介以及业界发展趋势分析(深入解析UI框架简介以及业界发展趋势)

作者:俞志强、孙飞、王雷,华为软件架构工程师UI框架简介及行业发展趋势

UI,即用户界面,主要包括视觉(如图片、文字、动画等视觉内容)和交互(如按钮点击、列表滑动、图片缩放等用户操作)。UI框架是为开发UI提供的基础设施,如视图布局、UI组件、事件响应机制等。

从操作系统平台支持的角度,UI框架一般可以分为原生UI框架和跨平台UI框架。

1.本机UI框架。一般是指操作系统自带的UI框架。典型的例子有iOS的UI Kit,Android的View框架等。这些UI框架与操作系统深度绑定,一般只能在相应的操作系统上运行。功能、性能、开发、调试等各方面都与相应的操作系统结合得很好。

2.跨平台UI框架。一般是指可以在不同平台(OS)上运行的独立UI框架。典型的例子有HTML5,前端框架React Native扩展自HTML5,Google Flutter。跨平台UI框架的目标是代码只需编写一次,并且只需很少或不需要修改就可以部署到不同的操作系统平台。当然,实现跨平台是有代价的。因为不同平台之间的差异(比如UI呈现的差异,API的差异等。),UI框架本身的架构实现以及与不同平台的集成都是不小的挑战。

从编程的角度来看,UI框架一般可以分为命令式UI框架和声明式UI框架:

1.命令式UI框架,面向过程的3354告诉机器具体步骤和命令机器遵循指定的步骤。比如安卓的原生UI框架(View framework)或iOS s UIKit为开发人员提供了一系列API来直接控制UI组件——例如,导航到指定的UI组件并进行属性更改。

这种方式的好处是开发者可以控制具体的实现路径,有经验的开发者可以写出更高效的实现。但这种情况下,开发者需要了解很多API细节,并指定具体的执行路径,所以开发门槛较高。具体实现效果也高度依赖开发者的自身开发技能。此外,由于与特定实现的紧密绑定,在跨设备的情况下,灵活性和可伸缩性相对有限。

2.声明式UI框架,面向结果的3354告诉机器你需要什么和机器负责如何去做。比如Web前端框架Vue,或者iOS的SwiftUI等等。框架将根据声明性语法的描述呈现相应的UI。同时,结合相应的编程模型,框架会根据数据的变化自动更新相应的UI。

这种方式的好处是开发者只需要描述结果,相应的实现和优化由框架处理。此外,由于结果描述与具体实现相分离,实现方式相对灵活,易于扩展。但在这种情况下,对框架的要求较高,需要框架具有完整、直观的描述能力,能够高效地处理相应的描述信息。

UI框架是应用开发的核心组件。纵观行业的UI框架,其主要发展趋势如下:

从命令式用户界面到声明式用户界面

比如iOS中的UIKit到SwiftUI,Android中的View到Jetpack Compose。这样可以实现更加直观便捷的UI开发。

框架和语言运行时是深度集成的。SwiftUI、Jetpack Compose和Flutter都利用了各自的语言特性3354。比如在UI描述方面,SwiftUI中的Swift语言和Jetpack中的Kotlin语言组成了所有简化的UI描述语法;在性能方面,Swift通过引入轻量级结构等语言特性,可以更好地快速分配和释放内存,而Flutter中的Dart语言针对运行时小对象的内存管理进行了优化。跨平台(OS)功能跨平台(OS)功能支持一组

1.JS/Web计划。比如HTML5利用JS/Web标准化生态,通过相应的Web引擎实现跨平台目标;

2.JS原生混合模式。比如React Native,Weex等。结合JS桥接原生UI组件,一套应用代码可以运行在不同的OS上;

3.独立于平台的UI自绘制能力。新语言。比如Flutter,整个UI是框架层基于底层画布绘制的,结合Dart语言实现一个完整的UI框架。Flutter从设计之初就把跨平台能力作为重要竞争力,以吸引更多开发者;

此外,有趣的是,一些原生开发框架开始向跨平台演进。比如Android的原生开发框架Jetpack Compose也开始以跨OS支持为目标,计划将Compose扩展到桌面平台,比如Windows和MacOS。

然而,随着智能设备的普及,在多设备场景下,设备外形的差异(屏幕大小、分辨率、形状、交互方式等。)、器件能力的差异(从100 K级内存器件到G级内存器件等。)以及应用程序需要在不同设备之间进行协调,这给UI框架和应用程序开发带来了新的挑战。

为什么要重新设计一个ACE UI框架?

ACE的全称是能力跨平台环境(元能力跨平台执行环境)。这是一个由华为设计并应用于鸿蒙系统的UI框架。ACE框架结合了鸿蒙系统的基本运行单元能力、语言和运行时,以及各种平台(OS)能力API,形成了鸿蒙系统应用开发的基础,实现了跨设备的分布式调度和免安装原子化服务的能力。

ACE提供了两种开发语言供不同的开发者选择,分别是Java语言和JavaScript语言。其中,Java只支持大内存的设备,如大屏幕、手机、平板等。而JavaScript支持100 K到G级别的设备。

目前主流的UI框架都有各自的缺点。此外,在多设备场景下,由于不同设备形态和能力的巨大差异,业界还没有一个UI框架能更好地解决相应的问题。

而ACE UI框架的整体设计思路是:

1.建立分层机制,引入高效的UI基础后端,与OS平台解耦,形成一致的UI体验。

2.通过多前端拓展应用生态,结合声明式UI,在开发效率上不断进化。

3.框架层统一了语言、运行时、分布式、组件设计等。以进一步增强跨设备的体验。

分析ace应用的UI界面,在后端创建特定的UI组件,通过计算布局、加载资源等过程生成特定的绘制指令,然后将绘制指令发送给渲染引擎,渲染引擎会将绘制指令转换成特定的屏幕像素,最后由显示设备将应用程序转换成可视的界面效果显示给用户。

ACE框架的整体架构如下图所示,主要由前端框架层、桥梁层、引擎层、平台抽象层四部分组成。让让我们逐一介绍。

1前端框架层该层主要包括相应的开发范式(如主流的类Web开发范式)、组件/API、编程模型MVVM(Model-View-ViewModel)。Model是数据模型层,表示从数据源读取的数据;视图UI层,将系统中的数据以一定的形式呈现给用户;ViewModel:视图模型层,是数据和视图之间的桥梁。它双向绑定视图和数据,这样数据的变化可以及时呈现在视图上,而用户视图上的变化也可以及时传递到后台数据,从而实现数据驱动的UI自动变化。

发展模式可以扩展到支持生态发展。不同的开发范例可以统一适用于底层引擎层。

桥接层2

这一层主要作为中间层,实现前端开发范式和底层引擎(包括UI后端和语言运行时)的连接。

3引擎层这一层主要由两部分组成:UI后端引擎和语言执行引擎。

1.由C构建的UI后端引擎,包括UI组件、布局视图、动画事件、自绘渲染管道和renderin

在渲染方面,我们把这部分组件设计的尽量小,尽量灵活。这种设计为不同的前端框架提供了灵活的UI能力,前端框架由C组件组成。通过底层组件的按需组合、布局计算和渲染的并行化,结合上层开发范式,实现视图变化最小的局部更新机制,从而实现高效的UI渲染。

此外,引擎层还提供了组件的渲染管道、动画、主题、事件处理等基础能力。目前复用了Flutter引擎提供的基本图形渲染能力、字体管理、文本排版等能力。底层由Skia或其他图形库实现,GPU硬件渲染由OpenGL加速。

在多设备UI适配方面,各种原子化布局能力(自动折线、隐藏、比例缩放等。)、多态UI控件(统一描述和多种表现形式)、统一交互框架(不同交互方式统一和统一事件处理)来满足不同设备的形态差异化需求。

此外,引擎层还包含能力扩展基础设施,实现自定义组件和系统API的能力扩展。

2.语言运行时执行引擎。可以根据需要切换到不同的运行时执行引擎,满足不同设备的能力差异化需求。

4平台抽象层

该层主要通过平台抽象,将平台依赖集中在底层画布、通用线程、事件机制等少数必要接口上,为跨平台搭建相应的基础设施,可以实现统一的UI渲染体验。

相应的,配套的开发者工具(华为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框架具有以下特点:

支持主流语言生态——JavaScript;

支持Web式开发模式,MVVM机制。并且可以在架构上支持多前端开发范式,进一步简化开发;

通过统一的UI后端,实现高性能、跨平台的一致渲染体验;

通过多态UI、原子布局、统一交互、可扩展的运行时设计,进一步降低不同设备形态下的UI开发门槛,通过统一的开发范式,实现一套代码跨设备(覆盖100 K到G的内存设备)部署。

ACE框架的渲染过程分析

接下来我们通过一个手机端ACE JS应用渲染流程的完整过程来介绍ACE UI框架的具体渲染技术。

1个线程模型

ACE应用启动时,会创建一系列线程,形成独立的线程模型,从而实现高性能的渲染过程。

每个ACE JS应用进程都包含一个异步任务线程池,只由一个平台线程和几个后台线程组成:

平台线程:当前平台的主线程,即主线程

后台线程池:一系列后台任务,用于一些低优先级的并行异步任务,如网络请求、资产资源加载等。此外,每个实例都包括一系列专有线程。

JS线程:JS前端框架的执行线程,应用JS逻辑和应用UI界面的分析构造都在这个线程中执行。

UI线程:引擎的核心线程,组件树的构建,整个渲染流水线的核心逻辑都在这个线程中:包括渲染树的构建,布局,绘制,动画调度。

GPU线程:现代渲染引擎,为了充分发挥硬件性能,都支持GPU硬件加速。在这个线程上,会通过系统的窗口句柄创建GPU加速的OpenGL环境,窗口句柄负责将整个渲染树的内容进行栅格化,直接将每一帧的内容渲染合成到窗口的表面并发送显示。

IO线程:主要用于异步文件IO读写,同时这个线程会创建一个离屏GL环境,和GPU线程的GL环境是同一个共享组,可以共享资源。图像资源解码后的内容可以直接上传到这个线程生成GPU纹理,从而实现更高效的图像渲染。

每个线程的作用将在后续的渲染过程中进一步提及。2前端脚本解析

ACE框架支持不同的开发范式,可以连接不同的前端框架。

以类Web开发范式为例,开发者开发的应用会通过开发工具链的编译生成引擎的可执行捆绑文件。当应用程序启动时,捆绑文件将被加载到JS线程上,内容将被用作JS引擎解析和执行的输入。

生成最终前端组件的结构化描述,建立数据绑定关系。比如一个包含一些简单文本的应用,会生成一个如下图这样的树形结构,每个组件节点都会包含那个节点的属性和样式信息。

3抹灰管道施工

如上图所示,前端框架解析后,根据具体的组件规范定义,请求前端框架对接层创建ACE渲染引擎提供的组件。

前端对接层通过ACE引擎层提供的组件构件实现前端组件的定义。Component是C实现的UI组件的声明性描述,描述UI组件的属性和样式,用于生成组件的实体元素。每个前端组件都连接到一个组合组件,该组合组件表示一个组合的UI组件。通过组合不同的子组件,构建相应的前端组合组件。每个组合组件都是前端对接的基本更新单元。

以上面的前端组件树为例。每个节点将使用一组组合组件进行组合描述。对应关系如下图所示。这个对应关系只是一个例子,实际场景中的对应关系可能更复杂。

组件对应每个前端节点,形成完整页面的描述结构,并通知渲染管道挂载新页面。

在页面挂载之前,渲染管道已经提前创建了几个关键的核心结构,元素树和渲染树:

元素树是组件的一个实例,代表一个特定的组件节点。它形成的元素树负责在整个运行时维护接口的树形结构,方便计算局部更新算法。对于其他复杂的组件,子组件的一些逻辑管理将在这个数据结构上实现。

渲染树,对于每个可显示的元素,都会创建一个对应的RenderNode,负责一个节点的显示信息。它形成的渲染树维护了整个界面渲染所需的信息,包括它的位置、大小、绘制命令等。后续的界面布局和绘制都是在渲染树上进行的。

当应用程序启动时,最初形成的元素树只有几个基本节点,一般包括根、覆盖和阶段,它们的功能如下:

房间:元素树的根节点,只负责绘制全局背景色。

重叠

当前结束帧的停靠层通知渲染管道页面准备就绪,当下一个帧同步信号(VSync)到达时,页面将被挂载到渲染管道上。具体流程是通过组件实例化生成元素的流程,创建一个成功的元素同步来创建对应的RenderNode:

如上图所示,目标想要在StageElement上挂载整个页面的组件描述。如果当前阶段下没有元素节点,它将逐个递归生成组件对应的元素节点。

对于组合类型的ComposedElement,元素的引用会同时记录在一个合成图中,方便后续更新时快速查找。对于可见类型的容器节点或渲染节点,相应的RenderNode将被创建并挂在渲染树上。

当当前页面的元素树和渲染树生成后,页面渲染和构建的完整过程就结束了。4布局绘制机制

接下来,我们进入布局和绘图阶段,这些都是在渲染树上完成的。每个RenderNode将实现自己的布局算法和绘制方法。

总体布局

布局过程就是通过各种布局算法计算出每个RenderNode在相对空间中的真实大小和位置。

如下图所示,当一个节点的内容发生变化时,它会将自己标记为needLayout,并一直标记到布局边界,这是一个重新布局的范围标记。通常,如果节点的LayoutParameter信息(布局参数)受到强烈约束,例如,其预期的最大大小和最小大小相同,则可以将其用作布局边界。

布局是一个深度优先的遍历过程。从布局边界开始,父节点自顶向下将LayoutParam传递给子节点,子节点自底向上计算大小和位置。

对于每个节点,布局分为三个步骤:

(1)当前节点递归调用子节点的布局方法,传递布局参数信息(LayoutParam),包括布局的预期最大尺寸和最小尺寸等。

根据布局参数信息,子节点使用自己定义的布局算法计算自己的尺寸;

当前节点获得布局后子节点的大小,然后根据自己的布局算法计算每个子节点的位置信息,并设置相对于子节点的位置进行保存。

按照上面的流程,完成一次布局遍历后,计算出每个节点的大小和位置,就可以进行下一次绘图了。

和布局一样,画图也是一个深度遍历的过程。遍历调用每个RenderNode的Paint方法。此时绘制只是基于layout计算的大小和位置,每个节点的绘制命令都记录在当前的绘制上下文中。

为什么要记录命令而不是直接画图渲染?在现代渲染引擎中,为了充分利用GPU硬件加速能力,一般采用DisplayList的机制,在渲染过程中只记录渲染命令。

GPU渲染时,指令统一转换成OpenGL来执行,可以最大化图形的处理效率。因此,在上述绘图上下文中,将提供一个画布来记录绘图命令。每个独立的绘图上下文可以被视为一个层。

为了提高性能,这里引入了层的概念。画图通常会把渲染的内容分成多个图层来加速。对于会频繁变化的内容,创建一个单独的层,那么这个独立层的频繁刷新不会导致其他内容的重绘,从而达到提升性能降低功耗的效果,同时可以支持GPU缓存等优化。每个RenderNode可以决定是否需要单独分层。

如下图所示,绘制过程会从需要分层的节点中选择需要绘制的最近的节点,自上而下执行每个节点的Paint方法。

对于每个节点,绘制分为四个步骤:

如果当前节点需要分层,需要新建一个绘图上下文,提供一个可以记录绘图命令的画布;

在当前画布上记录背景绘制命令;

递归调用子节点的绘制方法,记录子节点的绘制命令;

在当前画布上记录前景的绘制命令。

一个完整的绘制过程之后,我们会得到一个完整的图层树,里面包含了这一帧完整的绘制信息:包括每一层的位置,变换信息,剪辑信息,以及每一个元素的绘制命令。下一步是通过栅格化和合成的过程将该帧的内容显示给界面。

5光栅化合成机制

以上绘制过程完成后,会通知GPU线程开始合成过程。

渲染管道中UI线程的输出是LayerTree,相当于一个生产者,将产生的LayerTree加入渲染队列。GPU线程的合成器相当于消费者。在每个新的渲染周期中,合成器将从渲染队列中获取一个LayerTree用于合成消耗。

对于需要缓存的图层,还需要进行栅格化,生成GPU纹理。所谓光栅化,就是回放图层中记录的命令,生成每个实体的像素的过程。像素存储在纹理的图形内存中。

合成器会从系统的窗口中获取当前表面,合成各层生成的纹理,最后合成到当前表面的图形缓冲区中。该存储器存储当前帧的渲染结果。最后,渲染结果需要提交给系统合成器进行合成显示。该体系的合成过程如下图所示:

当GPU线程的合成器完成一帧的合成时,会执行一次SwapBuffer操作,将生成的图形缓冲区提交到系统合成器建立的帧缓冲队列中。合成器将从每个制作端获取最新内容进行最终合成。

合成器会将当前的应用内容和系统的其他显示内容进行合成,比如系统UI的状态栏、导航栏等,最后写入屏幕对应的帧缓冲区。LCD驱动程序将从缓冲区读取内容以刷新屏幕,并最终在屏幕上显示内容。

6本地更新机制

经过以上1~5个过程,第一个完整的渲染过程就完成了。在随后的操作中,例如用户输入、动画和数据改变,页面可能被刷新。如果只改变了一些元素,则不需要全局刷新,而只需要开始局部更新。那么本地更新是怎么做的呢?让下面介绍部分更新的过程。

JS更新代码中的数据,数据绑定模块会自动触发前端组件属性的更新,然后通过JS引擎异步发起更新属性的请求。根据改变后的属性,前端组件将构建一组新的合成补丁,作为渲染管道更新的输入。

当下一个VSync到来时,渲染管道将在UI线程中启动更新过程。根据合成补丁的Id,可以在ComposedMap中找到相应ComposedElement在元素树上的位置。通过补丁更新元素树。

从ComposedElement开始,逐层对比。如果节点类型一致,则直接更新对应的属性和对应的rendernodes否则,将重新创建新元素和rendernodes。并将相关的RenderNode标记为needLayout和needRender。

标记需要重新铺设和重新渲染的rendernodes,从最近的布局边界和绘制图层的过程生成新的图层树。只需要重新生成和更改对应于rendernodes的层。

接下来,根据刷新后的图层树作为输入,在GPU线程中进行栅格化和合成。对于缓存图层,不需要重新栅格化。合成器只需要重新合成缓存的层和未缓存的或更新的层。最后,系统合成器合成后,会显示一个新帧的内容。

以上是ACE JS应用的渲染和更新过程。最后,通过流程图回顾整个过程:

ACE框架的当前成熟度和演进

截至目前,ACE UI框架已经在华为运动手表、华为智能手表、华为智能屏幕、华为手机、华为平板等产品中实现商用。使用场景包括日历、旅行、健身、实用工具等应用,手机的各种应用——设备触控,以及今年6月在鸿蒙系统发布的各种服务卡——图库、相机等等。

此外,在开发调试方面,开发者工具(华为DevEco Studio)中还集成了ACE UI框架,支持在PC (MacOS、Windows)上的开发调试和实时预览(包括实时多设备预览、组件级预览、双向预览等。),从而实现PC和设备上的一致渲染体验。

未来,ACE UI框架将继续沿着两个方面演进:精简开发和高性能,面向开发者的最小开发,面向消费者的流畅酷炫体验,以及在不同设备和平台上的高效部署。

结合语言进一步简化开发范式,结合运行时进一步提升跨语言交互、类型优化等方面的性能体验。并结合分布式能力将编程模型从MVVM进化到分布式MVVM(分布式模型-视图-视图模型)。采用自然语言的声明式UI描述构建接口,进一步开放编程语言。未来会考虑向TS演进,从动态、布局、性能等方面进一步提升用户体验。

当然,应用生态会涉及到更多方面,比如三方插件的繁荣,跨OS平台的扩展,更多创新的分布式体验等等。ACE框架还很年轻,期待和众多开发者一起,聚焦多设备超级终端这一新兴场景,不断打磨完善,共同构建领先的应用体验和生态!

目前,鸿蒙系统的在线开发体验已经上架。欢迎在线体验。ACE框架已经进驻开源社区。欢迎关注,共建。我们期待您一起构建我们的开发框架。如果您在发展过程中有什么问题,对鸿蒙系统发展有什么好的建议,欢迎登录论坛,让让我们一起讨论一下。

japan quarterly 日本季刊