Layout Framework 知识库
文档版本: v1.0 更新时间: 2026-02-26 源码版本: OpenHarmony ace_engine (master 分支)
📚 目录
概述
布局系统定位
Layout Flow (布局流程) 是 OpenHarmony ACE Engine 的核心子系统,负责计算和更新 UI 组件的尺寸与位置。布局系统从垂直同步信号触发开始,通过任务调度、节点测量、布局计算、安全区域扩展,最终将几何数据同步到 Rosen 渲染引擎,形成完整的布局计算管道。
技术架构
分层设计:
┌─────────────────────────────────────────────┐
│ PipelineContext (管道上下文) │
│ - FlushVsync 触发 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ UITaskScheduler (任务调度器) │
│ - 任务调度和管理 │
│ - dirty 节点收集 │
│ - 批量任务执行 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ FrameNode (组件节点) │
│ - Measure 测量 │
│ - Layout 布局 │
│ - OnLayoutFinish 完成处理 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ LayoutAlgorithm (布局算法) │
│ - Measure 测量实现 │
│ - Layout 布局实现 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ LayoutWrapper (布局包装器) │
│ - SafeArea 安全区域处理 │
│ - Keyboard 键盘避让 │
└──────────────────┬──────────────────────────┘
│
┌──────────────────▼──────────────────────────┐
│ RosenRenderContext (渲染上下文) │
│ - SyncGeometryProperties 同步 │
│ - SavePaintRect 保存绘制区域 │
└─────────────────────────────────────────────┘
核心特性:
- 异步任务调度: 支持多线程布局计算,提升性能
- 增量更新: 只处理 dirty 标记的节点,减少计算量
- 安全区域适配: 支持系统安全区域(刘海屏、圆角等)和键盘避让
- 几何节点同步: 布局结果自动同步到 Rosen 渲染引擎
- 多轮布局支持: 支持几何过渡动画等多轮布局场景
代码规模
| 模块 | 文件 | 代码行数(估计) | 作用 |
|---|---|---|---|
| 任务调度 | ui_task_scheduler.cpp |
~500 行 | 任务调度和管理 |
| 节点布局 | frame_node.cpp |
~3500 行 | 组件节点布局实现 |
| 布局包装 | layout_wrapper.cpp |
~850 行 | 布局数据包装和传递 |
| 布局算法 | layout_algorithm.cpp |
~150 行 | 布局算法基础实现 |
目录结构
源码组织
ace_engine/
├── frameworks/
│ ├── core/
│ │ ├── components_ng/
│ │ │ ├── base/
│ │ │ │ └── frame_node.cpp # 帧节点,布局执行主体
│ │ │ ├── layout/
│ │ │ │ ├── layout_wrapper.cpp # 布局包装器
│ │ │ │ ├── layout_algorithm.cpp # 布局算法基础
│ │ │ │ └── layout_algorithm.h # 布局算法接口
│ │ │ ├── manager/safe_area/
│ │ │ │ └── safe_area_manager.cpp # 安全区域管理器
│ │ │ └── pattern/
│ │ │ └── [各组件]/
│ │ │ └── *_layout_algorithm.cpp # 各组件布局算法
│ │ └── pipeline_ng/
│ │ ├── ui_task_scheduler.cpp # UI任务调度器
│ │ └── pipeline_context.cpp # 管道上下文
│ └── ...
└── ...
核心文件说明
1. UITaskScheduler
路径: frameworks/core/pipeline_ng/ui_task_scheduler.cpp
职责:
- 管理布局和渲染任务的调度
- 收集 dirty 节点
- 批量执行布局和同步任务
- 协调任务执行顺序
关键类:
class UITaskScheduler {
std::vector<RefPtr<FrameNode>> dirtyLayoutNodes_; // dirty 布局节点
std::map<int32_t, PageDirtySet> dirtyRenderNodes_; // dirty 渲染节点
std::vector<std::function<void()>> syncGeometryNodeTasks_; // 同步任务队列
void FlushTask(); // 主任务执行函数
void FlushLayoutTask(); // 刷新布局任务
void FlushSyncGeometryNodeTasks(); // 刷新几何节点同步任务
void FlushRenderTask(); // 刷新渲染任务
};
2. FrameNode
路径: frameworks/core/components_ng/base/frame_node.cpp
职责:
- 组件节点的布局执行主体
- 测量和布局的主要执行者
- 布局完成后的回调处理
- 几何节点同步
关键方法:
class FrameNode : public UINode {
void CreateLayoutTask(bool forceUseMainThread, LayoutType layoutTaskType);
void Measure(const std::optional<LayoutConstraintF>& parentConstraint);
void Layout();
bool OnLayoutFinish(bool& needSyncRsNode, DirtySwapConfig& config);
void SyncGeometryNode(bool needSyncRsNode, const DirtySwapConfig& config);
RefPtr<LayoutAlgorithmWrapper> GetLayoutAlgorithm(bool needReset);
LayoutConstraintF GetLayoutConstraint() const;
};
3. LayoutWrapper
路径: frameworks/core/components_ng/layout/layout_wrapper.cpp
职责:
- 布局数据的包装和传递
- 安全区域扩展处理
- 键盘避让处理
- 布局约束应用
关键方法:
class LayoutWrapper {
void ApplyConstraint(LayoutConstraintF constraint);
void ExpandSafeArea();
bool AvoidKeyboard(bool isFocusOnPage);
OffsetF GetParentGlobalOffsetWithSafeArea() const;
RectF GetFrameRectWithSafeArea() const;
};
4. LayoutAlgorithm
路径: frameworks/core/components_ng/layout/layout_algorithm.cpp
职责:
- 布局算法的基础实现
- Measure 和 Layout 的包装器层
关键方法:
class LayoutAlgorithm {
virtual void Measure(LayoutWrapper* layoutWrapper) = 0;
virtual void Layout(LayoutWrapper* layoutWrapper) = 0;
};
class LayoutAlgorithmWrapper {
void Measure(LayoutWrapper* layoutWrapper);
void Layout(LayoutWrapper* layoutWrapper);
};
核心类继承关系
布局相关类继承图
UINode
│
├─ FrameNode (组件节点)
│ │
│ ├─ pattern_: Pattern (业务逻辑)
│ ├─ layoutProperty_: LayoutProperty (布局属性)
│ ├─ geometryNode_: GeometryNode (几何数据)
│ ├─ renderContext_: RenderContext (渲染上下文)
│ └─ layoutAlgorithm_: LayoutAlgorithmWrapper (布局算法)
│
└─ ...
LayoutAlgorithm (布局算法基类)
│
├─ FlexLayoutAlgorithm (弹性布局)
├─ GridLayoutAlgorithm (网格布局)
├─ ScrollLayoutAlgorithm (滚动布局)
└─ ... (各组件的布局算法)
LayoutWrapper (布局包装器)
│
└─ LayoutWrapperNode (节点包装器)
│
├─ hostNode_: FrameNode (宿主节点)
├─ geometryNode_: GeometryNode (几何节点)
└─ layoutProperty_: LayoutProperty (布局属性)
关键数据结构
LayoutConstraintF (布局约束)
struct LayoutConstraintF {
OptionalSizeF minSize; // 最小尺寸
OptionalSizeF maxSize; // 最大尺寸
OptionalSizeF percentReference; // 百分比参考尺寸
ScaleProperty scaleProperty; // 缩放属性
};
DirtySwapConfig (变化配置)
struct DirtySwapConfig {
bool frameSizeChange; // 框架尺寸是否变化
bool frameOffsetChange; // 框架位置是否变化
bool contentSizeChange; // 内容尺寸是否变化
bool contentOffsetChange; // 内容位置是否变化
bool skipMeasure; // 是否跳过测量
bool skipLayout; // 是否跳过布局
};
SafeAreaExpandOpts (安全区域扩展选项)
struct SafeAreaExpandOpts {
LayoutSafeAreaType type; // 扩展类型(系统/键盘等)
LayoutSafeAreaEdge edges; // 扩展边(上/下/左/右)
bool switchToNone; // 是否切换到无扩展
bool Expansive() const; // 是否需要扩展
bool ExpansiveToKeyboard() const; // 是否扩展到键盘
};
布局流程详解
完整流程图
┌─────────────────────────────────────────────────────────────┐
│ 1. PipelineContext::FlushVsync │
│ 接收垂直同步信号,触发 UI 刷新 │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. UITaskScheduler::FlushTask │
│ 协调布局和渲染任务的执行顺序 │
└────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. UITaskScheduler::FlushLayoutTask │
│ 批量执行所有 dirty 布局节点 │
│ - 收集 dirty 节点 │
│ - 为每个节点创建布局任务 │
└────────────────────────┬────────────────────────────────────┘
│
├─────────────────────────────────────────┐
│ │
▼ ▼
┌──────────────────────────────────┐ ┌──────────────────────────────┐
│ 4. FrameNode::CreateLayoutTask │ │ 其他 dirty 节点 │
│ - 更新布局属性标志 │ │ ... │
│ - 获取布局约束 │ └──────────────────────────────┘
└────────────┬─────────────────────┘
│
├──────────────────┐
│ │
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────────┐
│ 5. FrameNode::Measure │ │ 6. FrameNode::Layout │
│ 测量阶段 │ │ 布局阶段 │
│ ├─ 应用约束 │ │ ├─ 调整安全区域 │
│ ├─ 测量内容 │ │ ├─ 执行布局算法 │
│ ├─ 执行算法 │ │ ├─ 布局完成处理 │
│ └─ 应用宽高比 │ │ └─ 添加同步任务 │
└────────┬────────────────┘ └────────┬───────────────────────┘
│ │
│ ├────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
│ MeasureContent │ │ 算法布局 │ │ OnLayoutFinish │
│ 测量内容尺寸 │ │ 排列子组件 │ │ 完成回调 │
└─────────────────┘ └──────────┬───────┘ └────────┬─────────────┘
│ │
│ ├─────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────┐ ┌──────────────┐
│ LayoutAlgorithm │ │ SavePaintRect│ │ MarkDirty │
│ Layout 方法 │ │ 保存绘制区域 │ │ 标记重绘 │
└────────┬────────┘ └──────────────┘ └──────────────┘
│
▼
┌─────────────────┐
│ AddSyncTask │
│ 添加同步任务 │
└────────┬────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 7. UITaskScheduler::FlushSyncGeometryNodeTasks │
│ 批量执行几何节点同步任务 │
│ ├─ ExpandSafeArea 扩展安全区域 │
│ ├─ SetLayoutNodeRect 设置节点位置 │
│ └─ 执行所有同步任务 │
└────────────────────────┬────────────────────────────────────────────┘
│
├────────────────────┐
│ │
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────────┐
│ 8. SafeAreaManager │ │ 9. FrameNode::SyncGeometryNode │
│ ::ExpandSafeArea │ │ 同步几何节点 │
│ 扩展安全区域 │ │ ├─ 同步边框属性 │
│ └─ LayoutWrapper │ │ ├─ 触发 Pattern 回调 │
│ ::ExpandSafeArea │ │ ├─ 同步几何属性 │
│ 扩展到安全区域 │ │ ├─ 更新背景 │
└──────────────────────────────┘ │ └─ 重建渲染树 │
└──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 10. UITaskScheduler::FlushRenderTask │
│ 执行渲染任务(送显阶段,非布局核心流程) │
└─────────────────────────────────────────────────────────────────────┘
核心函数调用序列
测量阶段调用链
FrameNode::Measure (frame_node.cpp:5535)
│
├─> GetLayoutAlgorithm() // 获取布局算法
│ └─> pattern_->CreateLayoutAlgorithm()
│
├─> ApplyConstraint() // 应用布局约束
│ └─> layoutProperty_->UpdateLayoutConstraint()
│
├─> layoutAlgorithm_->MeasureContent() // 测量内容尺寸
│ └─> [具体算法实现]
│
├─> layoutAlgorithm_->Measure(this) // 执行测量算法
│ └─> LayoutAlgorithmWrapper::Measure()
│ └─> layoutAlgorithm_->Measure(layoutWrapper)
│
└─> geometryNode_->SetFrameSize() // 设置框架尺寸
关键文件引用:
frame_node.cpp:5535-5679- FrameNode::Measureframe_node.cpp:6209-6220- GetLayoutAlgorithmlayout_algorithm.cpp:85-97- LayoutAlgorithmWrapper::Measure
布局阶段调用链
FrameNode::Layout (frame_node.cpp:5682)
│
├─> OffsetNodeToSafeArea() // 调整到安全区域
│
├─> layoutAlgorithm_->Layout(this) // 执行布局算法
│ └─> LayoutAlgorithmWrapper::Layout()
│ └─> layoutAlgorithm_->Layout(layoutWrapper)
│
├─> OnLayoutFinish() // 布局完成处理
│ ├─> 检测尺寸变化
│ ├─> renderContext_->SavePaintRect() // 保存绘制区域
│ ├─> pattern_->OnDirtyLayoutWrapperSwap() // Pattern 回调
│ └─> MarkDirtyNode() // 标记需要重绘
│
└─> pipeline->AddSyncGeometryNodeTask() // 添加同步任务
└─> 任务队列存储
关键文件引用:
frame_node.cpp:5682-5789- FrameNode::Layoutframe_node.cpp:5860-5965- FrameNode::OnLayoutFinishlayout_algorithm.cpp:99-108- LayoutAlgorithmWrapper::Layout
同步阶段调用链
UITaskScheduler::FlushSyncGeometryNodeTasks (ui_task_scheduler.cpp:116)
│
├─> ExpandSafeArea() // 扩展安全区域
│ └─> SafeAreaManager::ExpandSafeArea()
│ └─> LayoutWrapper::ExpandSafeArea()
│ ├─> GetParentGlobalOffsetWithSafeArea() // 获取父节点偏移
│ ├─> ExpandHelper() // 执行扩展
│ ├─> AdjustFixedSizeNode() // 调整固定尺寸节点
│ ├─> AdjustChildren() // 调整子节点
│ └─> renderContext_->UpdatePaintRect() // 更新绘制区域
│
├─> SetLayoutNodeRect() // 设置节点位置
│
└─> 执行所有同步任务
└─> FrameNode::SyncGeometryNode() // 同步几何节点
├─> 更新边框属性
├─> pattern_->OnSyncGeometryNode() // Pattern 回调
├─> renderContext_->SyncGeometryProperties() // 同步几何属性
├─> UpdateBackground() // 更新背景
└─> RebuildRenderContextTree() // 重建渲染树
关键文件引用:
ui_task_scheduler.cpp:116-127- FlushSyncGeometryNodeTasksui_task_scheduler.cpp:107-114- ExpandSafeArealayout_wrapper.cpp:297-365- LayoutWrapper::ExpandSafeAreaframe_node.cpp:5967-6033- FrameNode::SyncGeometryNode
组件树的递归执行逻辑
布局系统通过递归遍历组件树来完成所有组件的测量和布局。父容器在执行 Measure/Layout 时,会遍历所有子组件,对每个子组件调用其 Measure/Layout 方法,子组件又会递归地对它的子组件执行相同的操作,形成深度优先的遍历。
递归执行的三层结构
┌─────────────────────────────────────────────────────────────┐
│ FrameNode 层 (frame_node.cpp) │
│ - 触发递归: Measure() / Layout() │
│ - 调用 LayoutAlgorithm │
│ - 不直接遍历子组件 │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ LayoutAlgorithm 层 (flex_layout_algorithm.cpp) │
│ - 遍历子组件: GetAllChildrenWithBuild() │
│ - 创建约束: CreateChildConstraint() │
│ - 触发子组件递归: child->Measure() / child->Layout() │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ LayoutWrapper 层 (layout_wrapper_node.cpp) │
│ - 递归入口: Measure() / Layout() │
│ - 调用子组件的 LayoutAlgorithm │
│ - 形成递归循环 │
└─────────────────────────────────────────────────────────────┘
Measure 阶段的递归
执行流程:
FlexLayoutAlgorithm::Measure (父容器)
│
├─> 1. 获取所有子组件
│ const auto& children = layoutWrapper->GetAllChildrenWithBuild()
│
├─> 2. 计算父容器尺寸
│ realSize = CreateIdealSizeByPercentRef(...)
│
└─> 3. 遍历子组件并递归测量
for (auto& child : children) {
// 创建子组件约束
childConstraint = CreateChildConstraint()
// 递归调用子组件 Measure
child->Measure(childConstraint)
│
└─> LayoutWrapperNode::Measure (layout_wrapper_node.cpp:150)
│
├─> ApplyConstraint(childConstraint) // 应用约束
│
├─> layoutAlgorithm_->MeasureContent() // 测量内容
│
├─> layoutAlgorithm_->Measure(this) // 执行算法
│ │
│ └─> 如果子组件是容器: 继续递归遍历其子组件
│
└─> geometryNode_->SetFrameSize() // 设置尺寸
}
例举源码位置:
- Flex Measure:
flex_layout_algorithm.cpp:1106-1177 - Flex 递归调用:
flex_layout_algorithm.cpp:588-629(MeasureAndCleanMagicNodes) - LayoutWrapperNode Measure:
layout_wrapper_node.cpp:150-215
Flex 的多次测量场景:
- 第一轮: 测量无
layoutWeight的子组件 - 计算: 根据剩余空间计算
layoutWeight子组件的分配 - 第二轮: 测量有
layoutWeight的子组件,使用分配的空间
Layout 阶段的递归
执行流程:
FlexLayoutAlgorithm::Layout (父容器)
│
├─> 1. 获取所有子组件
│ const auto& children = layoutWrapper->GetAllChildrenWithBuild(false)
│
├─> 2. 计算布局参数
│ mainAxisSize_ = GetMainAxisSizeHelper(contentSize, direction_)
│ remainSpace = mainAxisSize_ - allocatedSize_
│
├─> 3. 放置子组件(设置位置)
│ PlaceChildren(layoutWrapper, frontSpace, betweenSpace, paddingOffset)
│ │
│ └─> 对每个子组件:
│ childGeometryNode->SetMarginFrameOffset(offset)
│
└─> 4. 递归调用子组件 Layout
for (auto&& child : children) {
child->Layout() // stack_layout_algorithm.cpp:30-32
│
└─> LayoutWrapperNode::Layout (layout_wrapper_node.cpp:218)
│
├─> OffsetNodeToSafeArea() // 调整安全区域
│
├─> layoutAlgorithm_->Layout(this) // 执行布局算法
│ │
│ └─> 如果子组件是容器: 继续递归遍历其子组件
│
└─> 返回
}
例举源码位置:
- Flex Layout:
flex_layout_algorithm.cpp:1240-1299 - Stack Layout:
stack_layout_algorithm.cpp:26-33 - LayoutWrapperNode Layout:
layout_wrapper_node.cpp:218-249
典型组件递归对比
| 特性 | Flex | Stack |
|---|---|---|
| Measure 递归 | 父容器遍历子组件递归调用 | 父容器不递归,子组件独立测量 |
| Layout 递归 | 放置子组件后递归调用 | 放置子组件后递归调用 |
| 约束传递 | 向子组件传递约束 | 不传递约束 |
| 测量次数 | 可能多次(flexWeight) | 通常一次 |
递归执行示例
Column (父容器)
├─ Text (子组件1)
├─ Row (子组件2)
│ ├─ Button (孙组件1)
│ └─ Image (孙组件2)
└─ Divider (子组件3)
Measure 递归顺序:
1. Column::Measure 开始
2. Text::Measure 开始 → 结束
3. Row::Measure 开始
4. Button::Measure 开始 → 结束
5. Image::Measure 开始 → 结束
6. Row::Measure 结束
7. Divider::Measure 开始 → 结束
8. Column::Measure 结束
Layout 递归顺序:
1. Column::Layout 开始(设置所有子组件位置)
2. Text::Layout 开始 → 结束
3. Row::Layout 开始(设置 Button 和 Image 位置)
4. Button::Layout 开始 → 结束
5. Image::Layout 开始 → 结束
6. Row::Layout 结束
7. Divider::Layout 开始 → 结束
8. Column::Layout 结束
递归性能优化
- SkipMeasure/SkipLayout: 约束未变化时跳过
- 增量布局: 只对变化的子组件递归执行
- 懒构建: 使用
GetAllChildrenWithBuild(false)只获取已构建的子组件 - 测量缓存: 缓存子组件的测量结果
关键函数调用链
完整调用路径
PipelineContext::FlushVsync
└─> UITaskScheduler::FlushTask
├─> UITaskScheduler::FlushLayoutTask
│ ├─> FrameNode::CreateLayoutTask
│ │ ├─> FrameNode::Measure
│ │ │ ├─> FrameNode::GetLayoutAlgorithm
│ │ │ │ └─> Pattern::CreateLayoutAlgorithm
│ │ │ ├─> LayoutAlgorithmWrapper::Measure
│ │ │ │ └─> LayoutAlgorithm::Measure
│ │ │ └─> GeometryNode::SetFrameSize
│ │ └─> FrameNode::Layout
│ │ ├─> LayoutAlgorithmWrapper::Layout
│ │ │ └─> LayoutAlgorithm::Layout
│ │ ├─> FrameNode::OnLayoutFinish
│ │ │ ├─> RosenRenderContext::SavePaintRect
│ │ │ ├─> Pattern::OnDirtyLayoutWrapperSwap
│ │ │ └─> FrameNode::MarkDirtyNode
│ │ └─> PipelineContext::AddSyncGeometryNodeTask
│ └─> UITaskScheduler::FlushSyncGeometryNodeTasks
│ ├─> UITaskScheduler::ExpandSafeArea
│ │ └─> SafeAreaManager::ExpandSafeArea
│ │ └─> LayoutWrapper::ExpandSafeArea
│ └─> FrameNode::SyncGeometryNode
│ └─> RosenRenderContext::SyncGeometryProperties
└─> UITaskScheduler::FlushRenderTask
函数角色定位表
| 函数 | 层次 | 角色定位 | 核心职责 |
|---|---|---|---|
FlushVsync |
触发层 | 流程触发者 | 接收垂直同步信号,启动刷新流程 |
FlushTask |
调度层 | 任务协调者 | 协调布局和渲染任务的执行顺序 |
FlushLayoutTask |
调度层 | 批量执行者 | 批量执行所有 dirty 节点的布局 |
CreateLayoutTask |
执行层 | 任务创建者 | 为单个节点创建布局任务 |
Measure |
执行层 | 测量执行者 | 计算组件的理想尺寸 |
GetLayoutAlgorithm |
执行层 | 算法获取者 | 获取或创建布局算法 |
LayoutAlgorithm::Measure |
算法层 | 测量算法 | 具体的测量算法实现 |
Layout |
执行层 | 布局执行者 | 计算组件位置并排列子组件 |
LayoutAlgorithm::Layout |
算法层 | 布局算法 | 具体的布局算法实现 |
OnLayoutFinish |
执行层 | 完成处理者 | 布局完成后的回调处理 |
SavePaintRect |
渲染层 | 区域保存者 | 保存绘制区域到渲染上下文 |
AddSyncGeometryNodeTask |
调度层 | 任务收集者 | 添加同步任务到队列 |
FlushSyncGeometryNodeTasks |
调度层 | 批量同步者 | 批量执行几何节点同步任务 |
ExpandSafeArea |
功能层 | 区域扩展者 | 扩展组件到安全区域 |
SyncGeometryNode |
同步层 | 节点同步者 | 同步几何数据到渲染引擎 |
SyncGeometryProperties |
同步层 | 属性同步者 | 同步几何属性到 RS 节点 |
FlushRenderTask |
渲染层 | 渲染执行者 | 执行渲染任务(送显) |
性能优化策略
1. Dirty 标记优化
原理: 只处理标记为 dirty 的节点
实现位置: frame_node.cpp:2545-2547
if (!isLayoutDirtyMarked_ && (layoutTaskType == LayoutType::NONE)) {
return; // 非 dirty 节点跳过
}
优化效果:
- 减少不必要的布局计算
- 降低 CPU 占用
- 提升布局响应速度
触发条件:
- 节点属性变化时会标记
isLayoutDirtyMarked_ = true - 父节点 dirty 可能触发子节点 dirty
2. 约束未变化优化
原理: 约束未变化且未强制测量时跳过测量
实现位置: frame_node.cpp:5606-5618
isConstraintNotChanged_ = layoutProperty_->ConstraintEqual(preConstraint, contentConstraint);
if (isConstraintNotChanged_) {
if (!CheckNeedForceMeasureAndLayout()) {
layoutAlgorithm_->SetSkipMeasure();
return; // 跳过测量
}
}
优化效果:
- 避免重复测量
- 减少算法执行次数
- 提升布局效率
判断依据:
- 当前约束与上次约束相等
- 无强制测量标志
3. SkipMeasure 和 SkipLayout
原理: 分别控制测量和布局阶段的跳过
实现位置: frame_node.cpp:5572-5577, 5708-5745
// 测量阶段
if (layoutAlgorithm_->SkipMeasure()) {
isLayoutDirtyMarked_ = false;
return; // 跳过测量
}
// 布局阶段
if (!CheckNeedLayout(layoutProperty_->GetPropertyChangeFlag())) {
layoutAlgorithm_->SetSkipLayout();
return; // 跳过布局
}
优化效果:
- 测量和布局可以独立优化
- 细粒度控制布局流程
- 最大化复用已有结果
使用场景:
- 约束未变化 → SkipMeasure
- 尺寸未变化 → SkipLayout
- 子组件未变化 → SkipLayout
4. 安全区域扩展缓存
原理: 使用缓存避免重复计算安全区域扩展
实现位置: layout_wrapper.cpp:376-420
bool LayoutWrapper::AccumulateExpandCacheHit(ExpandEdges& totalExpand, ...)
{
auto& selfAccumulateExpand = geometryNode->GetAccumulatedSafeAreaExpand();
if (selfAccumulateExpand->OptionalValueCover(totalExpand)) {
// 使用缓存结果
totalExpand += *selfAccumulateExpand;
return true;
}
return false;
}
优化效果:
- 减少安全区域计算开销
- 避免重复遍历父节点链
- 提升布局性能
缓存条件:
- 父节点已有缓存覆盖子节点扩展方向
- PaddingBorder 为零
5. 批量同步优化
原理: 批量执行同步任务减少开销
实现位置: ui_task_scheduler.cpp:116-127
void UITaskScheduler::FlushSyncGeometryNodeTasks()
{
auto tasks = std::move(syncGeometryNodeTasks_);
for (auto& task : tasks) {
if (task) {
task(); // 批量执行
}
}
}
优化效果:
- 减少任务调度开销
- 提高执行效率
- 便于优化同步策略
优势:
- 任务集中执行,减少上下文切换
- 便于实现同步策略优化
- 支持任务优先级排序
6. 增量布局
原理: 只布局变化的子组件
实现: 组件级别支持,由各 Pattern 实现
示例: Flex 组件的增量布局
优化效果:
- 大幅减少布局计算量
- 提升复杂场景性能
- 降低布局耗时
适用场景:
- 列表滚动
- 动态内容更新
- 局部刷新
调试指南
调试追踪开关
1. 布局追踪
位置: frame_node.cpp:5540
ACE_LAYOUT_TRACE_BEGIN("Measure[%s][self:%d][parent:%d][key:%s]", ...);
// ... 布局代码
ACE_LAYOUT_TRACE_END()
使用方法: 编译时启用 ACE_LAYOUT_TRACE 宏
输出内容:
- 节点标签、ID、父节点ID、Inspector ID
- 测量和布局耗时
- 约束信息
2. 测量调试追踪
位置: frame_node.cpp:5542-5548
if (SystemProperties::GetMeasureDebugTraceEnabled()) {
ACE_MEASURE_SCOPED_TRACE("MeasureInfo[frameRect:%s][parentConstraint:%s][calcConstraint:%s]",
GetGeometryNode()->GetFrameRect().ToString().c_str(),
parentConstraint.has_value() ? parentConstraint.value().ToString().c_str() : "NA",
layoutProperty_->GetCalcLayoutConstraint() ?
layoutProperty_->GetCalcLayoutConstraint()->ToString().c_str() : "NA");
}
使用方法: 运行时设置 SystemProperties::GetMeasureDebugTraceEnabled() = true
输出内容:
- 框架矩形信息
- 父节点约束信息
- 计算约束信息
3. 同步调试追踪
位置: frame_node.cpp:5969-5974
if (SystemProperties::GetSyncDebugTraceEnabled()) {
ACE_LAYOUT_TRACE_BEGIN("SyncGeometryNode[%s][self:%d][parent:%d][key:%s][paintRect:%s][needSyncRsNode:%d]",
tag_.c_str(), nodeId_, ..., renderContext_->GetPaintRectWithoutTransform().ToString().c_str(), needSyncRsNode);
ACE_LAYOUT_TRACE_END()
}
使用方法: 运行时设置 SystemProperties::GetSyncDebugTraceEnabled() = true
输出内容:
- 同步节点信息
- 绘制矩形信息
- 是否需要同步 RS 节点
4. 安全区域调试追踪
位置: layout_wrapper.cpp:289-294
if (SystemProperties::GetSafeAreaDebugTraceEnabled()) {
ACE_SAFE_AREA_SCOPED_TRACE("ExpandSafeAreaFinish[%s][self:%d][parent:%d][key:%s][paintRectRect:%s][selfAdjust:%s]",
host->GetTag().c_str(), host->GetId(), ..., renderContext->GetPaintRectWithoutTransform().ToString().c_str(),
selfAdjust.ToString().c_str());
}
使用方法: 运行时设置 SystemProperties::GetSafeAreaDebugTraceEnabled() = true
输出内容:
- 安全区域扩展信息
- 绘制矩形变化
- 自调整量信息
常用调试命令
查看 dirty 节点
# 添加日志到 UITaskScheduler::FlushLayoutTask
# 查看有多少 dirty 节点
追踪单个节点的布局
# 在 FrameNode::Measure/Layout 开始处添加条件断点
# 条件: nodeId_ == <目标节点ID>
查看布局约束
# 在 FrameNode::Measure 中查看 parentConstraint
# 在 FrameNode::Layout 中查看 layoutProperty_->GetLayoutConstraint()
查看安全区域扩展
# 在 LayoutWrapper::ExpandSafeArea 中添加日志
# 查看 opts、frame、selfAdjust 等关键变量
性能分析
布局耗时分析
工具: ACE_LAYOUT_TRACE
方法:
- 启用布局追踪
- 查看日志输出
- 分析各阶段耗时
关键指标:
- Measure 耗时
- Layout 耗时
- SyncGeometryNode 耗时
- ExpandSafeArea 耗时
布局次数分析
工具: 日志统计
方法:
- 在
FlushTask中添加计数器 - 统计每帧的布局次数
- 分析是否存在过多次布局
异常情况:
- 单帧布局次数 > 2
- 某个节点反复布局
- 布局循环
内存占用分析
工具: 内存分析工具
关键指标:
- LayoutWrapper 数量
- GeometryNode 数量
- LayoutAlgorithm 数量
优化方向:
- 及时清理过期对象
- 复用 LayoutAlgorithm
- 减少不必要的 GeometryNode 创建
常见问题
Q1: 为什么需要多次布局?
场景:
- 几何过渡动画: 需要在动画过程中多次布局以更新位置
- 增量布局: 第一次布局可能触发子组件需要重新布局
- 安全区域扩展: 扩展后可能需要重新布局相关节点
实现机制:
void UITaskScheduler::FlushTask()
{
do {
FlushLayoutTask();
if (NeedAdditionalLayout()) {
FlushLayoutTask();
}
layoutedCount++;
multiLayoutCount_--;
} while (multiLayoutCount_ > 0);
}
控制策略:
- 最多执行
ENDORSE_LAYOUT_COUNT(2) 次后请求下一帧继续 - 通过
multiLayoutCount_控制布局次数 - 避免无限循环
Q2: SkipMeasure 和 SkipLayout 的区别?
区别:
| 特性 | SkipMeasure | SkipLayout |
|---|---|---|
| 跳过阶段 | 测量阶段 | 布局阶段 |
| 复用内容 | 复用测量结果 | 复用布局结果 |
| 触发条件 | 约束未变化 | 尺寸未变化 |
| 影响范围 | 不影响子组件测量 | 不影响子组件布局 |
独立性: 两者可以独立触发,实现细粒度优化
使用场景:
- 约束未变化 → SkipMeasure
- 尺寸未变化 → SkipLayout
- 两者同时满足 → 完全跳过布局流程
Q3: 安全区域扩展何时执行?
执行时机:
1. 所有节点的 Measure 和 Layout 完成后
2. 在 FlushSyncGeometryNodeTasks 阶段
3. 在同步几何节点之前
原因:
- 统一处理: 在统一的时机处理所有需要扩展的节点
- 避免重复: 避免重复计算和同步
- 最终位置: 确保同步的位置是最终位置(包括安全区域扩展)
执行流程:
FlushLayoutTask (完成所有测量和布局)
└─> FlushSyncGeometryNodeTasks
├─> ExpandSafeArea (统一扩展安全区域)
└─> 执行同步任务 (同步最终位置)
Q4: 几何节点同步任务为什么要延迟执行?
原因:
- 批量处理: 可以批量处理多个节点的同步任务,减少开销
- 避免重复: 如果节点在后续布局中再次变化,避免重复同步
- 安全区域: 先统一扩展安全区域,再同步,确保同步的位置是最终位置
- 性能优化: 减少同步次数,提升性能
实现机制:
// FrameNode::Layout
auto task = [weak = WeakClaim(this), needSync, dirtyConfig]() {
auto frameNode = weak.Upgrade();
CHECK_NULL_VOID(frameNode);
frameNode->SyncGeometryNode(needSync, dirtyConfig);
};
pipeline->AddSyncGeometryNodeTask(task); // 添加到队列
// UITaskScheduler::FlushTask
FlushLayoutTask(); // 先完成所有布局
FlushSyncGeometryNodeTasks(); // 再批量同步
Q5: 如何判断一个节点是否需要同步到渲染引擎?
判断位置: FrameNode::OnLayoutFinish
判断条件:
needSyncRsNode = frameSizeChange || frameOffsetChange ||
contentSizeChange || HasPositionProp() || SelfOrParentExpansive();
包括以下情况:
- 框架尺寸变化: frameSizeChange
- 框架位置变化: frameOffsetChange
- 内容尺寸变化: contentSizeChange (有上下文参数时)
- 有位置属性: HasPositionProp()
- 自身或父节点需要扩展到安全区域: SelfOrParentExpansive()
优化策略:
- 只在必要时同步
- 避免无意义的同步开销
- 提升渲染性能
Q6: LayoutWrapper 和 FrameNode 的关系?
关系:
| 特性 | FrameNode | LayoutWrapper |
|---|---|---|
| 定位 | 组件节点的完整表示 | 布局数据的包装器 |
| 包含内容 | Pattern, Property, EventHub, GeometryNode 等 | 只包含布局相关的数据 |
| 生命周期 | 组件存在期间一直存在 | 只在布局期间创建 |
| 内存占用 | 较大 | 较小(轻量级) |
使用场景:
- FrameNode: 组件的完整生命周期管理
- LayoutWrapper: 布局流程中的数据传递
优势:
- 减少布局过程中的数据依赖
- 提供布局算法的统一接口
- 优化布局性能
Q7: 如何添加自定义布局算法?
步骤:
- 创建布局算法类
class MyLayoutAlgorithm : public LayoutAlgorithm {
public:
void Measure(LayoutWrapper* layoutWrapper) override {
// 测量逻辑
}
void Layout(LayoutWrapper* layoutWrapper) override {
// 布局逻辑
}
};
- 在 Pattern 中创建算法
class MyPattern : public Pattern {
RefPtr<LayoutAlgorithm> CreateLayoutAlgorithm() override {
return MakeRefPtr<MyLayoutAlgorithm>();
}
};
- 框架自动调用
- FrameNode::Measure 会调用 GetLayoutAlgorithm
- GetLayoutAlgorithm 会调用 Pattern::CreateLayoutAlgorithm
- 自动执行算法的 Measure 和 Layout 方法
Q8: 如何调试布局问题?
调试步骤:
- 启用调试追踪
SystemProperties::SetMeasureDebugTraceEnabled(true);
SystemProperties::SetSyncDebugTraceEnabled(true);
- 查看日志输出
- Measure 日志: 查看约束和尺寸
- Layout 日志: 查看位置信息
- Sync 日志: 查看同步信息
- 添加断点
- FrameNode::Measure: 测量阶段
- FrameNode::Layout: 布局阶段
- FrameNode::OnLayoutFinish: 完成阶段
- 检查关键数据
- 布局约束: LayoutProperty::GetLayoutConstraint()
- 测量结果: GeometryNode::GetFrameSize()
- 布局结果: GeometryNode::GetFrameOffset()
附录
相关文档
- Text 组件知识库: Text 组件的布局实现
- Flex 组件知识库: Flex 布局的详细实现
- Scroll 组件知识库: 滚动布局的特殊处理
- ArkUI SDK API 知识库: SDK API 的布局相关接口
源码路径汇总
| 文件 | 路径 | 作用 |
|---|---|---|
| 任务调度器 | frameworks/core/pipeline_ng/ui_task_scheduler.cpp |
任务调度和管理 |
| 帧节点 | frameworks/core/components_ng/base/frame_node.cpp |
组件节点布局执行主体 |
| 布局包装器 | frameworks/core/components_ng/layout/layout_wrapper.cpp |
布局数据包装和传递 |
| 布局算法 | frameworks/core/components_ng/layout/layout_algorithm.cpp |
布局算法基础实现 |
| 管道上下文 | frameworks/core/pipeline_ng/pipeline_context.cpp |
管道上下文,触发布局 |
关键类汇总
| 类名 | 头文件 | 作用 |
|---|---|---|
| UITaskScheduler | ui_task_scheduler.h |
UI 任务调度器 |
| FrameNode | frame_node.h |
组件节点 |
| LayoutWrapper | layout_wrapper.h |
布局包装器 |
| LayoutAlgorithm | layout_algorithm.h |
布局算法基类 |
| LayoutAlgorithmWrapper | layout_algorithm.h |
布局算法包装器 |
| GeometryNode | geometry_node.h |
几何节点 |
| LayoutProperty | layout_property.h |
布局属性 |
| SafeAreaManager | safe_area_manager.h |
安全区域管理器 |
布局相关宏定义
| 宏名称 | 作用 | 定义位置 |
|---|---|---|
| ACE_LAYOUT_TRACE | 布局追踪 | ace_trace.h |
| ACE_SCOPED_TRACE | 作用域追踪 | ace_trace.h |
| ENDORSE_LAYOUT_COUNT | 最大布局次数 | ui_task_scheduler.cpp |
文档维护: 本文档应随代码变更同步更新,确保准确反映当前实现。