页面加载
onWindowStageCreate->loadContent
在EntryAbility的onWindowStageCreate
生命周期回调中,windowStage调用loadContent 完成后续的UI树初始化
ArkUI-X
这一块我们需要借助ArkUI-X来看。我们首先看一下Android的启动流程图
可以看到进入到鸿蒙的流程类加载顺序依次是,AbilityStage、JsAbility、WindowStage、Window、UIContent、AceContainer、Pipeline
JsAbility
void JsAbility::OnWindowStageCreated()
前面的步骤和特定平台我们可以略过,直接看JsAbility的OnWindowStageCreated方法。在这个方法里面创建了一个JsWindowStage
实际上创建WindowStage的是Rosen里面的同名方法
WindowStage
NativeValue* CreateJsWindowStage(NativeEngine& engine, std::shared_ptrRosen::WindowStage WindowStage)
在这个方法里,我们可以看到loadContent方法绑定到了JsWindowStage::LoadContent方法
NativeValue* JsWindowStage::LoadContent(NativeEngine* engine, NativeCallbackInfo* info)
调用了重载的同名方法
异步任务执行完毕,会调用LoadContentTask加载内容
LoadContentTask最终是调用了window的SetUIContent
Window
WMError Window::SetUIContent(const std::string& contentInfo, NativeEngine* engine, NativeValue* storage, bool isdistributed, AbilityRuntime::Platform::Ability* ability)
创建了UIContent并调用了Initialize和Foreground方法
可以看到,创建了UIContentImpl类的一个实例
UIContentImpl是特定于平台的实现
void UIContentImpl::Foreground()
首先获取了一个Container,然后从container里面获取了pipelineContext
ACE
上面我们通过ArkUI-X追踪到了UIContentImpl,实际上这一块在iOS、Android、鸿蒙上都有各自的同名类。我们现在可以回到ACE看一下代码了
UIContentImpl
UIContentImpl有好几个重载的Initialize方法。上面Initialize的方法签名和这个方法签名一样
内部调用了InitializeInner方法
UIContentErrorCode UIContentImpl::InitializeInner( OHOS::Rosen::Window* window, const std::string& contentInfo, napi_value storage, bool isNamedRouter)
调用了CommontInitialize方法初始化之后,调用了Platform::AceContainer::RunPage加载页面
UIContentErrorCode UIContentImpl::CommonInitialize( OHOS::Rosen::Window* window, const std::string& contentInfo, napi_value storage)
CommonInitialize 内部最终都会调用 Platform::AceContainer::SetViewNew(aceView, density, 0, 0, window_)
CommonInitialize内部还有一些其他逻辑
AceContainer
UIContentErrorCode AceContainer::SetViewNew( AceView* view, double density, float width, float height, sptrOHOS::Rosen::Window rsWindow)
SetView会调用AttachView,进而在AttachView内部初始化pipeLineContext
void AceContainer::AttachView(std::shared_ptr window, AceView* view, double density, float width, float height, uint32_t windowId, UIEnvCallback callback)
void AceContainer::InitializeFrontend()
根据不同的设置,创建或共享了JS引擎。同时页创建了一个前端渲染并调用Initialize进行初始化
DeclarativeFrontend
bool DeclarativeFrontend::Initialize(FrontendType type, const RefPtr& taskExecutor)
声明式前端的初始化,首先初始化了一下代理回调,然后初始化了JS引擎
JsiDeclarativeEngine
bool JsiDeclarativeEngine::Initialize(const RefPtr& delegate)
可以看到一些JSView和C++类绑定的工作也是在引擎初始化的时候就做的
CheckUVLoop
我们看到最终是调用了nativeEngine,并且在这里还调用了CheckUVLoop。我们简单看一下CheckUIVLoop
这个类在 gitee.com/openharmony… 中
void NativeEngine::CheckUVLoop()
可以看到CheckUVLoop调用了UVThreadRunner,最终的loop是通过PostLoopTask运行的。
void NativeEngine::PostLoopTask()
可以看到最终也是通过libuv来完成的loop循环
AceContainer
上面调用UIContentImpl的InitializeInner完成了初始化工作,万事俱备之后就调用AceContainer的RunPage将内容进行加载
UIContentErrorCode AceContainer::RunPage( int32_t instanceId, const std::string& content, const std::string& params, bool isNamedRouter)
可以看到RunPage内部根据是命名路由还是路径路由调用了不同的方法
DeclarativeFrontend
UIContentErrorCode DeclarativeFrontend::RunPage(const std::string& url, const std::string& params)
RunPage内部最终调用了delegate的RunPage方法
FrontendDelegateDeclarative
UIContentErrorCode FrontendDelegateDeclarative::RunPage( const std::string& url, const std::string& params, const std::string& profile, bool isNamedRouter)
解析一些配置信息,并通过routerManager加载页面
PageRouterManager
void PageRouterManager::RunPage(const std::string& url, const std::string& params)
RunPage里面根据跳转的url是否以@bundle开头判断分别调用了不同的方法进入页面
void PageRouterManager::LoadPage(int32_t pageId, const RouterPageInfo& target, bool needHideLast, bool needTransition)
方法里最终调用了loadJs_这个函数。这个函数是通过我们之前提到的 DeclarativeFrontend::Initialize 里面设置的
VsyncCallback 注册
VsyncCallback是注册到PipelineContext上的,当Vsync信号过来时会调用OnVsyncEvent
void PipelineBase::OnVsyncEvent(uint64_t nanoTimestamp, uint32_t frameCount)
void PipelineContext::FlushVsync(uint64_t nanoTimestamp, uint32_t frameCount)
可以看到先处理事件,然后做动画以及自定义动画的回调任务
创建 FrameNode 树 Mark dirty
PageRouterManager
void PageRouterManager::LoadPage(int32_t pageId, const RouterPageInfo& target, bool needHideLast, bool needTransition)
继续回到我们之前的PageRouterMangager的LoadPage方法。该方法除了加载JS,最后还调用了OnPageReady这个方法
bool PageRouterManager::OnPageReady( const RefPtr& pageNode, bool needHideLast, bool needTransition, bool isCardRouter, int64_t cardId)
PageRouterManager最终调用了stageManager的PushPage方法。
StageManager
bool StageManager::PushPage(const RefPtr& node, bool needHideLast, bool needTransition)
这里挂载到父节点,并执行一些声明周期方法。最后将UI 节点标记为dirty,之后 Vsync 信号到来会重新完成 dirty nodes 节点的布局和重绘
void FrameNode::MarkDirtyNode(PropertyChangeFlag extraFlag)
PipelineContext
void PipelineContext::AddDirtyPropertyNode(const RefPtr& dirtyNode)
我们发现最终将节点添加进了dirtyPropertyNodes。当Vsync信号到来的时候就会刷新dirtyPropertyNodes
我们逆向找一下dirtyPropertyNodes怎么使用的。
void PipelineContext::FlushVsync(uint64_t nanoTimestamp, uint32_t frameCount)
FlushVsync内部会调用FlushBuild
void PipelineContext::FlushBuild()
FlushBuild内部又会调用FlushDirtyNodeUpdate
void PipelineContext::FlushDirtyNodeUpdate()
小结
- ace eventloop和Node.js一样,底层是使用libuv实现的
- 它的更新机制和Flutter及其他声明式框架一样,都是先标记dirty,然后在下一次vsync到来时进行更新
参考资料
- 声明式范式的语法编译转换,语法验证等 https://gitee.com/openharmony/developtools_ace_ets2bundle
- ArkUI引擎 https://gitee.com/openharmony/arkui_ace_engine
- 原生模块扩展开发框架 https://gitee.com/openharmony/arkui_napi
- ArkUIX gitee.com/arkui-x
- libuv https://github.com/libuv/libuv
- gitee.com/arkui-x/doc…
- openharmony文档 https://gitee.com/openharmony/docs/tree/master/zh-cn/readme