鸿蒙ACE-ArkUI构建(三)、页面加载过程

438 阅读4分钟

页面加载

onWindowStageCreate->loadContent

在EntryAbility的onWindowStageCreate生命周期回调中,windowStage调用loadContent 完成后续的UI树初始化

image.png

ArkUI-X

这一块我们需要借助ArkUI-X来看。我们首先看一下Android的启动流程图

gitee.com/arkui-x/doc…

可以看到进入到鸿蒙的流程类加载顺序依次是,AbilityStage、JsAbility、WindowStage、Window、UIContent、AceContainer、Pipeline

image.png

JsAbility

void JsAbility::OnWindowStageCreated()

前面的步骤和特定平台我们可以略过,直接看JsAbility的OnWindowStageCreated方法。在这个方法里面创建了一个JsWindowStage

image.png

实际上创建WindowStage的是Rosen里面的同名方法

image.png

WindowStage

NativeValue* CreateJsWindowStage(NativeEngine& engine, std::shared_ptrRosen::WindowStage WindowStage)

在这个方法里,我们可以看到loadContent方法绑定到了JsWindowStage::LoadContent方法

image.png

NativeValue* JsWindowStage::LoadContent(NativeEngine* engine, NativeCallbackInfo* info)

调用了重载的同名方法

image.png

异步任务执行完毕,会调用LoadContentTask加载内容

image.png

LoadContentTask最终是调用了window的SetUIContent

image.png

Window

WMError Window::SetUIContent(const std::string& contentInfo, NativeEngine* engine, NativeValue* storage, bool isdistributed, AbilityRuntime::Platform::Ability* ability)

创建了UIContent并调用了Initialize和Foreground方法

image.png

可以看到,创建了UIContentImpl类的一个实例

image.png

UIContentImpl是特定于平台的实现

void UIContentImpl::Foreground()

首先获取了一个Container,然后从container里面获取了pipelineContext

image.png

ACE

上面我们通过ArkUI-X追踪到了UIContentImpl,实际上这一块在iOS、Android、鸿蒙上都有各自的同名类。我们现在可以回到ACE看一下代码了

UIContentImpl

UIContentImpl有好几个重载的Initialize方法。上面Initialize的方法签名和这个方法签名一样

内部调用了InitializeInner方法

image.png

UIContentErrorCode UIContentImpl::InitializeInner( OHOS::Rosen::Window* window, const std::string& contentInfo, napi_value storage, bool isNamedRouter)

调用了CommontInitialize方法初始化之后,调用了Platform::AceContainer::RunPage加载页面

image.png

UIContentErrorCode UIContentImpl::CommonInitialize( OHOS::Rosen::Window* window, const std::string& contentInfo, napi_value storage)

CommonInitialize 内部最终都会调用 Platform::AceContainer::SetViewNew(aceView, density, 0, 0, window_)

CommonInitialize内部还有一些其他逻辑

image.png

AceContainer

UIContentErrorCode AceContainer::SetViewNew( AceView* view, double density, float width, float height, sptrOHOS::Rosen::Window rsWindow)

SetView会调用AttachView,进而在AttachView内部初始化pipeLineContext

image.png

void AceContainer::AttachView(std::shared_ptr window, AceView* view, double density, float width, float height, uint32_t windowId, UIEnvCallback callback)

image.png

void AceContainer::InitializeFrontend()

根据不同的设置,创建或共享了JS引擎。同时页创建了一个前端渲染并调用Initialize进行初始化

image.png

DeclarativeFrontend

bool DeclarativeFrontend::Initialize(FrontendType type, const RefPtr& taskExecutor)

声明式前端的初始化,首先初始化了一下代理回调,然后初始化了JS引擎

image.png

JsiDeclarativeEngine

bool JsiDeclarativeEngine::Initialize(const RefPtr& delegate)

可以看到一些JSView和C++类绑定的工作也是在引擎初始化的时候就做的

image.png

CheckUVLoop

我们看到最终是调用了nativeEngine,并且在这里还调用了CheckUVLoop。我们简单看一下CheckUIVLoop

这个类在 gitee.com/openharmony… 中

void NativeEngine::CheckUVLoop()

可以看到CheckUVLoop调用了UVThreadRunner,最终的loop是通过PostLoopTask运行的。

image.png

void NativeEngine::PostLoopTask()

可以看到最终也是通过libuv来完成的loop循环

image.png

AceContainer

上面调用UIContentImpl的InitializeInner完成了初始化工作,万事俱备之后就调用AceContainer的RunPage将内容进行加载

UIContentErrorCode AceContainer::RunPage( int32_t instanceId, const std::string& content, const std::string& params, bool isNamedRouter)

可以看到RunPage内部根据是命名路由还是路径路由调用了不同的方法

image.png

DeclarativeFrontend

UIContentErrorCode DeclarativeFrontend::RunPage(const std::string& url, const std::string& params)

RunPage内部最终调用了delegate的RunPage方法

image.png

FrontendDelegateDeclarative

UIContentErrorCode FrontendDelegateDeclarative::RunPage( const std::string& url, const std::string& params, const std::string& profile, bool isNamedRouter)

解析一些配置信息,并通过routerManager加载页面

image.png

PageRouterManager

void PageRouterManager::RunPage(const std::string& url, const std::string& params)

RunPage里面根据跳转的url是否以@bundle开头判断分别调用了不同的方法进入页面

image.png

image.png

void PageRouterManager::LoadPage(int32_t pageId, const RouterPageInfo& target, bool needHideLast, bool needTransition)

方法里最终调用了loadJs_这个函数。这个函数是通过我们之前提到的 DeclarativeFrontend::Initialize 里面设置的

image.png

VsyncCallback 注册

VsyncCallback是注册到PipelineContext上的,当Vsync信号过来时会调用OnVsyncEvent

image.png

void PipelineBase::OnVsyncEvent(uint64_t nanoTimestamp, uint32_t frameCount)

image.png

void PipelineContext::FlushVsync(uint64_t nanoTimestamp, uint32_t frameCount)

可以看到先处理事件,然后做动画以及自定义动画的回调任务

image.png

创建 FrameNode 树 Mark dirty

PageRouterManager

void PageRouterManager::LoadPage(int32_t pageId, const RouterPageInfo& target, bool needHideLast, bool needTransition)

继续回到我们之前的PageRouterMangager的LoadPage方法。该方法除了加载JS,最后还调用了OnPageReady这个方法

image.png

bool PageRouterManager::OnPageReady( const RefPtr& pageNode, bool needHideLast, bool needTransition, bool isCardRouter, int64_t cardId)

PageRouterManager最终调用了stageManager的PushPage方法。

image.png

StageManager

bool StageManager::PushPage(const RefPtr& node, bool needHideLast, bool needTransition)

这里挂载到父节点,并执行一些声明周期方法。最后将UI 节点标记为dirty,之后 Vsync 信号到来会重新完成 dirty nodes 节点的布局和重绘

image.png

image.png

void FrameNode::MarkDirtyNode(PropertyChangeFlag extraFlag)

image.png

PipelineContext

void PipelineContext::AddDirtyPropertyNode(const RefPtr& dirtyNode)

我们发现最终将节点添加进了dirtyPropertyNodes。当Vsync信号到来的时候就会刷新dirtyPropertyNodes

image.png

我们逆向找一下dirtyPropertyNodes怎么使用的。

void PipelineContext::FlushVsync(uint64_t nanoTimestamp, uint32_t frameCount)

FlushVsync内部会调用FlushBuild

image.png

void PipelineContext::FlushBuild()

FlushBuild内部又会调用FlushDirtyNodeUpdate

image.png

void PipelineContext::FlushDirtyNodeUpdate()

image.png

小结

  1. ace eventloop和Node.js一样,底层是使用libuv实现的
  2. 它的更新机制和Flutter及其他声明式框架一样,都是先标记dirty,然后在下一次vsync到来时进行更新

参考资料

  1. 声明式范式的语法编译转换,语法验证等 https://gitee.com/openharmony/developtools_ace_ets2bundle
  2. ArkUI引擎 https://gitee.com/openharmony/arkui_ace_engine
  3. 原生模块扩展开发框架 https://gitee.com/openharmony/arkui_napi
  4. ArkUIX gitee.com/arkui-x
  5. libuv https://github.com/libuv/libuv
  6. gitee.com/arkui-x/doc…
  7. openharmony文档 https://gitee.com/openharmony/docs/tree/master/zh-cn/readme