likes
comments
collection
share

ArkUI Engine - 导读

作者站长头像
站长
· 阅读数 8

系列介绍与导读

本系列是基于OpenHarmony中关键的ArkUI Engine 进行分析,所需要的源码可以通过以下链接下载arkui_ace_engine。UI是作为OpenHarmony系统中最关键的页面展示元素,UI框架的设计往往关系着系统流畅度的具体表现,本系列讲的engine,是指ArkUI的framework层,engine大部分代码由C++实现,其中少部分的ts/js属于部分映射

ArkUI Engine - 导读

推荐阅读人员

本文基于分析的代码都是属于OpenHarmony, 与我们听到的鸿蒙OS/鸿蒙next(星河版)还是有些区别,同时本文注重于engine的实现,对于ArkUI的编写不涉及。推荐对系统感兴趣的开发者阅读。

学习完本系列后,你将会获得:

  1. engine是如何把UI进行分层
  2. engine的三棵树
  3. engine是如何对接平台层
  4. 了解ArkUI 控件在C++的实现

环境配置

因此学习engine,我们需要准备好能够编译C++的工具,便于我们进行之后的流程跟踪,这里我推荐visual stuido,开发者可以通过C++扩展插件,进行C++环境的初步配置,如图

ArkUI Engine - 导读

安装成功后,打开engine代码,那么你将会看到如下界面:

ArkUI 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 - 导读

当然,为了学习ArkUI Engine,我们不需要都了解上面的几个分层,我们再把上面的模型进行再一步的简化,得到下图

ArkUI Engine - 导读

  1. 表示层:面向开发人员的最上层UI抽象,比如Android的View 或者Compose里面的Composable等,都是具体UI的抽象,比如Flutter的Widget

  2. 中间层:中间层,比如android 把View变成一个个RenderNode,用于进一步抽象,用于描述Skia绘制的命令等,其他UI框架比如Flutter也是,不同于Android的实现RenderNode,而是自己抽象一个个RenderObject,通过RenderObject生成Scene给到实现层(Flutter Engine)进行绘制。

  3. 实现层:依托于具体图形标准实现,比如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

ArkUI Engine - 导读

我们在ArkTS写的各种Component,其实通过ArkCompiler编译后,就会得到编译后的js代码,比如我们定义一个Component名称为Index,其实编译后就会变成一个js类,Index且集成于上面我们说到的ViewPU,用于实现UI的表示

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  .....

编译后

ArkUI Engine - 导读

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 Engine - 导读

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~