ArkUI Engine - 导读

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

系列介绍与导读

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

推荐阅读人员

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

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

  1. engine是如何把UI进行分层
  2. engine的三棵树
  3. engine是如何对接平台层
  4. 了解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诞生要早得多。

java 复制代码
AOSP implements OpenHarmony

可能大家会有误解,虽然AOSP 远远早于OpenHarmony,但是OpenHarmony其实是以AOSP先作为实现类 再定义出接口,这样才能兼容当前所有的Andriod手机,因此后续即使剔除AOSP后,也能够继续以其他实现完成。但是以AOSP先作为实现类再设计出抽象的后果便是,接口定义与系统设计其实是受限于android的定义的。因此关于next版本的设计,相信广大开发者都很好奇。

UI Framework

UI Framework这个概念从事过相关移动端开发都不太陌生,我们把Android原生系统,Flutter等UI Framework做一个分层,根据应用层到真正渲染层,主流的UI框架(系统实现)都满足下图:

当然,为了学习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

scala 复制代码
abstract class ViewPU extends NativeViewPartialUpdate
  implements IViewPropertiesChangeSubscriber {
scala 复制代码
abstract class View extends NativeViewFullUpdate implements
  IMultiPropertiesChangeSubscriber, IMultiPropertiesReadSubscriber {

它们的区别是两者的集成基类不同,一类是NativeViewPartialUpdate,用于增量更新,一类是NativeViewFullUpdate即全更新

大部分的控件都是继承于ViewPU,小部分特殊控件比如播放器可以继承于View

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

less 复制代码
@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  .....

编译后

ViewPU承载着UI的表示,比如维护parent的关系,local storage的维护等,最重要的是它将代表UI在JS的表现,具体的实现在C++里面。

我们也讲到ViewPU继承于NativeViewPartialUpdate,NativeViewPartialUpdate定义如下,它里面的功能将通过napi完成js与C++的交互,即表示层与中间层的枢纽

scss 复制代码
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也会进行相应重新绘制

php 复制代码
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内核源码剖析》这本书)

scss 复制代码
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

arduino 复制代码
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渲染。

arduino 复制代码
class RenderComponent : public Component {
    DECLARE_ACE_TYPE(RenderComponent, Component);
    RenderComponent特有方法
    virtual RefPtr<RenderNode> CreateRenderNode() = 0;

而负责组合其他Component的,比如ForEach这些,它的父类是BaseComposedComponent

kotlin 复制代码
// 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功能基本一致,负责子节点维护与构建等

arduino 复制代码
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进行实现层渲染。

arduino 复制代码
// 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的步骤见上述材料

scss 复制代码
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~

相关推荐
一只大侠的侠3 小时前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
崔庆才丨静觅5 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60616 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了6 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅6 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅7 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment7 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅7 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊7 小时前
jwt介绍
前端