本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!
系列介绍与导读
本系列是基于OpenHarmony中关键的ArkUI Engine 进行分析,所需要的源码可以通过以下链接下载arkui_ace_engine。UI是作为OpenHarmony系统中最关键的页面展示元素,UI框架的设计往往关系着系统流畅度的具体表现,本系列讲的engine,是指ArkUI的framework层,engine大部分代码由C++实现,其中少部分的ts/js属于部分映射
推荐阅读人员
本文基于分析的代码都是属于OpenHarmony, 与我们听到的鸿蒙OS/鸿蒙next(星河版)还是有些区别,同时本文注重于engine的实现,对于ArkUI的编写不涉及。推荐对系统感兴趣的开发者阅读。
学习完本系列后,你将会获得:
- engine是如何把UI进行分层
- engine的三棵树
- engine是如何对接平台层
- 了解ArkUI 控件在C++的实现
环境配置
因此学习engine,我们需要准备好能够编译C++的工具,便于我们进行之后的流程跟踪,这里我推荐visual stuido,开发者可以通过C++扩展插件,进行C++环境的初步配置,如图
安装成功后,打开engine代码,那么你将会看到如下界面:
OpenHarmony 与鸿蒙OS关系
值得注意的是,本文所说到的鸿蒙OS,是指当前市面发行版本鸿蒙OS(4.0),并非鸿蒙next星河版本的组成
我们以当前能够接触的鸿蒙OS(4.0)为例子,当前鸿蒙OS可以粗略看作由以下两部分组成
OpenHarmony:OpenHarmony是由一系列系统关键系统组成,比如内核、UI FrameWork等等,他可以被使用在各种嵌入式设备中,值得注意的是,在当前手机系统中, OpenHarmony更属于一个中间层,通过中间层去驱动平台层的实现。比如ArkUI,其实是通过抽象层的,从而让ArkUI编写的代码能够在较低平台依赖下进行
AOSP:Android开放源码,当前鸿蒙手机系统中的OpenHarmony实现,由AOSP完成,比如平台相关渲染,平台相关运行等
从技术与架构的角度出发,笔者认为华为的目标是,把OpenHarmony打造成AOSP的完全抽象层,即使AOSP比OpenHarmony诞生要早得多。
AOSP implements OpenHarmony
可能大家会有误解,虽然AOSP 远远早于OpenHarmony,但是OpenHarmony其实是以AOSP先作为实现类再定义出接口,这样才能兼容当前所有的Andriod手机,因此后续即使剔除AOSP后,也能够继续以其他实现完成。但是以AOSP先作为实现类再设计出抽象的后果便是,接口定义与系统设计其实是受限于android的定义的。因此关于next版本的设计,相信广大开发者都很好奇。
UI Framework
UI Framework这个概念从事过相关移动端开发都不太陌生,我们把Android原生系统,Flutter等UI Framework做一个分层,根据应用层到真正渲染层,主流的UI框架(系统实现)都满足下图:
当然,为了学习ArkUI Engine,我们不需要都了解上面的几个分层,我们再把上面的模型进行再一步的简化,得到下图
-
表示层:面向开发人员的最上层UI抽象,比如Android的View 或者Compose里面的Composable等,都是具体UI的抽象,比如Flutter的Widget
-
中间层:中间层,比如android 把View变成一个个RenderNode,用于进一步抽象,用于描述Skia绘制的命令等,其他UI框架比如Flutter也是,不同于Android的实现RenderNode,而是自己抽象一个个RenderObject,通过RenderObject生成Scene给到实现层(Flutter Engine)进行绘制。
-
实现层:依托于具体图形标准实现,比如OpenGL/VulKan,这一层是真正的图形绘制,比如我们可以在Andorid直接通过OpenGL绘制出形状。因为直接通过OpenGL绘制会比较复杂,因此往往还会抽象出一层,比如Skia。
接下来,我们将代入arkui engine的视角,去探索这三层如何实现
表示层
我们都知道Android的View体系中,每一个控件都可以是一个View,它其实就是Android中UI的表示,那么ArkUI中的表示是什么呢?
其实有两个,它们都在state_mgnt 目录下,分别是ViewPU 与 View
abstract class ViewPU extends NativeViewPartialUpdate
implements IViewPropertiesChangeSubscriber {
abstract class View extends NativeViewFullUpdate implements
IMultiPropertiesChangeSubscriber, IMultiPropertiesReadSubscriber {
它们的区别是两者的集成基类不同,一类是NativeViewPartialUpdate,用于增量更新,一类是NativeViewFullUpdate即全更新
大部分的控件都是继承于ViewPU,小部分特殊控件比如播放器可以继承于View
我们在ArkTS写的各种Component,其实通过ArkCompiler编译后,就会得到编译后的js代码,比如我们定义一个Component名称为Index,其实编译后就会变成一个js类,Index且集成于上面我们说到的ViewPU,用于实现UI的表示
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
.....
编译后
ViewPU承载着UI的表示,比如维护parent的关系,local storage的维护等,最重要的是它将代表UI在JS的表现,具体的实现在C++里面。
我们也讲到ViewPU继承于NativeViewPartialUpdate,NativeViewPartialUpdate定义如下,它里面的功能将通过napi完成js与C++的交互,即表示层与中间层的枢纽
declare class NativeViewPartialUpdate {
constructor( );
markNeedUpdate(): void;
findChildById(compilerAssignedUniqueChildId: string): View;
syncInstanceId(): void;
isFirstRender(): boolean;
restoreInstanceId(): void;
static create(newView: NativeViewPartialUpdate): void;
finishUpdateFunc(elmtId: number): void;
isLazyItemRender(elmtId : number) : boolean;
setCardId(cardId: number): void;
getCardId(): number;
resetRecycleCustomNode(): void;
}
IViewPropertiesChangeSubscriber是一个接口,用于属性更新回调,这也是为什么我们通过一些装饰器能够实现属性值监听,其实本质还是把属性作为监听者,当属性发生改变时其dependent也会进行相应重新绘制
interface IViewPropertiesChangeSubscriber extends IPropertySubscriber {
// ViewPU get informed when View variable has changed
// informs the elmtIds that need update upon variable change
viewPropertyHasChanged(varName: PropertyInfo, dependentElmtIds: Set<number>): void ;
}
中间层
像Flutter,widget其实是相对于Element与RenderObject的抽象。同样的ArkUI也有类似概念,因为当前ArkUI默认情况下,使用了Flutter的管线渲染引擎进行渲染,即Layer之后到Scene这一层渲染,其实都是Flutter引擎绘制。(想要了解Flutter引擎工作流程,这里推荐《Flutter内核源码剖析》这本书)
namespace OHOS::Ace::Flutter {
void Layer::AddChildren(const RefPtr<Layer>& layer)
{
auto it = std::find(children_.begin(), children_.end(), layer);
if (it == children_.end()) {
layer->SetParent(AceType::Claim(this));
children_.push_back(layer);
}
}
void Layer::DumpTree(int32_t depth)
{
if (DumpLog::GetInstance().GetDumpFile()) {
Dump();
DumpLog::GetInstance().Print(depth, AceType::TypeName(this), children_.size());
}
for (const auto& item : children_) {
item->DumpTree(depth + 1);
}
}
} // namespace OHOS::Ace::Flutter
之后Layer生成Scene进入Flutter Engine的光栅化。
既然最后走的是Flutter Engine的渲染,那么ArkUI也是用Element去表示中间结果吗?其实不是。因为Flutter的Element其实存在着大部分Widget相关的逻辑,直接用肯定是不行的,因为表示层都不一样,这也意味着中间层想要完全迁移至Flutter Framework肯定是不行的。
对标于Element,ArkUI在这基础上提出了Componet这一概念,用于进一步抽象。Component作为一个最基础的容器,它的最重要一个作用是为了生成对应的Element,即CreateElement
class ACE_EXPORT Component : public virtual AceType {
DECLARE_ACE_TYPE(Component, AceType);
public:
Component();
~Component() override;
virtual RefPtr<Element> CreateElement() = 0;
我们知道,普通的View可以是ViewGroup或者View,ViewGroup它的目的是把一些列可以渲染的View进行组合。对于ArkUI也是一样,对于一个能够承担渲染具体UI的View,Component在这基础上有一个RenderComponent的实现类,它除了生成Element之外,还要负责生成一个RenderNode,用于真实UI渲染。
class RenderComponent : public Component {
DECLARE_ACE_TYPE(RenderComponent, Component);
RenderComponent特有方法
virtual RefPtr<RenderNode> CreateRenderNode() = 0;
而负责组合其他Component的,比如ForEach这些,它的父类是BaseComposedComponent
// A component can compose others components.
class ACE_EXPORT BaseComposedComponent : public Component {
DECLARE_ACE_TYPE(BaseComposedComponent, Component);
我们拿FlexComponent举例子,一切可渲染UI的,都将实现来自Component的CreateElement与RenderComponent的CreateRenderNode方法
ArkUI的Element与Flutter framwork的Element功能基本一致,负责子节点维护与构建等
class ACE_EXPORT Element : public virtual AceType {
DECLARE_ACE_TYPE(Element, AceType);
public:
Element() = default;
~Element();
void AddChild(const RefPtr<Element>& child, int32_t slot = DEFAULT_ELEMENT_SLOT);
void RemoveChild(const RefPtr<Element>& child);
RefPtr<Element> GetChildBySlot(int32_t slot);
void DeactivateChild(RefPtr<Element> child);
void Rebuild();
同样RenderNode负责视图渲染以及操作TreeRender,用于输出最后的Scene给pipeline进行实现层渲染。
// RenderNode is the base class for different render backend, represent a render unit for render pipeline.
class ACE_EXPORT RenderNode : public PropertyAnimatable, public AnimatableProperties, public virtual AceType {
DECLARE_ACE_TYPE(RenderNode, PropertyAnimatable, AceType);
public:
using OpacityCallback = std::function<void(uint8_t)>;
using SlipFactorSetting = std::function<void(double)>;
~RenderNode() override = default;
static void MarkTreeRender(const RefPtr<RenderNode>& root, bool& meetHole, bool needFlush);
static void MarkWholeRender(const WeakPtr<RenderNode>& nodeWeak, bool needFlush);
void SetZIndex(int32_t zIndex)
{
zIndex_ = zIndex;
}
int32_t GetZIndex() const
{
return zIndex_;
}
实现层
arkui的默认实现层与flutter实现层一样,用于最终平台相关的输出,比如skia绘制指令等,这里就不再详述,通过Layer合成Scene的步骤见上述材料
void FlutterRenderContext::StartRecording()
{
currentLayer_ = AceType::MakeRefPtr<PictureLayer>();
recorder_ = flutter::PictureRecorder::Create();
canvas_ = flutter::Canvas::Create(
recorder_.get(), estimatedRect_.Left(), estimatedRect_.Top(), estimatedRect_.Right(), estimatedRect_.Bottom());
if (clipHole_.IsValid()) {
canvas_->save();
needRestoreHole_ = true;
canvas_->clipRect(
clipHole_.Left(), clipHole_.Top(), clipHole_.Right(), clipHole_.Bottom(), SkClipOp::kDifference);
}
containerLayer_->AddChildren(currentLayer_);
}
总结
本篇作为ArkUI Engine系列的导读,希望能够让读者对整体框架有一个大概的了解,通过建立较为全面的思维导图之后,接下来的学习就能够更加轻松。本系列将着重于中间层的实现,读者们需要对表示层的基础UI,比如ForEach,普通的View比如Row有个大致了解即可。
接下来,在接下来几篇文章中,我们将更深入具体源码的实现,让大家更加了解engine的实现,bye~