# Understanding ArkUI Internals **Repository Path**: zjucx/understanding-arkui-internals ## Basic Information - **Project Name**: Understanding ArkUI Internals - **Description**: 深入理解ArkUI - **Primary Language**: JavaScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2024-12-13 - **Last Updated**: 2025-07-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 深入理解ArkUI ## ArkUI框架初始化 ### 从Stage模型说起 Stage模型应用程序中的 UIAbility 实例会以不同的状态传输。UIAbility 类提供一系列回调。通过这些回调,可以了解 UIAbility 实例的状态变化。 UIAbility的生命周期有创建、前台、后台、销毁四种状态 UIAbility实例创建完成后,在进入Foreground状态之前,系统会创建一个WindowStage实例,并触发***onWindowStageCreate***回调。您可以在回调中设置 UI 加载和 WindowStage 事件订阅。 ![image desc](./stage.png) 使用DevEco创建简单Demo,`src/main/ets/entryability/EntryAbility.ts`代码中定义了onWindowStageCreate函数 ```js export default class EntryAbility extends UIAbility { ... onWindowStageCreate(windowStage: window.WindowStage) { // Main window is created, set main page for this ability hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); windowStage.loadContent('pages/Index', (err, data) => { if (err.code) { hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); return; } hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); }); } ... } ``` ![image](stagecreate.png) 启动过程UIAbility::OnForeground过程中会创建AppWindowStage,回调onWindowStageCreate函数,通过windowStage.loadContent函数完成ArkUI初始化和页面加载。 loadContent函数实现在文件`foundation/window/window_manager/interfaces/kits/napi/window_runtime/window_stage_napi/js_window_stage.cpp`中 ```c++ napi_value JsWindowStage::OnLoadContent(napi_env env, napi_callback_info info, bool isLoadedByName) { ... // 创建LoadContentTask异步Napi任务 NapiAsyncTask::CompleteCallback complete = [weak = windowScene_, contentStorage, contextUrl, isLoadedByName]( napi_env env, NapiAsyncTask& task, int32_t status) { auto weakScene = weak.lock(); sptr win = weakScene ? weakScene->GetMainWindow() : nullptr; if (win == nullptr) { task.Reject(env, CreateJsError(env, static_cast(WmErrorCode::WM_ERROR_STATE_ABNORMALLY))); WLOGFE("[NAPI]Get window failed"); return; } LoadContentTask(contentStorage, contextUrl, win, env, task, isLoadedByName); }; napi_value result = nullptr; NapiAsyncTask::Schedule("JsWindowStage::OnLoadContent", env, CreateAsyncTaskWithLastParam(env, callBack, nullptr, std::move(complete), &result)); return result; } ``` 在LoadContentTask中创建UiContent并初始化ArkUI框架。 ```c++ WMError WindowSessionImpl::SetUIContentInner(const std::string& contentInfo, napi_env env, napi_value storage, WindowSetUIContentType type, AppExecFwk::Ability* ability) { ... switch (type) { default: case WindowSetUIContentType::DEFAULT: aceRet = uiContent->Initialize(this, contentInfo, storage); break; ... } ... } ``` ### 初始化 整个流程图如下所示: ![image](out/arkui/arkui.png) 涉及到数据结构如下所示: ![image](out/data%20struct/date.png) #### Container初始化 如时序图`Container初始化`所示 #### Pipeline初始化 如时序图`Pipeline初始化`所示 #### VSync注册 `AceContainer::SetView`函数会创建`RosenWindow`,构造函数如下: ```c++ RosenWindow::RosenWindow(const OHOS::sptr& window, RefPtr taskExecutor, int32_t id) : rsWindow_(window), taskExecutor_(taskExecutor), id_(id) { vsyncCallback_ = std::make_shared(); vsyncCallback_->onCallback = [weakTask = taskExecutor_, id = id_](int64_t timeStampNanos, int64_t frameCount) { auto taskExecutor = weakTask.Upgrade(); auto onVsync = [id, timeStampNanos, frameCount] { int64_t ts = GetSysTimestamp(); ArkUIPerfMonitor::GetInstance().StartPerf(); if (FrameReport::GetInstance().GetEnable()) { FrameReport::GetInstance().FlushBegin(); } ContainerScope scope(id); // use container to get window can make sure the window is valid auto container = Container::Current(); CHECK_NULL_VOID(container); auto window = container->GetWindow(); CHECK_NULL_VOID(window); int64_t refreshPeriod = window->GetVSyncPeriod(); window->OnVsync(static_cast(timeStampNanos), static_cast(frameCount)); ArkUIPerfMonitor::GetInstance().FinishPerf(); auto pipeline = container->GetPipelineContext(); CHECK_NULL_VOID(pipeline); pipeline->OnIdle(std::min(ts, timeStampNanos) + refreshPeriod); JankFrameReport::GetInstance().JankFrameRecord(timeStampNanos, window->GetWindowName()); if (FrameReport::GetInstance().GetEnable()) { FrameReport::GetInstance().FlushEnd(); } window->SetLastVsyncEndTimestamp(GetSysTimestamp()); }; auto uiTaskRunner = SingleTaskExecutor::Make(taskExecutor, TaskExecutor::TaskType::UI); if (uiTaskRunner.IsRunOnCurrentThread()) { onVsync(); return; } uiTaskRunner.PostTask([callback = std::move(onVsync)]() { callback(); }, "ArkUIRosenWindowVsync"); }; rsUIDirector_ = OHOS::Rosen::RSUIDirector::Create(); if (window->GetSurfaceNode()) { rsUIDirector_->SetRSSurfaceNode(window->GetSurfaceNode()); } rsUIDirector_->SetCacheDir(AceApplicationInfo::GetInstance().GetDataFileDirPath()); rsUIDirector_->Init(); rsUIDirector_->SetUITaskRunner( [taskExecutor, id](const std::function& task, uint32_t delay) { ContainerScope scope(id); CHECK_NULL_VOID(taskExecutor); taskExecutor->PostDelayedTask( task, TaskExecutor::TaskType::UI, delay, "ArkUIRosenWindowRenderServiceTask", PriorityType::HIGH); }, id); rsUIDirector_->SetRequestVsyncCallback([weak = weak_from_this()]() { auto self = weak.lock(); CHECK_NULL_VOID(self); self->RequestFrame(); }); } ``` `vsyncCallback_`就是ArkUI收到Vsync信号后要执行的逻辑:1. `window->OnVsync`(渲染管线执行)2.`pipeline->OnIdle` ArkUI通过函数`rsWindow_->RequestVsync(vsyncCallback_)`向RS注册Vsync。下次Vsync信号到来时,RS通过此`vsyncCallback_`分发执行任务 ![image](out/vsync/vsync.png) ### Index页面加载 如时序图`Page加载`所示先创建Index页面,再`OnPageReady`中执行Build函数,创建页面组件。最终完成页面的加载 ![image](pageload.png) #### 组件树创建 ![image](out/nodetree/nodetree.png) ```c++ void AceContainer::AttachView( std::shared_ptr window, AceViewPreview* view, double density, int32_t width, int32_t height) { ... taskExecutor_->PostTask( [weak]() { auto context = weak.Upgrade(); if (context == nullptr) { return; } context->SetupRootElement(); }, TaskExecutor::TaskType::UI); aceView_->Launch(); ... } ``` 1. `stageNode`挂载在`rootNode_`中 ```c++ void PipelineContext::SetupRootElement() { CHECK_RUN_ON(UI); rootNode_ = FrameNode::CreateFrameNodeWithTree( V2::ROOT_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), MakeRefPtr()); ... auto stageNode = FrameNode::CreateFrameNode( V2::STAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), MakeRefPtr()); ... if (windowModal_ == WindowModal::CONTAINER_MODAL) { ACE_SCOPED_TRACE("WindowModal::CONTAINER_MODAL"); MaximizeMode maximizeMode = GetWindowManager()->GetWindowMaximizeMode(); rootNode_->AddChild( ContainerModalViewFactory::GetView(atomicService ? atomicService : stageNode, maximizeMode)); } else { rootNode_->AddChild(atomicService ? atomicService : stageNode); } stageManager_ = MakeRefPtr(stageNode); //保存在stageManager_ } ``` 2. `pageNode`挂载在`stageNode`中 ```c++ void PageRouterManager::LoadPage(int32_t pageId, const RouterPageInfo& target, bool needHideLast, bool needTransition) { ... auto pageNode = FrameNode::CreateFrameNode(V2::PAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), pagePattern); ... // loadNamedRouter_ == JsiDeclarativeEngine::LoadNamedRouterSource auto result = loadNamedRouter_(target.url, target.isNamedRouterMode); ... if (!OnPageReady(pageNode, needHideLast, needTransition)) { pageRouterStack_.pop_back(); LOGE("LoadPage OnPageReady Failed"); return; } ... } bool PageRouterManager::OnPageReady( const RefPtr& pageNode, bool needHideLast, bool needTransition, bool isCardRouter, int64_t cardId) { ... auto context = DynamicCast(pipeline); auto stageManager = context ? context->GetStageManager() : nullptr; //取出stageNode if (stageManager) { return stageManager->PushPage(pageNode, needHideLast, needTransition); } return false; } ``` 3. `pageRootNode`(Index组件)挂载在`pageNode`中 ```c++ bool JsiDeclarativeEngine::LoadNamedRouterSource(const std::string& namedRoute, bool isTriggeredByJs) { ... // 执行 new Index() 创建View auto ret = iter->second.pageGenerator->Call(vm, JSNApi::GetGlobalObject(vm), argv.data(), 0); if (!ret->IsObject()) { return false; } // view 挂载到 pageNode 中 Framework::UpdateRootComponent(ret->ToObject(vm)); JSViewStackProcessor::JsStopGetAccessRecording(); return true; } ``` 最终执行到`UpdateRootComponent`函数中将`pageRootNode`添加到`pageNode`中 ```c++ void UpdateRootComponent(const panda::Local& obj) { ACE_FUNCTION_TRACE(); auto* view = static_cast(obj->GetNativePointerField(0)); ... { auto frontEnd = AceType::DynamicCast(container->GetFrontend()); CHECK_NULL_VOID(frontEnd); auto pageRouterManager = frontEnd->GetPageRouterManager(); CHECK_NULL_VOID(pageRouterManager); pageNode = pageRouterManager->GetCurrentPageNode(); CHECK_NULL_VOID(pageNode); } auto pageRootNode = AceType::DynamicCast(view->CreateViewNode()); CHECK_NULL_VOID(pageRootNode); // root custom component pageRootNode->MountToParent(pageNode); ... } ``` 最终生成[组件树](#node_tree) ## 页面执行原理 ### Index页面的ets描述 `Index.ets`代码如下: ```js @Component struct My { @State isShow: boolean = true build() { Row() { if (this.isShow) { Text("My") } } .onClick(()=>{ this.isShow = !this.isShow }) } } @Entry @Component struct Index { @State message: string = 'Hello World' build() { Row() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) My() }.width('100%') }.width('100%') } } ``` ### Index页面的js描述 经编译工具链后生成代码`build/default/cache/default/default@CompileArkTS/esmodule/debug/entry/src/main/ets/pages/Index.js` > Tip: 不同SDK版本路径有差异 ```js "use strict"; class My extends ViewPU { constructor(parent, params, __localStorage, elmtId = -1) { super(parent, __localStorage, elmtId); this.__isShow = new ObservedPropertySimplePU(true, this, "isShow"); this.setInitiallyProvidedValue(params); } setInitiallyProvidedValue(params) { if (params.isShow !== undefined) { this.isShow = params.isShow; } } updateStateVars(params) { } purgeVariableDependenciesOnElmtId(rmElmtId) { this.__isShow.purgeDependencyOnElmtId(rmElmtId); } aboutToBeDeleted() { this.__isShow.aboutToBeDeleted(); SubscriberManager.Get().delete(this.id__()); this.aboutToBeDeletedInternal(); } get isShow() { return this.__isShow.get(); } set isShow(newValue) { this.__isShow.set(newValue); } initialRender() { this.observeComponentCreation((elmtId, isInitialRender) => { ViewStackProcessor.StartGetAccessRecordingFor(elmtId); Row.create(); Row.onClick(() => { this.isShow = !this.isShow; }); if (!isInitialRender) { Row.pop(); } ViewStackProcessor.StopGetAccessRecording(); }); this.observeComponentCreation((elmtId, isInitialRender) => { ViewStackProcessor.StartGetAccessRecordingFor(elmtId); If.create(); if (this.isShow) { this.ifElseBranchUpdateFunction(0, () => { this.observeComponentCreation((elmtId, isInitialRender) => { ViewStackProcessor.StartGetAccessRecordingFor(elmtId); Text.create("My"); if (!isInitialRender) { Text.pop(); } ViewStackProcessor.StopGetAccessRecording(); }); Text.pop(); }); } else { If.branchId(1); } if (!isInitialRender) { If.pop(); } ViewStackProcessor.StopGetAccessRecording(); }); If.pop(); Row.pop(); } rerender() { this.updateDirtyElements(); } } class Index extends ViewPU { constructor(parent, params, __localStorage, elmtId = -1) { super(parent, __localStorage, elmtId); this.__message = new ObservedPropertySimplePU('Hello World', this, "message"); this.setInitiallyProvidedValue(params); } setInitiallyProvidedValue(params) { if (params.message !== undefined) { this.message = params.message; } } updateStateVars(params) { } purgeVariableDependenciesOnElmtId(rmElmtId) { this.__message.purgeDependencyOnElmtId(rmElmtId); } aboutToBeDeleted() { this.__message.aboutToBeDeleted(); SubscriberManager.Get().delete(this.id__()); this.aboutToBeDeletedInternal(); } get message() { return this.__message.get(); } set message(newValue) { this.__message.set(newValue); } initialRender() { this.observeComponentCreation((elmtId, isInitialRender) => { ViewStackProcessor.StartGetAccessRecordingFor(elmtId); Row.create(); Row.width('100%'); if (!isInitialRender) { Row.pop(); } ViewStackProcessor.StopGetAccessRecording(); }); this.observeComponentCreation((elmtId, isInitialRender) => { ViewStackProcessor.StartGetAccessRecordingFor(elmtId); Column.create(); Column.width('100%'); if (!isInitialRender) { Column.pop(); } ViewStackProcessor.StopGetAccessRecording(); }); this.observeComponentCreation((elmtId, isInitialRender) => { ViewStackProcessor.StartGetAccessRecordingFor(elmtId); Text.create(this.message); Text.fontSize(50); Text.fontWeight(FontWeight.Bold); if (!isInitialRender) { Text.pop(); } ViewStackProcessor.StopGetAccessRecording(); }); Text.pop(); { this.observeComponentCreation((elmtId, isInitialRender) => { ViewStackProcessor.StartGetAccessRecordingFor(elmtId); if (isInitialRender) { ViewPU.create(new My(this, {}, undefined, elmtId)); } else { this.updateStateVarsOfChildByElmtId(elmtId, {}); } ViewStackProcessor.StopGetAccessRecording(); }); } Column.pop(); Row.pop(); } rerender() { this.updateDirtyElements(); } } ViewStackProcessor.StartGetAccessRecordingFor(ViewStackProcessor.AllocateNewElmetIdForNextComponent()); loadDocument(new Index(undefined, {})); ViewStackProcessor.StopGetAccessRecording(); //# sourceMappingURL=Index.js.map ``` 通过命令`hdc shell "hidumper -s WindowManagerService -a '-w windowId -element -c'"` dump组件树: ``` |-> root childSize:1 | ID: 0 | Depth: 1 | AccessibilityId: 0 | FrameRect: RectT (0.00, 0.00) - [720.00 x 1136.00] | ParentLayoutConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[NA x NA]]selfIdealSize: [[NA x NA]] | User defined constraint: minSize: [NA]maxSize: [[720.00px x 1136.00px]]selfIdealSize: [[720.00px x 1136.00px]] | ContentConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[NA x NA]]selfIdealSize: [[720.00 x 1136.00]] | FrameProxy: FrameChildNode:[1-0-1,] partFrameNodeChildren:[1,] TotalCount:1 | ------------start print rsNode | RootNode[63436666961923] child[63436666961924 ] Bounds[0.0 0.0 720.0 1136.0] Frame[0.0 0.0 720.0 1136.0] |-> stage childSize:1 | ID: 1 | Depth: 2 | AccessibilityId: 1 | FrameRect: RectT (0.00, 0.00) - [720.00 x 1136.00] | BackgroundColor: #FFFFFFFF | ParentLayoutConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[720.00 x 1136.00]]selfIdealSize: [[NA x NA]] | FrameProxy: FrameChildNode:[2-0-1,] partFrameNodeChildren:[2,] TotalCount:1 | ------------start print rsNode | CanvasNode[63436666961924] child[63436666961925 ] Bounds[0.0 0.0 720.0 1136.0] Frame[0.0 0.0 720.0 1136.0], BackgroundColor[#FFFFFFFF] |-> page childSize:1 | ID: 2 | Depth: 3 | AccessibilityId: 2 | FrameRect: RectT (0.00, 0.00) - [720.00 x 1136.00] | ParentLayoutConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[720.00 x 1136.00]]selfIdealSize: [[NA x NA]] | FrameProxy: FrameChildNode:[-1-0-1,] partFrameNodeChildren:[6,] TotalCount:1 | ------------start print rsNode | CanvasNode[63436666961925] child[63436666961926 ] Bounds[0.0 0.0 720.0 1136.0] Frame[0.0 0.0 720.0 1136.0] |-> JsView childSize:1 | ID: -1 | Depth: 4 | AccessibilityId: 3 |-> Row childSize:1 | ID: 6 | Depth: 5 | AccessibilityId: 4 | FrameRect: RectT (0.00, 0.00) - [720.00 x 120.00] | ParentLayoutConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[720.00 x 1136.00]]selfIdealSize: [[NA x NA]] | User defined constraint: minSize: [NA]maxSize: [NA]selfIdealSize: [[100.00% x NA]] | ContentConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[720.00 x 1136.00]]selfIdealSize: [[720.00 x NA]] | FrameProxy: FrameChildNode:[7-0-1,] partFrameNodeChildren:[7,] TotalCount:1 | ------------start print rsNode | CanvasNode[63436666961926] child[63436666961927 ] Bounds[0.0 0.0 720.0 120.0] Frame[0.0 0.0 720.0 120.0] |-> Column childSize:2 | ID: 7 | Depth: 6 | AccessibilityId: 5 | FrameRect: RectT (0.00, 0.00) - [720.00 x 120.00] | ParentLayoutConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[720.00 x NA]]selfIdealSize: [[NA x NA]] | User defined constraint: minSize: [NA]maxSize: [NA]selfIdealSize: [[100.00% x NA]] | ContentConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[720.00 x NA]]selfIdealSize: [[720.00 x NA]] | FrameProxy: FrameChildNode:[8-0-1,9-1-1,] partFrameNodeChildren:[8,11,] TotalCount:2 | ------------start print rsNode | CanvasNode[63436666961927] child[63436666961928 63436666961929 ] Bounds[0.0 0.0 720.0 120.0] Frame[0.0 0.0 720.0 120.0] |-> Text childSize:0 ID: 8 Depth: 7 AccessibilityId: 6 FrameRect: RectT (150.00, 0.00) - [420.00 x 91.00] ParentLayoutConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[720.00 x NA]]selfIdealSize: [[NA x NA]] top: 0.000000 left: 150.000000 Content: Hello World FontColor: #FF000000 FontSize: 50.00fp Selection: (baseOffset: -1, selectionBaseOffset: Offset (0.00, 0.00), destinationOffset: -1, selectionDestinationOffset: Offset (0.00, 0.00), firstHandle: RectT (0.00, 0.00) - [0.00 x 0.00], secondHandle: RectT (0.00, 0.00) - [0.00 x 0.00], firstHandleOffset_: Offset (0.00, 0.00), secondHandleOffset_: Offset (0.00, 0.00)) ------------start print rsNode CanvasNode[63436666961928] child[] Bounds[150.0 0.0 420.0 91.0] Frame[150.0 0.0 420.0 91.0] |-> JsView childSize:1 | ID: 9 | Depth: 7 | AccessibilityId: 7 |-> Row childSize:1 | ID: 11 | Depth: 8 | AccessibilityId: 8 | FrameRect: RectT (343.00, 91.00) - [34.00 x 29.00] | ParentLayoutConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[720.00 x NA]]selfIdealSize: [[NA x NA]] | top: 91.000000 left: 343.000000 | FrameProxy: FrameChildNode:[12-0-1,] partFrameNodeChildren:[13,] TotalCount:1 | ------------start print rsNode | CanvasNode[63436666961929] child[63436666961930 ] Bounds[343.0 91.0 34.0 29.0] Frame[343.0 91.0 34.0 29.0] |-> IfElse childSize:1 | ID: 12 | Depth: 9 | AccessibilityId: 9 |-> Text childSize:0 ID: 13 Depth: 10 AccessibilityId: 10 FrameRect: RectT (0.00, 0.00) - [34.00 x 29.00] ParentLayoutConstraint: minSize: [[0.00 x 0.00]]maxSize: [[720.00 x 1136.00]]percentReference: [[720.00 x 1136.00]]parentIdealSize: [[NA x NA]]selfIdealSize: [[NA x NA]] top: 91.000000 left: 343.000000 Content: My FontColor: #FF000000 FontSize: 16.00fp ``` ### 组件及属性(js语言与c++语言映射)注册 组件及属性注册要从启动说起,`foundation/ability/ability_runtime/frameworks/native/runtime/js_runtime.cpp`中会创建JsRUntime ```c++ bool JsRuntime::Initialize(const Options& options) { HITRACE_METER_NAME(HITRACE_TAG_APP, __PRETTY_FUNCTION__); ... if (IsUseAbilityRuntime(options)) { ... if (!preloaded_) { ... HILOG_DEBUG("PreloadAce start."); PreloadAce(options); HILOG_DEBUG("PreloadAce end."); nativeEngine->RegisterPermissionCheck(PermissionCheckFunc); } ... } } ``` 加载`libace_compatible.z.so`,调用PreloadAceModule函数 ```c++ void JsRuntime::PreloadAce(const Options& options) { auto nativeEngine = GetNativeEnginePointer(); CHECK_POINTER(nativeEngine); #ifdef SUPPORT_GRAPHICS if (options.loadAce) { // ArkTsCard start if (options.isUnique) { OHOS::Ace::DeclarativeModulePreloader::PreloadCard(*nativeEngine, options.bundleName); } else { OHOS::Ace::DeclarativeModulePreloader::Preload(*nativeEngine); } // ArkTsCard end } #endif } ``` `foundation/arkui/ace_engine/interfaces/inner_api/ace/declarative_module_preloader.cpp` ```c++ void InitAceModule(void* runtime) { LIBHANDLE handle = LOADLIB(AceForwardCompatibility::GetAceLibName()); if (handle == nullptr) { return; } auto entry = reinterpret_cast(LOADSYM(handle, PRE_INIT_ACE_MODULE_FUNC)); if (entry == nullptr) { FREELIB(handle); return; } entry(runtime); } ``` 函数`OHOS_ACE_PreloadAceModule`定义在`foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/jsi/jsi_declarative_engine.cpp`中 ```c++ extern "C" ACE_FORCE_EXPORT void OHOS_ACE_PreloadAceModule(void* runtime) { JsiDeclarativeEngineInstance::PreloadAceModule(runtime); } /* 创建ArkJSRuntime,并绑定方舟虚拟机vm */ void JsiDeclarativeEngineInstance::PreloadAceModule(void* runtime) { ... // preload js views JsRegisterViews(JSNApi::GetGlobalObject(vm), runtime); ... } ``` `JsRegisterViews`实现了js和C++映射关系的绑定,实现在文件`foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register.cpp` ```c++ void JsRegisterViews(BindingTarget globalObj, void* nativeEngine) { auto runtime = std::static_pointer_cast(JsiDeclarativeEngineInstance::GetCurrentRuntime()); if (!runtime) { return; } auto vm = runtime->GetEcmaVm(); /* 全局方法绑定 */ globalObj->Set(vm, panda::StringRef::NewFromUtf8(vm, "loadDocument"), panda::FunctionRef::New(const_cast(vm), JsLoadDocument)); globalObj->Set(vm, panda::StringRef::NewFromUtf8(vm, "registerNamedRoute"), panda::FunctionRef::New(const_cast(vm), JsRegisterNamedRoute)); ... /* 组件方法绑定 */ JsBindViews(globalObj, nativeEngine); ... ``` `JsBindViews`实现了组件的js和C++映射关系的绑定,实现在文件`foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/jsi/jsi_view_register_impl_ng.cpp` ```c++ void JsBindViews(BindingTarget globalObj, void* nativeEngine) { JSViewAbstract::JSBind(globalObj); JSViewStackProcessor::JSBind(globalObj); JSContainerBase::JSBind(globalObj); JSScrollableBase::JSBind(globalObj); JSView::JSBind(globalObj); JSShapeAbstract::JSBind(globalObj); JSText::JSBind(globalObj); JSColumn::JSBind(globalObj); JSRow::JSBind(globalObj); JSStack::JSBind(globalObj); JSImage::JSBind(globalObj); JSLazyForEach::JSBind(globalObj); ... } ``` `JSText::JSBind`等函数实现了特定组件创建和属性设置函数的映射。以text为例`foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_text.cpp` ```c++ void JSText::JSBind(BindingTarget globalObj) { JSClass::Declare("Text"); MethodOptions opt = MethodOptions::NONE; JSClass::StaticMethod("create", &JSText::Create, opt); JSClass::StaticMethod("width", &JSText::SetWidth); JSClass::StaticMethod("height", &JSText::SetHeight); JSClass::StaticMethod("font", &JSText::SetFont, opt); JSClass::StaticMethod("fontColor", &JSText::SetTextColor, opt); JSClass::StaticMethod("textShadow", &JSText::SetTextShadow, opt); JSClass::StaticMethod("fontSize", &JSText::SetFontSize, opt); JSClass::StaticMethod("fontWeight", &JSText::SetFontWeight, opt); JSClass::StaticMethod("wordBreak", &JSText::SetWordBreak, opt); JSClass::StaticMethod("ellipsisMode", &JSText::SetEllipsisMode, opt); JSClass::StaticMethod("selection", &JSText::SetTextSelection, opt); JSClass::StaticMethod("maxLines", &JSText::SetMaxLines, opt); JSClass::StaticMethod("textIndent", &JSText::SetTextIndent); JSClass::StaticMethod("textOverflow", &JSText::SetTextOverflow, opt); JSClass::StaticMethod("fontStyle", &JSText::SetFontStyle, opt); JSClass::StaticMethod("align", &JSText::SetAlign, opt); JSClass::StaticMethod("textAlign", &JSText::SetTextAlign, opt); JSClass::StaticMethod("lineHeight", &JSText::SetLineHeight, opt); JSClass::StaticMethod("fontFamily", &JSText::SetFontFamily, opt); JSClass::StaticMethod("minFontSize", &JSText::SetMinFontSize, opt); JSClass::StaticMethod("maxFontSize", &JSText::SetMaxFontSize, opt); JSClass::StaticMethod("letterSpacing", &JSText::SetLetterSpacing, opt); JSClass::StaticMethod("textCase", &JSText::SetTextCase, opt); ... } ``` 至此js与c++函数映射完成。 ### Index页面UI组件树如何形成 普通非容器组件的创建一般通过其对应组件的Create完成,以Text组件为例,代码如下: ```js void TextModelNG::Create(const std::u16string& content) { auto* stack = ViewStackProcessor::GetInstance(); CHECK_NULL_VOID(stack); auto nodeId = stack->ClaimNodeId(); ACE_LAYOUT_SCOPED_TRACE("Create[%s][self:%d]", V2::TEXT_ETS_TAG, nodeId); auto frameNode = FrameNode::GetOrCreateFrameNode(V2::TEXT_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr(); }); stack->Push(frameNode); ``` 编译后的文件如下: ```js this.observeComponentCreation((elmtId, isInitialRender) => { ViewStackProcessor.StartGetAccessRecordingFor(elmtId); Text.create(this.message); Text.fontSize(50); Text.fontWeight(FontWeight.Bold); if (!isInitialRender) { Text.pop(); } ViewStackProcessor.StopGetAccessRecording(); }); ``` 组件的Create函数的`Push`方法表示组件创建开始,后续`Text.fontSize` `Text.fontWeight`等这些属性设置会设置给栈顶组件。通过`Pop`函数将节点挂载到父节点上。 ```js void ViewStackProcessor::Pop() { if (elementsStack_.empty() || elementsStack_.size() == 1) { return; } auto currentNode = Finish(); currentNode->SetBuildByJs(true); auto parent = GetMainElementNode(); if (AceType::InstanceOf(parent)) { auto groupNode = AceType::DynamicCast(parent); groupNode->AddChildToGroup(currentNode); return; } currentNode->MountToParent( parent, DEFAULT_NODE_SLOT, AceType::InstanceOf(parent), AceType::InstanceOf(parent)); auto currentFrameNode = AceType::DynamicCast(currentNode); if (currentFrameNode) { currentFrameNode->OnMountToParentDone(); } } ``` 组件从栈中`pop`后新的栈顶必然是当前组件的父组件,通过`MountToParent`函数便可完成父子组件关系的创建。 需要注意的是容器组件在设置属性之后不会立即`pop`,而是在所有子节点都完成后才会执行自身的`pop`,是否是容器组件可通过其对应的Pattern的IsAomitNode返回值来判断。 ### Index页面执行(initialRender函数执行) 分四个阶段完成: ![image](out/initialRender/initialRenderTrace.png) 阶段一、构造`JSViewPartialUpdate`类型的对象,并在构造函数阶段保存`initialRenderView`到后端 阶段二、`CreateViewNode`创建对应的CustomNode对象,保存`RenderFunction`到Build函数执行中 阶段三、遍历执行子节点的Build函数,执行CustomNode的Build,执行`initialRenderView` 阶段四、`initialRenderView`函数执行ui描述,通过上节描述内容执行到对应组件的c++逻辑 详细流程参考下图: ![image](out/initialRender/initialRender.png) ## 布局更新原理 前文已经分析对于每一个页面组件树的生成,概括起来如下: 1. 每个页面基于PagePattern生成PageNode 2. Index.ets中`struct Index`作为自定义组件生成JsView 3. PageNode挂载在Ability对应的StageNode上,并标脏向RS请求Vsync信号 4. 页面根节点根据build函数的描述生成对应Pattern的FrameNode挂载在,并挂载再根节点 ### 最小化更新原理 ![iamge](out/arkui/%E9%A1%B5%E9%9D%A2%E5%B8%83%E5%B1%80.png) 1. 页面首次创建时基于Index.ets的ui描述为每一个组件生成唯一标识id 2. 状态变量发生变化会标记持有的自定义组件为脏节点并保存再渲染管线中 3. 接收到Vsync信号,会更新渲染管线中的保存的脏节点 4. 自定义组件的更新是通过前端框架识别build函数内部子节点的更新条件,重新执行变更的状态变量关联的子节点的UI描述 5. 子节点设置新的属性或者状态变量时会与老节点比较并进行脏区标记,如果是If分支,则会动态创建或删除子节点 6. 布局任务大多依赖父子关系,子节点标脏通常会标脏父节点,并保存在渲染管线中 7. 渲染管线的Measure和Layout阶段重新布局,并保存大小位置等信息到RSNode 8. 最后渲染管线发送绘制指令给RS进程进行绘制和显示 #### 依赖收集 ![image](out/arkui/%E6%9C%80%E5%B0%8F%E5%8C%96%E6%9B%B4%E6%96%B0%E4%BE%9D%E8%B5%96%E6%94%B6%E9%9B%86.png) 状态管理的代码在`foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/stateMgmt.js`文件中,依赖的收集是在`initialRender`函数中完成,`initialRender`执行过程参考[页面执行](#initialRender),`initialRender`会挨个执行`observeComponentCreation`函数 ```js observeComponentCreation2(compilerAssignedUpdateFunc, classObject) { const _componentName = (classObject && ("name" in classObject)) ? Reflect.get(classObject, "name") : "unspecified UINode"; const _popFunc = (classObject && "pop" in classObject) ? classObject.pop : () => { }; const updateFunc = (elmtId, isFirstRender) => { this.syncInstanceId(); ViewStackProcessor.StartGetAccessRecordingFor(elmtId); this.currentlyRenderedElmtIdStack_.push(elmtId); compilerAssignedUpdateFunc(elmtId, isFirstRender); if (!isFirstRender) { _popFunc(); } this.currentlyRenderedElmtIdStack_.pop(); ViewStackProcessor.StopGetAccessRecording(); this.restoreInstanceId(); }; const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent(); // needs to move set before updateFunc. // make sure the key and object value exist since it will add node in attributeModifier during updateFunc. this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc, classObject: classObject }); // add element id -> owning ViewPU UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this)); try { updateFunc(elmtId, /* is first render */ true); } catch (error) { ... } } ``` 1. 先调用`ViewStackProcessor.AllocateNewElmetIdForNextComponent()`生成闭包函数的elmtId 2. 保存`elmtId`和`updateFunc`闭包函数到`updateFuncByElmtId`中 3. `elmtId`作为参数执行闭包函数 4. 如果闭包函数涉及状态变量则通过get函数保存变量名和`elmtId`到`dependentElmtIdsByProperty_`中 ```js class My extends ViewPU { constructor(parent, params, __localStorage, elmtId = -1) { super(parent, __localStorage, elmtId); this.__isShow = new ObservedPropertySimplePU(true, this, "isShow"); this.setInitiallyProvidedValue(params); } ... } class ObservedPropertySimplePU extends ObservedPropertyPU { } class ObservedPropertyPU extends ObservedPropertyAbstractPU { ... get() { this.recordPropertyDependentUpdate(); if (this.shouldInstallTrackedObjectReadCb) { ObservedObject.registerPropertyReadCb(this.wrappedValue_, this.onOptimisedObjectPropertyRead.bind(this)); } else { } return this.wrappedValue_; } } recordPropertyDependentUpdate() { const elmtId = this.getRenderingElmtId(); if (elmtId < 0) { // not access recording return; } /* 保存变量名和elmtId到dependentElmtIdsByProperty_中 */ this.dependentElmtIdsByProperty_.addPropertyDependency(elmtId); } ``` #### 状态变更 状态的变更在`set`函数中 ```js set(newValue) { if (this.wrappedValue_ === newValue) { return; } const oldValue = this.wrappedValue_; if (this.setValueInternal(newValue)) { TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.wrappedValue_, this.notifyPropertyHasChangedPU.bind(this), this.notifyTrackedObjectPropertyHasChanged.bind(this)); } } notifyPropertyHasChangedPU() { if (this.owningView_) { if (this.delayedNotification_ == ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) { // send viewPropertyHasChanged right away this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getAllPropertyDependencies()); } else { // mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending; } } this.subscriberRefs_.forEach((subscriber) => { if (subscriber) { if ('syncPeerHasChanged' in subscriber) { subscriber.syncPeerHasChanged(this); } else { stateMgmtConsole.warn(`${this.debugInfo()}: notifyPropertyHasChangedPU: unknown subscriber ID 'subscribedId' error!`); } } }); } viewPropertyHasChanged(varName, dependentElmtIds) { stateMgmtTrace.scopedTrace(() => { this.syncInstanceId(); if (dependentElmtIds.size && !this.isFirstRender()) { if (!this.dirtDescendantElementIds_.size && !this.runReuse_) { // mark ComposedElement dirty when first elmtIds are added // do not need to do this every time /* 标记脏节点 */ this.markNeedUpdate(); } for (const elmtId of dependentElmtIds) { if (this.hasRecycleManager()) { this.dirtDescendantElementIds_.add(this.recycleManager_.proxyNodeId(elmtId)); } else { this.dirtDescendantElementIds_.add(elmtId); } } } else { } /* 执行watch回调函数 */ let cb = this.watchedProps.get(varName); if (cb) { cb.call(this, varName); } this.restoreInstanceId(); }, "ViewPU.viewPropertyHasChanged", this.constructor.name, varName, dependentElmtIds.size); } ``` ## 布局渲染原理 同Android一样,OHOS的布局渲染流程是有VSync驱动,首页加载后请求VSync,[Vsync注册](#vsync)过程前文`PipelineContext`初始化已经分析过不再赘述。 ### 渲染管线 `VSync`信号产生后会回调渲染管线`foundation/arkui/ace_engine/frameworks/core/pipeline/pipeline_base.cpp`的`OnVsyncEvent`函数如下: ```c++ void PipelineBase::OnVsyncEvent(uint64_t nanoTimestamp, uint32_t frameCount) { CHECK_RUN_ON(UI); ... /* 页面首次加载会置为true */ if (delaySurfaceChange_) { delaySurfaceChange_ = false; OnSurfaceChanged(width_, height_, type_, rsTransaction_); } /* 此处开始渲染管线流程 */ FlushVsync(nanoTimestamp, frameCount); if (onVsyncProfiler_) { onVsyncProfiler_(AceTracker::Stop()); } } ``` 管线的实现在`foundation/arkui/ace_engine/frameworks/core/pipeline_ng/pipeline_context.cpp`的`FlushVsync`函数中 ```c++ void PipelineContext::FlushVsync(uint64_t nanoTimestamp, uint32_t frameCount) { CHECK_RUN_ON(UI); ... /* 事件处理 */ DispatchDisplaySync(nanoTimestamp); FlushAnimation(nanoTimestamp); bool hasRunningAnimation = window_->FlushAnimation(nanoTimestamp); FlushTouchEvents(); /* 脏节点更新 */ FlushBuild(); ... taskScheduler_->FinishRecordFrameInfo(); FlushAnimationClosure(); ... /* 布局任务主要流程 */ taskScheduler_->FlushTask(); ... window_->FlushModifier(); FlushFrameRate(); if (dragWindowVisibleCallback_) { dragWindowVisibleCallback_(); dragWindowVisibleCallback_ = nullptr; } /* 发送绘制指令给RS */ FlushMessages(); InspectDrew(); if (!isFormRender_ && onShow_ && onFocus_) { /* 处理焦点切换 */ FlushFocus(); } ... HandleOnAreaChangeEvent(nanoTimestamp); HandleVisibleAreaChangeEvent(); if (isNeedFlushMouseEvent_) { FlushMouseEvent(); isNeedFlushMouseEvent_ = false; } if (isNeedFlushAnimationStartTime_) { window_->FlushAnimationStartTime(nanoTimestamp); isNeedFlushAnimationStartTime_ = false; } needRenderNode_.clear(); taskScheduler_->FlushAfterRenderTask(); ... window_->Unlock(); } ``` 用一张图概括下: ![iamge](out/arkui/pipeline.png) 其中主要的流程包含 * FlushAnimation:动画阶段。在动画过程中会修改相应的FrameNode节点触发脏区标记,在特定场景下会执行用户侧ets代码实现自定义动画; * FlushTouchEvents:事件处理阶段,比如手势事件处理。在手势处理过程中也会修改FrameNode节点触发脏区标记,在特定场景下会执行用户侧ets代码实现自定义事件; * FlushBuild(FlushDirtyNodeUpdate):自定义组件(@Component)在首次创建挂载或者状态变量变更时会标记为需要rebuild状态,在下一次Vsync过来时会执行rebuild流程,rebuild流程会执行程序UI代码,通过调用View的方法生成相应的组件树结构和属性样式修改任务。 * FlushTask:当FrameNode节点标记为脏区后会根据修改类型创建相应的 Wrapper工具类用于执行相应的布局和渲染任务。 > Measure:布局包装器执行相关的大小测算任务。 测算完成后会继续执行布局任务。特定场景下,比如自定义组件的测算任务,懒加载组件的测算任务会执行用户侧ets代码,动态生成和测算相应的节点子树。 > Layout:布局包装器执行相关的布局任务。 布局完成后会生成geometryNode,用于绘制任务时的大小位置信息获取。 > Render:绘制任务包装器执行相关的绘制任务,执行完成后会标记请求刷新RSNode绘制 * FlushMessage:通过RSUIDirector请求刷新界面绘制。 * OnAreaChange: 组件区域变化事件指组件显示的尺寸、位置等发生变化时触发的事件。 * OnVisibleAreaChange: 组件可见区域变化事件是组件在屏幕中的显示区域面积变化时触发的事件,提供了判断组件是否完全或部分显示在屏幕中的能力。 先熟悉下与布局相关的一些数据结构及关系 ![image](out/arkui/arkui-layout.png) ### 页面标脏 启动首帧渲染会触发`OnSurfaceChanged`,首次页面的标脏逻辑主要在这个函数完成。 ```c++ /* foundation/arkui/ace_engine/adapter/ohos/entrance/ui_content_impl.cpp */ UIContentErrorCode UIContentImpl::CommonInitialize( OHOS::Rosen::Window* window, const std::string& contentInfo, napi_value storage) { ... // create ace_view auto aceView = Platform::AceViewOhos::CreateView(instanceId_, false, container->GetSettings().usePlatformAsUIThread); ... /* 1. 设置SurfaceChanged callback */ errorCode = Platform::AceContainer::SetViewNew(aceView, density, 0, 0, window_); ... /* 2. 执行SurfaceChanged callback */ Platform::AceViewOhos::SurfaceChanged(aceView, 0, 0, deviceHeight >= deviceWidth ? 0 : 1); ... return errorCode; } ``` 1. 初始化SurfaceChanged callback ```c++ /* foundation/arkui/ace_engine/adapter/ohos/entrance/ace_container.cpp */ void AceContainer::SetView(AceView* view, double density, int32_t width, int32_t height, sptr rsWindow, UIEnvCallback callback) { ... container->AttachView(window, view, density, width, height, rsWindow->GetWindowId(), callback); } void AceContainer::AttachView(std::shared_ptr window, AceView* view, double density, float width, float height, uint32_t windowId, UIEnvCallback callback) { ... InitializeCallback(); ... } void AceContainer::InitializeCallback() { ... auto&& viewChangeCallback = [context = pipelineContext_, id = instanceId_](int32_t width, int32_t height, WindowSizeChangeReason type, const std::shared_ptr& rsTransaction) { ... if (type != WindowSizeChangeReason::ROTATION) { /* 设置delaySurfaceChange_ = true,触发OnSurfaceChanged */ context->SetSurfaceChangeMsg(width, height, type, rsTransaction); context->RequestFrame(); return; } ... }; aceView_->RegisterViewChangeCallback(viewChangeCallback); ... } ``` 2. 设置delaySurfaceChange_ = true ```c++ /* foundation/arkui/ace_engine/adapter/ohos/entrance/ace_view_ohos.cpp */ void AceViewOhos::SurfaceChanged(AceViewOhos* view, int32_t width, int32_t height, int32_t orientation, WindowSizeChangeReason type, const std::shared_ptr& rsTransaction) { CHECK_NULL_VOID(view); view->NotifySurfaceChanged(width, height, type, rsTransaction); auto instanceId = view->GetInstanceId(); auto container = Platform::AceContainer::GetContainer(instanceId); if (container) { auto pipelineContext = container->GetPipelineContext(); CHECK_NULL_VOID(pipelineContext); pipelineContext->HideOverlays(); } } ``` 3. 执行SurfaceChanged callback ```C++ /* foundation/arkui/ace_engine/frameworks/core/pipeline/pipeline_base.cpp */ void PipelineBase::OnVsyncEvent(uint64_t nanoTimestamp, uint32_t frameCount) { ... if (delaySurfaceChange_) { delaySurfaceChange_ = false; OnSurfaceChanged(width_, height_, type_, rsTransaction_); } FlushVsync(nanoTimestamp, frameCount); ... } void PipelineContext::OnSurfaceChanged(int32_t width, int32_t height, WindowSizeChangeReason type, const std::shared_ptr& rsTransaction) { ... if (container->IsUseStageModel()) { callback(); /* FlushDirtyNodeUpdate */ FlushBuild(); } ... /* Mark root node dirty */ SetRootRect(width, height, 0.0); } ``` 4. MarkDirty标脏root节点 ```c++ /* foundation/arkui/ace_engine/frameworks/core/pipeline_ng/pipeline_context.cpp */ void PipelineContext::SetRootRect(double width, double height, double offset) { ... SizeF sizeF { static_cast(width), static_cast(height) }; if (rootNode_->GetGeometryNode()->GetFrameSize() != sizeF || rootNode_->IsLayoutDirtyMarked()) { CalcSize idealSize { CalcLength(width), CalcLength(height) }; MeasureProperty layoutConstraint; layoutConstraint.selfIdealSize = idealSize; layoutConstraint.maxSize = idealSize; /* 1. 更新propertyChangeFlag_ = propertyChangeFlag_ | PROPERTY_UPDATE_MEASURE */ rootNode_->UpdateLayoutConstraint(layoutConstraint); // reset parentLayoutConstraint to update itself when next measure task rootNode_->GetGeometryNode()->ResetParentLayoutConstraint(); /* 2. 添加layout task */ rootNode_->MarkDirtyNode(); } ... } ``` 5. 添加Layout Task 通过函数`context->AddDirtyLayoutNode(Claim(this))`将task添加到管线中 ```c++ /* foundation/arkui/ace_engine/frameworks/core/components_ng/base/frame_node.cpp */ void FrameNode::MarkDirtyNode(PropertyChangeFlag extraFlag) { if (CheckNeedMakePropertyDiff(extraFlag)) { if (isPropertyDiffMarked_) { return; } auto context = GetContext(); CHECK_NULL_VOID(context); context->AddDirtyPropertyNode(Claim(this)); isPropertyDiffMarked_ = true; return; } MarkDirtyNode(IsMeasureBoundary(), IsRenderBoundary(), extraFlag); } void FrameNode::MarkDirtyNode(bool isMeasureBoundary, bool isRenderBoundary, PropertyChangeFlag extraFlag) { ACE_SCOPED_TRACE("MarkDirtyNode[%s][self:%d][parent:%d]", GetTag().c_str(), GetId(), GetParent() ? GetParent()->GetId() : 0); if (CheckNeedRender(extraFlag)) { paintProperty_->UpdatePropertyChangeFlag(extraFlag); } layoutProperty_->UpdatePropertyChangeFlag(extraFlag); paintProperty_->UpdatePropertyChangeFlag(extraFlag); auto layoutFlag = layoutProperty_->GetPropertyChangeFlag(); auto paintFlag = paintProperty_->GetPropertyChangeFlag(); if (CheckNoChanged(layoutFlag | paintFlag)) { return; } auto context = GetContext(); CHECK_NULL_VOID(context); if (CheckNeedRequestMeasureAndLayout(layoutFlag)) { /* 判断是否是布局边界 */ if (!isMeasureBoundary && IsNeedRequestParentMeasure()) { auto parent = GetAncestorNodeOfFrame(); if (parent) { parent->MarkDirtyNode(PROPERTY_UPDATE_BY_CHILD_REQUEST); return; } } if (isLayoutDirtyMarked_) { return; } isLayoutDirtyMarked_ = true; /* 添加layout task */ context->AddDirtyLayoutNode(Claim(this)); return; } layoutProperty_->CleanDirty(); MarkNeedRender(isRenderBoundary); } /* foundation/arkui/ace_engine/frameworks/core/pipeline_ng/pipeline_context.cpp */ void PipelineContext::AddDirtyLayoutNode(const RefPtr& dirty) { ... taskScheduler_->AddDirtyLayoutNode(dirty); ... RequestFrame(); } /* foundation/arkui/ace_engine/frameworks/core/pipeline_ng/ui_task_scheduler.cpp */ void UITaskScheduler::AddDirtyLayoutNode(const RefPtr& dirty) { dirtyLayoutNodes_.emplace_back(dirty); } ``` #### 向上标脏原理 `MarkDirtyNode`会根据自身的PropertyChangeFlag来判断是否需要向上标脏父节点 ```c++ bool FrameNode::IsNeedRequestParentMeasure() const { auto layoutFlag = layoutProperty_->GetPropertyChangeFlag(); if (layoutFlag == PROPERTY_UPDATE_BY_CHILD_REQUEST) { const auto& calcLayoutConstraint = layoutProperty_->GetCalcLayoutConstraint(); if (calcLayoutConstraint && calcLayoutConstraint->selfIdealSize && calcLayoutConstraint->selfIdealSize->IsValid()) { return false; } } return CheckNeedRequestParentMeasure(layoutFlag); } ``` 当flag为`PROPERTY_UPDATE_BY_CHILD_REQUEST`并且组件中设置了.width .height则不需要继续向上标准标脏,此时会将当前节点作为布局子树的跟节点添加到dirtyNodeList中 ### DispatchDisplaySync(TODO) ### FlushTouchEvents(TODO) ### FlushBuild(TODO) ### FlushDirtyNodeUpdate ![image](out/arkui/stateupdate.png) #### 自定义组件标脏 此过程会对自定义组件中涉及状态变量发生变化的子组件重新执行属性更新方法。以Demo为例,首帧加载不涉及状态变量更新,故不存在此过程。当点击自定义组件`My`组件,会执行`onClick`,改变状态变量`isShow`。[前文](#most_small_update)已经介绍当状态变量发生变化会执行`viewPropertyHasChanged`函数中`markNeedUpdate`的函数标脏`My`组件。 > My组件继承关系:My -> ViewPU -> NativeViewPartialUpdate ```c++ /* `foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view.cpp`的`JSBind`绑定了`markNeedUpdate`方法。 */ void JSViewPartialUpdate::JSBind(BindingTarget object) { JSClass::Declare("NativeViewPartialUpdate"); MethodOptions opt = MethodOptions::NONE; JSClass::Method("markNeedUpdate", &JSViewPartialUpdate::MarkNeedUpdate); ... JSClass::Method("finishUpdateFunc", &JSViewPartialUpdate::JsFinishUpdateFunc); JSClass::InheritAndBind(object, ConstructorCallback, DestructorCallback); } void JSViewPartialUpdate::MarkNeedUpdate() { needsUpdate_ = ViewPartialUpdateModel::GetInstance()->MarkNeedUpdate(viewNode_); } bool ViewPartialUpdateModelNG::MarkNeedUpdate(const WeakPtr& node) { auto weakNode = AceType::DynamicCast(node); auto customNode = weakNode.Upgrade(); CHECK_NULL_RETURN(customNode, false); customNode->MarkNeedUpdate(); return true; } ``` 最终将脏节点保存在渲染管线`dirtyNodes_`中 ```c++ void CustomNodeBase::MarkNeedUpdate() { if (recycleRenderFunc_) { return; } auto context = PipelineContext::GetCurrentContext(); CHECK_NULL_VOID(context); if (needRebuild_) { return; } needRebuild_ = true; context->AddDirtyCustomNode(AceType::DynamicCast(Claim(this))); } void PipelineContext::AddDirtyCustomNode(const RefPtr& dirtyNode) { CHECK_RUN_ON(UI); CHECK_NULL_VOID(dirtyNode); auto customNode = DynamicCast(dirtyNode); if (customNode && !dirtyNode->GetInspectorIdValue("").empty()) { ACE_LAYOUT_SCOPED_TRACE("AddDirtyCustomNode[%s][self:%d][parent:%d][key:%s]", customNode->GetJSViewName().c_str(), dirtyNode->GetId(), dirtyNode->GetParent() ? dirtyNode->GetParent()->GetId() : 0, dirtyNode->GetInspectorIdValue("").c_str()); } else if (customNode) { ACE_LAYOUT_SCOPED_TRACE("AddDirtyCustomNode[%s][self:%d][parent:%d]", customNode->GetJSViewName().c_str(), dirtyNode->GetId(), dirtyNode->GetParent() ? dirtyNode->GetParent()->GetId() : 0); } dirtyNodes_.emplace(dirtyNode); hasIdleTasks_ = true; RequestFrame(); } ``` #### 自定义组件更新 ```c++ void PipelineContext::FlushDirtyNodeUpdate() { CHECK_RUN_ON(UI); ACE_FUNCTION_TRACE(); ... // SomeTimes, customNode->Update may add some dirty custom nodes to dirtyNodes_, // use maxFlushTimes to avoid dead cycle. int maxFlushTimes = 3; while (!dirtyNodes_.empty() && maxFlushTimes > 0) { decltype(dirtyNodes_) dirtyNodes(std::move(dirtyNodes_)); for (const auto& node : dirtyNodes) { if (AceType::InstanceOf(node)) { auto customNode = AceType::DynamicCast(node); ACE_SCOPED_TRACE("CustomNodeUpdate %s", customNode->GetJSViewName().c_str()); customNode->Update(); } } --maxFlushTimes; } } ``` 然后执行自定义组件的`Update`函数 ```c++ /* foundation/arkui/ace_engine/frameworks/core/components_ng/pattern/custom/custom_node_base.cpp */ void CustomNodeBase::Update() { needRebuild_ = false; if (updateFunc_) { updateFunc_(); } } ``` [前文](#页面build执行todo)已经接受`updateFunc_`是在自定义组件创建时赋值 ```c++ RefPtr ViewPartialUpdateModelNG::CreateNode(NodeInfoPU&& info) { ... customNode->SetUpdateFunction(std::move(info.updateFunc)); ... return customNode; } /* NodeInfoPU在文件foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view.cpp中定义 */ auto updateFunction = [weak = AceType::WeakClaim(this)]() -> void { auto jsView = weak.Upgrade(); CHECK_NULL_VOID(jsView); ContainerScope scope(jsView->GetInstanceId()); if (!jsView->needsUpdate_) { return; } jsView->needsUpdate_ = false; { ACE_SCOPED_TRACE("JSView: ExecuteRerender"); jsView->jsViewFunction_->ExecuteRerender(); } for (const UpdateTask& updateTask : jsView->pendingUpdateTasks_) { ViewPartialUpdateModel::GetInstance()->FlushUpdateTask(updateTask); } jsView->pendingUpdateTasks_.clear(); }; NodeInfoPU info = { .appearFunc = std::move(appearFunc), .renderFunc = std::move(renderFunction), .updateFunc = std::move(updateFunction), .removeFunc = std::move(removeFunction), .updateNodeFunc = std::move(updateViewNodeFunction), .pageTransitionFunc = std::move(pageTransitionFunction), .reloadFunc = std::move(reloadFunction), .completeReloadFunc = std::move(completeReloadFunc), .nodeUpdateFunc = std::move(nodeUpdateFunc), .recycleCustomNodeFunc = recycleCustomNode, .setActiveFunc = std::move(setActiveFunc), .onDumpInfoFunc = std::move(onDumpInfoFunc), .hasMeasureOrLayout = jsViewFunction_->HasMeasure() || jsViewFunction_->HasLayout() || jsViewFunction_->HasMeasureSize() || jsViewFunction_->HasPlaceChildren(), .isStatic = IsStatic(), .jsViewName = GetJSViewName() }; ``` 最后执行js的rerender函数 ```c++ /* foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/jsview/js_view_functions.cpp */ JSRef jsRerenderFunc = jsObject->GetProperty("rerender") void ViewFunctions::ExecuteRerender() { JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(context_) if (jsRerenderFunc_.IsEmpty()) { LOGE("no rerender function in View!"); return; } auto func = jsRerenderFunc_.Lock(); JSRef jsThis = jsObject_.Lock(); if (!jsThis->IsUndefined()) { jsRenderResult_ = func->Call(jsThis); } else { LOGE("jsView Object is undefined and will not execute rerender function"); } } ``` ```js /* Index.js */ rerender() { this.updateDirtyElements(); } /* foundation/arkui/ace_engine/frameworks/bridge/declarative_frontend/engine/stateMgmt.js */ updateDirtyElements() { do { // see which elmtIds are managed by this View // and clean up all book keeping for them this.purgeDeletedElmtIds(); // process all elmtIds marked as needing update in ascending order. // ascending order ensures parent nodes will be updated before their children // prior cleanup ensure no already deleted Elements have their update func executed Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber).forEach(elmtId => { if (this.hasRecycleManager()) { this.UpdateElement(this.recycleManager_.proxyNodeId(elmtId)); } else { this.UpdateElement(elmtId); } this.dirtDescendantElementIds_.delete(elmtId); }); if (this.dirtDescendantElementIds_.size) { stateMgmtConsole.applicationError(`${this.debugInfo__()}: New UINode objects added to update queue while re-render! - Likely caused by @Component state change during build phase, not allowed. Application error!`); } } while (this.dirtDescendantElementIds_.size); this.dumpStateVars(); } ``` 在`UpdateElement`函数中执行状态变量对应的闭包函数,后执行`finishUpdateFunc`执行`markDirtyNode`对布局边界创建对应的布局任务。 ```js UpdateElement(elmtId) { if (elmtId == this.id__()) { // do not attempt to update itself. // a @Prop can add a dependency of the ViewPU onto itself. Ignore it. return; } // do not process an Element that has been marked to be deleted const entry = this.updateFuncByElmtId.get(elmtId); const updateFunc = entry ? entry.getUpdateFunc() : undefined; if (typeof updateFunc !== "function") { } else { const componentName = entry.getComponentName(); this.isRenderInProgress = true; updateFunc(elmtId, /* isFirstRender */ false); this.finishUpdateFunc(elmtId); this.isRenderInProgress = false; } } ``` 标脏逻辑:Row(My)-> Column(My) -> Row(Index) -> Row(index) -> page(布局边界) ### FlushTask Flush中会执行三种任务:`measure`、`layout`、`render` ```c++ void UITaskScheduler::FlushTask() { CHECK_RUN_ON(UI); ACE_SCOPED_TRACE("UITaskScheduler::FlushTask"); FlushLayoutTask(); if (NeedAdditionalLayout()) { FlushLayoutTask(); } if (!afterLayoutTasks_.empty()) { FlushAfterLayoutTask(); } /* 延迟变更jsactive,冻结相关 */ FlushDelayJsActive(); ElementRegister::GetInstance()->ClearPendingRemoveNodes(); FlushRenderTask(); } ``` ```c++ void UITaskScheduler::FlushLayoutTask(bool forceUseMainThread) { ... isLayouting_ = true; auto dirtyLayoutNodes = std::move(dirtyLayoutNodes_); PageDirtySet dirtyLayoutNodesSet(dirtyLayoutNodes.begin(), dirtyLayoutNodes.end()); // Priority task creation int64_t time = 0; for (auto&& node : dirtyLayoutNodesSet) { // need to check the node is destroying or not before CreateLayoutTask if (!node || node->IsInDestroying()) { continue; } time = GetSysTimestamp(); /* 执行 measure和layout */ node->CreateLayoutTask(forceUseMainThread); time = GetSysTimestamp() - time;} } /* 同步布局信息给RSClient,生成绘制cmd */ FlushSyncGeometryNodeTasks(); isLayouting_ = false; } ``` ```c++ /* foundation/arkui/ace_engine/frameworks/core/components_ng/base/frame_node.cpp */ void FrameNode::CreateLayoutTask(bool forceUseMainThread) { if (!isLayoutDirtyMarked_) { return; } SetRootMeasureNode(true); UpdateLayoutPropertyFlag(); SetSkipSyncGeometryNode(false); { ACE_SCOPED_TRACE("CreateTaskMeasure[%s][self:%d][parent:%d]", GetTag().c_str(), GetId(), GetParent() ? GetParent()->GetId() : 0); Measure(GetLayoutConstraint()); } { ACE_SCOPED_TRACE("CreateTaskLayout[%s][self:%d][parent:%d]", GetTag().c_str(), GetId(), GetParent() ? GetParent()->GetId() : 0); Layout(); } SetRootMeasureNode(false); } ``` ![image](out/data%20struct/date.png) > FrameNode: 基础组件节点的c++侧基类实现 > GeometryNode: 存储大小位置相关的信息,是LayoutAlgorithm的算法输出产物 > LayoutProperty: 存储布局相关信息,是LayoutAlgorithm的算法输入 > LayoutConstraintT: 存储最大最小限制,父亲节点期望大小及自身设置大小信息 > LayoutAlgorithm: 布局算法实现 > Pattern: 不同类型的组件通过pattern来扩展差异 #### Measure 了解`Measure`之前先来看看组件布局模型 ![image](out/arkui/%E7%BB%84%E4%BB%B6%E5%B8%83%E5%B1%80%E6%A8%A1%E5%9E%8B.png) > 组件区域: 组件大小,width和height属性指的是该区域 > 组件内容区: 组件区域减去Padding,是组件内容(子组件)进行大小测算的布局测算限制 > 组件内容: 组件本身占用大小。 以文本为例:如果设置了固定的width和height,组件内容区=组件区域-padding,但是文本内容通过字体引擎测算得出,会出现真实大小小于组件内容区,此时会生效align属性 > 组件布局边界: 组件区域加上Margin 组件获取到布局约束后,通过`MeasureContent`计算内容区大小,之后通过Measure计算组件大小。如Text带有内容的组件,先计算出内容区大小,通过盒模型可以快速计算组件区域。 布局跳过优化: > 1. 组件布局约束和内容区约束都未发生变化 > 2. 组件没有强制measure和layout相关的标脏 > 部分类似文本组件会存在文本内容变化的情况,此时布局算法会强制进入measure过程 `CreateLayoutTask`会执行`FrameNode`的`Measure`方法:layoutAlgorithm_ 1. 创建对应组件的`LayoutAlgorithmWrapper`保存到`layoutAlgorithm_`中 2. 如果组件处于`VisibleType::GONE`状态则跳过 3. 初始化设置`geometryNode_`的`margin`和`padding` 4. 根据组件自己的算法更新组件的大小 5. 设置layout标志`PROPERTY_UPDATE_LAYOUT` ```c++ void FrameNode::Measure(const std::optional& parentConstraint) { ACE_LAYOUT_SCOPED_TRACE("Measure[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(), GetParent() ? GetParent()->GetId() : 0, GetInspectorIdValue("").c_str()); isLayoutComplete_ = false; pattern_->BeforeCreateLayoutWrapper(); GetLayoutAlgorithm(true); if (layoutProperty_->GetVisibility().value_or(VisibleType::VISIBLE) == VisibleType::GONE) { ACE_SCOPED_TRACE("SetSkipLayout[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(), GetParent() ? GetParent()->GetId() : 0, GetInspectorIdValue("").c_str()); layoutAlgorithm_->SetSkipMeasure(); layoutAlgorithm_->SetSkipLayout(); geometryNode_->SetFrameSize(SizeF()); isLayoutDirtyMarked_ = false; return; } if (!isActive_) { layoutProperty_->UpdatePropertyChangeFlag(PROPERTY_UPDATE_MEASURE); } if (layoutAlgorithm_->SkipMeasure()) { isLayoutDirtyMarked_ = false; return; } const auto& geometryTransition = layoutProperty_->GetGeometryTransition(); if (geometryTransition != nullptr && geometryTransition->IsRunning(WeakClaim(this))) { geometryTransition->WillLayout(Claim(this)); } auto preConstraint = layoutProperty_->GetLayoutConstraint(); auto contentConstraint = layoutProperty_->GetContentLayoutConstraint(); layoutProperty_->BuildGridProperty(Claim(this)); if (parentConstraint) { ApplyConstraint(*parentConstraint); } else { CreateRootConstraint(); } layoutProperty_->UpdateContentConstraint();· geometryNode_->UpdateMargin(layoutProperty_->CreateMargin()); geometryNode_->UpdatePaddingWithBorder(layoutProperty_->CreatePaddingAndBorder()); isConstraintNotChanged_ = layoutProperty_->ConstraintEqual(preConstraint, contentConstraint); isLayoutDirtyMarked_ = false; if (isConstraintNotChanged_) { if (!CheckNeedForceMeasureAndLayout()) { ACE_SCOPED_TRACE("SkipMeasure"); layoutAlgorithm_->SetSkipMeasure(); return; } } auto size = layoutAlgorithm_->MeasureContent(layoutProperty_->CreateContentConstraint(), this); if (size.has_value()) { geometryNode_->SetContentSize(size.value()); } GetPercentSensitive(); layoutAlgorithm_->Measure(this); if (overlayNode_) { overlayNode_->Measure(layoutProperty_->CreateChildConstraint()); } UpdatePercentSensitive(); // check aspect radio. if (pattern_ && pattern_->IsNeedAdjustByAspectRatio()) { const auto& magicItemProperty = layoutProperty_->GetMagicItemProperty(); auto aspectRatio = magicItemProperty->GetAspectRatioValue(); // Adjust by aspect ratio, firstly pick height based on width. It means that when width, height and // aspectRatio are all set, the height is not used. auto width = geometryNode_->GetFrameSize().Width(); auto height = width / aspectRatio; geometryNode_->SetFrameSize(SizeF({ width, height })); } layoutProperty_->UpdatePropertyChangeFlag(PROPERTY_UPDATE_LAYOUT); } ``` 整个Measure的过程会以深度优先算法遍历组件树上的每个节点layoutAlgorithm_的Measure方法。 以`Row`组件为例:组件的`XxxModelNG::Create`函数中创建`FrameNode`并传入对应的`Pattern` ```c++ void RowModelNG::Create(const std::optional& space, AlignDeclaration*, const std::string& tag) { auto* stack = ViewStackProcessor::GetInstance(); auto nodeId = stack->ClaimNodeId(); ACE_LAYOUT_SCOPED_TRACE("Create[%s][self:%d]", V2::ROW_ETS_TAG, nodeId); auto frameNode = FrameNode::GetOrCreateFrameNode( V2::ROW_ETS_TAG, nodeId, []() { return AceType::MakeRefPtr(false); }); stack->Push(frameNode); ACE_UPDATE_LAYOUT_PROPERTY(LinearLayoutProperty, FlexDirection, FlexDirection::ROW); CHECK_NULL_VOID(space); if (GreatOrEqual(space->Value(), 0.0)) { ACE_UPDATE_LAYOUT_PROPERTY(LinearLayoutProperty, Space, space.value()); } } ``` 1. `Row`组件的`FrameNode`创建会绑定`LinearLayoutPattern` 2. `LinearLayoutPattern`创建`LinearLayoutAlgorithm`(继承自`FlexLayoutAlgorithm`) 3. `FlexLayoutProperty`存储FlexLayout布局所需的属性数据:主轴方向、主轴对齐属性、交叉轴对齐属性、子元素间距等 ```c++ ACE_DEFINE_PROPERTY_GROUP(FlexLayoutAttribute, FlexLayoutAttribute); ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FlexLayoutAttribute, FlexDirection, FlexDirection, PROPERTY_UPDATE_MEASURE); ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FlexLayoutAttribute, MainAxisAlign, FlexAlign, PROPERTY_UPDATE_MEASURE); ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FlexLayoutAttribute, CrossAxisAlign, FlexAlign, PROPERTY_UPDATE_MEASURE); ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FlexLayoutAttribute, Space, Dimension, PROPERTY_UPDATE_MEASURE); ``` ##### FlexLayoutAlgorithm FlexLayoutAlgorithm::Measure布局算法主要分为五个过程: 一、TravelChildrenFlexProps 遍历子节点,计算若干全局属性值,并收集信息填充magicNodes_(优先级为key,节点数组为value)、magicNodeWeights_ 二、MeasureAndCleanMagicNodes 第一个if分支: 1. 首先以优先级遍历magicNodes_中layoutWeight为0的节点,去除放不下的,Measure能够放下的; 2. 然后以优先级遍历magicNodes_中layoutWeight不为0的节点,每次遇到放不下,将优先级最低的i二点全部去除后重新尝试 3. 最后对于1和2中保留active的节点收集到secondaryMeasureList_中,对于2中保留的active节点,先做一次Measure 第二、三个if分支: 处理未设置layoutWeight的情况,首先通过UpdateChildLayoutConstrainByFlexBasis用flexbasis设置更新组件主轴尺寸 1. 优先级有区别:去除放不下的,Measure放的下的;若有优先级,必有摘去节点之后的空间,只使用flexGrow+flexGrow,Measure后收集到secondaryMeasureList_ 2. 优先级相同:累加flexShrink、flexGrow,直接Measure后收集到secondaryMeasureList_ 三、SecondaryMeasureByProperty 1. 循环遍历secondaryMeasureList_,每当遇到flexShrink生效且导致组件布局空间不足的子节点,就将节点返回主轴剩余,size置0,isActive置false再从头开始遍历,直到secondaryMeasureList_完整处理一遍 2. 遍历处理后的secondaryMeasureList_,对每个节点的Measure,若二次Measure执行了,在交叉轴进行更新 四、MeasureOutOfLayoutChildren 对布局流之外的节点进行Measure 五、AdjustTotalAllocatedSize 用上述Measure结果更新allocatedSize 算法复杂只可意会,最终Measure会将计算好的布局约束保存在`layoutProperty_` `geometryNode_`等数据结构中待同步给RSNode #### Layout 主要计算自己子组件的位置。与Measure不同的是,Measure会传递布局约束给子组件,由子组件决定内容区和组件大小而Layout则是由本身容器决定。 layout最后一步是同步位置大小等信息给RSNode生成绘制cmd,主要实现在 ```c++ auto task = [weak = WeakClaim(this), needSync = needSyncRsNode, dirtyConfig = config]() { auto frameNode = weak.Upgrade(); CHECK_NULL_VOID(frameNode); frameNode->SyncGeometryNode(needSync, dirtyConfig); }; pipeline->AddSyncGeometryNodeTask(task); ``` 添加同步任务到安全区域逻辑处理之后处理 ```c++ void RosenRenderContext::SyncGeometryFrame(const RectF& paintRect) { CHECK_NULL_VOID(rsNode_); rsNode_->SetBounds(paintRect.GetX(), paintRect.GetY(), paintRect.Width(), paintRect.Height()); if (rsTextureExport_) { rsTextureExport_->UpdateBufferInfo(paintRect.GetX(), paintRect.GetY(), paintRect.Width(), paintRect.Height()); } if (handleChildBounds_) { SetChildBounds(paintRect); } if (useContentRectForRSFrame_) { SetContentRectToFrame(paintRect); } else { rsNode_->SetFrame(paintRect.GetX(), paintRect.GetY(), paintRect.Width(), paintRect.Height()); } if (frameOffset_.has_value()) { rsNode_->SetFrame(paintRect.GetX() + frameOffset_->GetX(), paintRect.GetY() + frameOffset_->GetY(), paintRect.Width(), paintRect.Height()); } auto host = GetHost(); CHECK_NULL_VOID(host); host->OnSyncGeometryFrameFinish(paintRect); } ``` 通过rsNode_->SetBounds同步组件大小,通过rsNode_->SetFrame同步内容区大小,完成ArkUI组件树与图形渲染树的信息同步。 ##### OnSizeChange回调 `TriggerOnSizeChangeCallback`回调应用注册的OnSizeChange ```c++ void FrameNode::SyncGeometryNode(bool needSyncRsNode, const DirtySwapConfig& config) { if (SystemProperties::GetSyncDebugTraceEnabled()) { ACE_LAYOUT_TRACE_BEGIN("SyncGeometryNode[%s][self:%d][parent:%d][key:%s]", GetTag().c_str(), GetId(), GetParent() ? GetParent()->GetId() : 0, GetInspectorIdValue("").c_str()); ACE_LAYOUT_TRACE_END() } // update border. if (layoutProperty_->GetBorderWidthProperty()) { if (!renderContext_->HasBorderColor()) { BorderColorProperty borderColorProperty; borderColorProperty.SetColor(Color::BLACK); renderContext_->UpdateBorderColor(borderColorProperty); } if (!renderContext_->HasBorderStyle()) { BorderStyleProperty borderStyleProperty; borderStyleProperty.SetBorderStyle(BorderStyle::SOLID); renderContext_->UpdateBorderStyle(borderStyleProperty); } if (!renderContext_->HasDashGap()) { BorderWidthProperty dashGapProperty; dashGapProperty.SetBorderWidth(Dimension(-1)); renderContext_->UpdateDashGap(dashGapProperty); } if (!renderContext_->HasDashWidth()) { BorderWidthProperty dashWidthProperty; dashWidthProperty.SetBorderWidth(Dimension(-1)); renderContext_->UpdateDashWidth(dashWidthProperty); } if (layoutProperty_->GetLayoutConstraint().has_value()) { renderContext_->UpdateBorderWidthF(ConvertToBorderWidthPropertyF(layoutProperty_->GetBorderWidthProperty(), ScaleProperty::CreateScaleProperty(), layoutProperty_->GetLayoutConstraint()->percentReference.Width())); } else { renderContext_->UpdateBorderWidthF(ConvertToBorderWidthPropertyF(layoutProperty_->GetBorderWidthProperty(), ScaleProperty::CreateScaleProperty(), PipelineContext::GetCurrentRootWidth())); } } pattern_->OnSyncGeometryNode(config); if (needSyncRsNode) { pattern_->BeforeSyncGeometryProperties(config); renderContext_->SyncGeometryProperties(RawPtr(geometryNode_), true, layoutProperty_->GetPixelRound()); if (SystemProperties::GetSyncDebugTraceEnabled()) { ACE_LAYOUT_TRACE_BEGIN("TriggerOnSizeChangeNode:[%s][id:%d]", GetTag().c_str(), GetId()); ACE_LAYOUT_TRACE_END() } TriggerOnSizeChangeCallback(); } // update background if (builderFunc_) { auto builderNode = builderFunc_(); auto columnNode = FrameNode::CreateFrameNode(V2::COLUMN_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr(true)); if (builderNode) { builderNode->MountToParent(columnNode); } SetBackgroundLayoutConstraint(columnNode); renderContext_->CreateBackgroundPixelMap(columnNode); builderFunc_ = nullptr; backgroundNode_ = columnNode; } // update focus state UpdateFocusState(); // rebuild child render node. if (!isLayoutNode_) { RebuildRenderContextTree(); } /* Adjust components' position which have been set grid properties */ AdjustGridOffset(); } ``` #### Render 类似Image、Text等需要自绘制的组件需要继承XxxModifier、XxxPaintMethod类实现自己的绘制逻辑。 PaintMethod通过重写对应组件Pattern的CreateNodePaintMethod方法创建 组件在PaintMethod中GetContentDrawFunction方法真正实现绘制逻辑。Modifier则是通过调用UpdateXxxModifier的方法更新成员变量属性值,同步给RS触发对应的onDraw方法。 ```c++ /* Blank组件PaintMethon实现 -- 通过GetContentDrawFunction */ class ACE_EXPORT BlankPaintMethod : public NodePaintMethod { DECLARE_ACE_TYPE(BlankPaintMethod, NodePaintMethod) public: BlankPaintMethod() = default; ~BlankPaintMethod() override = default; CanvasDrawFunction GetContentDrawFunction(PaintWrapper* paintWrapper) override { return [weak = WeakClaim(this), paintWrapper](RSCanvas& canvas) { auto blank = weak.Upgrade(); if (blank) { blank->PaintRect(canvas, paintWrapper); } }; } void PaintRect(RSCanvas& canvas, PaintWrapper* paintWrapper); } ``` ```c++ /* Image组件PaintMethon实现 -- 通过Modifier */ class ACE_EXPORT ImagePaintMethod : public NodePaintMethod { DECLARE_ACE_TYPE(ImagePaintMethod, NodePaintMethod) public: explicit ImagePaintMethod( const RefPtr& canvasImage, const ImagePaintMethodConfig& imagePainterMethodConfig = {}) : selected_(imagePainterMethodConfig.selected), sensitive_(imagePainterMethodConfig.sensitive), canvasImage_(canvasImage), interpolationDefault_(imagePainterMethodConfig.interpolation), imageOverlayModifier_(imagePainterMethodConfig.imageOverlayModifier), imageContentModifier_(imagePainterMethodConfig.imageContentModifier) {} ~ImagePaintMethod() override = default; RefPtr GetOverlayModifier(PaintWrapper* paintWrapper) override; void UpdateOverlayModifier(PaintWrapper* paintWrapper) override; RefPtr GetContentModifier(PaintWrapper* paintWrapper) override; void UpdateContentModifier(PaintWrapper* paintWrapper) override; } ``` 一、节点标脏加入绘制队列 ```c++ void FrameNode::MarkNeedRender(bool isRenderBoundary) { auto context = GetContext(); CHECK_NULL_VOID(context); // If it has dirtyLayoutBox, need to mark dirty after layout done. paintProperty_->UpdatePropertyChangeFlag(PROPERTY_UPDATE_RENDER); if (isRenderDirtyMarked_ || isLayoutDirtyMarked_) { return; } isRenderDirtyMarked_ = true; if (isRenderBoundary) { context->AddDirtyRenderNode(Claim(this)); return; } auto parent = GetAncestorNodeOfFrame(); if (parent) { parent->MarkDirtyNode(PROPERTY_UPDATE_RENDER_BY_CHILD_REQUEST); } } ``` 二、管线依次执行绘制任务 ```c++ void UITaskScheduler::FlushRenderTask(bool forceUseMainThread) { CHECK_RUN_ON(UI); if (FrameReport::GetInstance().GetEnable()) { FrameReport::GetInstance().BeginFlushRender(); } auto dirtyRenderNodes = std::move(dirtyRenderNodes_); // Priority task creation int64_t time = 0; for (auto&& pageNodes : dirtyRenderNodes) { ACE_SCOPED_TRACE("FlushRenderTask %zu", pageNodes.second.size()); for (auto&& node : pageNodes.second) { if (!node) { continue; } if (node->IsInDestroying()) { continue; } time = GetSysTimestamp(); auto task = node->CreateRenderTask(forceUseMainThread); if (task) { if (forceUseMainThread || (task->GetTaskThreadType() == MAIN_TASK)) { (*task)(); time = GetSysTimestamp() - time; if (frameInfo_ != nullptr) { frameInfo_->AddTaskInfo(node->GetTag(), node->GetId(), time, FrameInfo::TaskType::RENDER); } } } } } } ``` 三、绘制流程 ```c++ void PaintWrapper::FlushRender() { auto renderContext = renderContext_.Upgrade(); CHECK_NULL_VOID(renderContext); auto contentModifier = DynamicCast(nodePaintImpl_ ? nodePaintImpl_->GetContentModifier(this) : nullptr); if (contentModifier) { nodePaintImpl_->UpdateContentModifier(this); if (extensionHandler_) { extensionHandler_->InvalidateRender(); } } auto overlayModifier = nodePaintImpl_ ? nodePaintImpl_->GetOverlayModifier(this) : nullptr; if (overlayModifier) { nodePaintImpl_->UpdateOverlayModifier(this); if (extensionHandler_) { extensionHandler_->OverlayRender(); } } auto foregroundModifier = nodePaintImpl_ ? nodePaintImpl_->GetForegroundModifier(this) : nullptr; if (foregroundModifier) { nodePaintImpl_->UpdateForegroundModifier(this); if (extensionHandler_) { extensionHandler_->ForegroundRender(); } } renderContext->StartRecording(); auto contentDraw = nodePaintImpl_ ? nodePaintImpl_->GetContentDrawFunction(this) : nullptr; auto foregroundDraw = nodePaintImpl_ ? nodePaintImpl_->GetForegroundDrawFunction(this) : nullptr; auto overlayDraw = nodePaintImpl_ ? nodePaintImpl_->GetOverlayDrawFunction(this) : nullptr; if (extensionHandler_) { auto layoutSize = GetGeometryNode()->GetFrameSize(); auto width = layoutSize.Width(); auto height = layoutSize.Height(); if (!contentModifier) { if (contentDraw) { extensionHandler_->SetInnerDrawImpl([contentDraw = std::move(contentDraw)]( DrawingContext& context) { contentDraw(context.canvas); }); } renderContext->FlushContentDrawFunction( [extensionHandler = RawPtr(extensionHandler_), width, height](RSCanvas& canvas) { DrawingContext context = { canvas, width, height }; extensionHandler->Draw(context); }); } if (!foregroundModifier) { if (foregroundDraw) { extensionHandler_->SetInnerForegroundDrawImpl( [foregroundDraw = std::move(foregroundDraw)]( DrawingContext& context) { foregroundDraw(context.canvas); }); } renderContext->FlushForegroundDrawFunction( [extensionHandler = RawPtr(extensionHandler_), width, height](RSCanvas& canvas) { DrawingContext context = { canvas, width, height }; extensionHandler->ForegroundDraw(context); }); } if (!overlayModifier) { if (overlayDraw) { extensionHandler_->SetInnerOverlayDrawImpl( [overlayDraw = std::move(overlayDraw)]( DrawingContext& context) { overlayDraw(context.canvas); }); } renderContext->FlushOverlayDrawFunction( [extensionHandler = RawPtr(extensionHandler_), width, height](RSCanvas& canvas) { DrawingContext context = { canvas, width, height }; extensionHandler->OverlayDraw(context); }); } } else { if (contentDraw && !contentModifier) { renderContext->FlushContentDrawFunction(std::move(contentDraw)); } if (foregroundDraw && !foregroundModifier) { renderContext->FlushForegroundDrawFunction(std::move(foregroundDraw)); } if (overlayDraw && !overlayModifier) { renderContext->FlushOverlayDrawFunction(std::move(overlayDraw)); } } if (renderContext->GetAccessibilityFocus().value_or(false)) { renderContext->PaintAccessibilityFocus(); } renderContext->StopRecordingIfNeeded(); } ``` 绘制执行的Modifier顺序依次为contentModifier、overlayModifier、foregroundModifier。 绘制执行的DrawFunction顺序依次为ContentDrawFunction、ForegroundDrawFunction、OverlayDrawFunction。renderContext->StartRecording和renderContext->StopRecordingIfNeeded通知图形开启关闭绘制指令。 以Image组件为例:nodePaintImpl_为ImagePaintMethod实例,会依次执行ImagePaintMethod的ContentModifier和OverlayModifier。 ##### 隐私遮罩实现 1. 组件创建时设置`Obccured`属性,并`MarkDirty`标脏 2. 下个`Vsync`信号触发管线流程`FlushRenderTask`,创建`ImageContentModifier`并包装成`ContentModifierAdapter`继承RS的`RSContentStyleModifier` 3. 触发管线`RSModifier::Draw`过程,最终回调`ImageContentModifier`的onDraw方法 流程图如下: ![image](out/obscured/obscured.png) ### FlushCommand 该过程将组件属性设置、Measure、layout等过程生成的不同类型的绘制CMD同步给RS进程,由RS进一步生成处理最终渲染。 #### CMD类型 ```c++ enum class RSModifierType : int16_t { INVALID = 0, // 0 BOUNDS, // 1 FRAME, // 2 POSITION_Z, // 3 PIVOT, // 4 PIVOT_Z, // 5 QUATERNION, // 6 ROTATION, // 7 ROTATION_X, // 8 ROTATION_Y, // 9 CAMERA_DISTANCE, // 10 SCALE, // 11 TRANSLATE, // 12 TRANSLATE_Z, // 13 SUBLAYER_TRANSFORM, // 14 CORNER_RADIUS, // 15 ALPHA, // 16 ALPHA_OFFSCREEN, // 17 FOREGROUND_COLOR, // 18 BACKGROUND_COLOR, // 19 BACKGROUND_SHADER, // 20 BG_IMAGE, // 21 BG_IMAGE_WIDTH, // 22 BG_IMAGE_HEIGHT, // 23 BG_IMAGE_POSITION_X, // 24 BG_IMAGE_POSITION_Y, // 25 SURFACE_BG_COLOR, // 26 BORDER_COLOR, // 27 BORDER_WIDTH, // 28 BORDER_STYLE, // 29 FILTER, // 30 BACKGROUND_FILTER, // 31 LINEAR_GRADIENT_BLUR_PARA, // 32 DYNAMIC_LIGHT_UP_RATE, // 33 DYNAMIC_LIGHT_UP_DEGREE, // 34 FRAME_GRAVITY, // 35 CLIP_RRECT, // 36 CLIP_BOUNDS, // 37 CLIP_TO_BOUNDS, // 38 CLIP_TO_FRAME, // 39 VISIBLE, // 40 SHADOW_COLOR, // 41 SHADOW_OFFSET_X, // 42 SHADOW_OFFSET_Y, // 43 SHADOW_ALPHA, // 44 SHADOW_ELEVATION, // 45 SHADOW_RADIUS, // 46 SHADOW_PATH, // 47 SHADOW_MASK, // 48 SHADOW_COLOR_STRATEGY, // 49 MASK, // 50 SPHERIZE, // 51 LIGHT_UP_EFFECT, // 52 PIXEL_STRETCH, // 53 PIXEL_STRETCH_PERCENT, // 54 USE_EFFECT, // 55 COLOR_BLEND_MODE, // 56 SANDBOX, // 57 GRAY_SCALE, // 58 BRIGHTNESS, // 59 CONTRAST, // 60 SATURATE, // 61 SEPIA, // 62 INVERT, // 63 AIINVERT, // 64 HUE_ROTATE, // 65 COLOR_BLEND, // 66 PARTICLE, // 67 SHADOW_IS_FILLED, // 68 OUTLINE_COLOR, // 69 OUTLINE_WIDTH, // 70 OUTLINE_STYLE, // 71 OUTLINE_RADIUS, // 72 USE_SHADOW_BATCHING, // 73 GREY_COEF1, // 74 GREY_COEF2, // 75 LIGHT_INTENSITY, // 76 LIGHT_POSITION, // 77 ILLUMINATED_BORDER_WIDTH, // 78 ILLUMINATED_TYPE, // 79 BLOOM, // 80 CUSTOM, // 81 EXTENDED, // 82 TRANSITION, // 83 BACKGROUND_STYLE, // 84 CONTENT_STYLE, // 85 FOREGROUND_STYLE, // 86 OVERLAY_STYLE, // 87 NODE_MODIFIER, // 88 ENV_FOREGROUND_COLOR, // 89 ENV_FOREGROUND_COLOR_STRATEGY, // 90 GEOMETRYTRANS, // 91 #ifdef USE_ROSEN_DRAWING MAX_RS_MODIFIER_TYPE, #endif }; ``` #### CMD生成与发送 上文讲到SyncGeometryFrame会同步大小和位置信息给RS便是通过此方法,具体流程如下: ![image](out/rscmd/rscmd.png) ### 组件可见区域变化事件 #### 功能概述 屏幕中的显示区域面积发生变化(上下滑动,缩放等)触发的事件,多用于根据组件在屏幕的可见比例动态决定预加载条目提高滑动性能或者计算组件的曝光度。 > 注意: > 该接口并不能做到肉眼效果上的可见性判断,仅提供了自身与祖先节点的相对裁切区域面积变化的通知 #### 接口 onVisibleAreaChange(radios: Array, event: (isVisible: boolean, currentRadio: number) => void) | 参数 | 描述 | 取值范围| | ---- | --- | ------ | |radios|设置触发回调的函数条件,每个阈值代表组件可见面积与组件自身面积的比值。当比值接近阈值时,触发回调|[0.0, 1.0]| |event|isVisible:表示组件可见面积与自身面积比值与上一次相比的变化情况,比值大为true,比值变小为false|NA| #### 注册和执行流程 ![image desc](./out/visiableareachange/nodetree.png) #### 算法优化 ![image desc](./out/visiableareachange/suanfa.png) 1. Test1组件上注册了onVisibleAreaChange 2. 对虚线框中的所有祖先节点进行区域裁切计算,得到的裁切区域面积为自身可见面积 3. 不会考虑Text2 和 JsView两个兄弟节点的遮挡情况 4. 当计算Test2组件时,由于父组件Column已经计算过,只需从缓存中取出结果做交际即可 ```c++ CacheVisibleRectResult FrameNode::GetCacheVisibleRect(uint64_t timestamp, bool logFlag) { RefPtr parentUi = GetAncestorNodeOfFrame(true); auto rectToParent = GetPaintRectWithTransform(); auto scale = GetTransformScale(); if (!parentUi || IsWindowBoundary()) { cachedVisibleRectResult_ = {timestamp, {rectToParent.GetOffset(), rectToParent, rectToParent, scale, rectToParent, rectToParent}}; return cachedVisibleRectResult_.second; } CacheVisibleRectResult result; if (parentUi->cachedVisibleRectResult_.first == timestamp) { auto parentCacheVisibleRectResult = parentUi->cachedVisibleRectResult_.second; result = CalculateCacheVisibleRect(parentCacheVisibleRectResult, parentUi, rectToParent, scale, timestamp); } else { CacheVisibleRectResult parentCacheVisibleRectResult = parentUi->GetCacheVisibleRect(timestamp, logFlag); result = CalculateCacheVisibleRect(parentCacheVisibleRectResult, parentUi, rectToParent, scale, timestamp); } return result; } ``` #### 规格约束 |序号|详细规格| |---|---| |1|仅提供自身节点相对于所有祖先节点的相对裁切面积与自身面积的比值| |2|不支持兄弟节点对自身,以及搜友祖先的兄弟节点对祖先节点的遮挡计算| |3|不支持非挂树节点的可见性变化计算,如预加载的节点、通过overlay能力挂载的自定义builder节点| |4|比率阈值比较为浮点比较,存在微小的精度误差|