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

系列介绍与导读

本系列是根据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~