研发效能 京东 TaroNative 框架静态布局直渲提速

京东云开发者 · June 08, 2026 · Last by 小黑子-IKUN replied at June 08, 2026 · 174 hits

技术背景
Taro Native 是京东自研的跨平台移动应用开发框架,支持鸿蒙、iOS、Android 三端应用开发,实现一次开发、多端部署。 随着秒送业务功能持续扩展,在华为鸿蒙低端机型上出现严重滑动卡顿,影响用户体验。该页面单张卡片包含 100+ 个元素(图片、文本、容器等),渲染耗时显著,其他端亦存在体验下降。本次优化重点针对鸿蒙端,通过性能分析与调优提升用户体验。

性能分析
页面构成
外层容器:WaterFlow 实现瀑布流列表
单元项:每个 FlowItem 承载一张商家卡片
卡片构建:TML 模板 → CAPI 创建 Node Tree → 挂载至 ArkTS 侧 NodeContent

卡片渲染流程
页面滑动时,WaterFlow 预加载 H:Preload FlowItem,调用 Repeat.GetFrameChildByIndex 获取可复用组件并更新数据,投递解析任务到 element 线程。
element 线程对 TML 业务代码进行解析并生成 Virtual Dom、CSS Info、yoga 渲染树,投递测量布局任务到 render 线程。
render 线程基于 yoga 布局引擎对渲染树进行测量布局,同时生成节点创建、属性设置等命令,投递命令集合到主线程。
主线程分析命令,调用 CAPI 接口创建节点、设置属性、更新布局,并将根节点挂载到 ArkUI 的 ContentNode。
主线程 OnVsync 时触发 Node Tree 的系统测量、布局后,发送给 render_service 渲染上屏。

从上面流程中可以观察到,滑动卡顿的根源在于 主线程过载,应聚焦阶段 1、4、5 进行深入优化。

瓶颈定位
阶段 1:FlowItem 预加载耗时 24ms,各区分别耗时 A 15ms、B 3ms、C 6ms

A 区 Repeat 组件从复用池获取可复用组件更新数据,导致组件内部状态变更并触发依赖变更。

B 区 图片组件创建图片并加载图片内容。

C 区 卡片内所有节点进行测量布局。

阶段 4:命令消费主要集中在 Image / Text 节点的创建和属性设置

阶段 5:卡片节点中存在大量 Stack / Image / Text 节点,递归测量布局开销巨大

核心问题归纳:

1.冗余测量布局:FlowItem 预加载时已进行一次旧内容测量 render 线程完成 yoga 测量后,系统在 OnVSync 再次全量测量。

2.Repeat 复用:复用链路分散在多阶段,单次耗时虽不长,但累积效应明显需结合鸿蒙系统源码进一步拆解。

方案一:节点树静态布局&拦截系统测量布局
设计思路
利用 render 线程已完成的 yoga 测量结果,直接作为最终布局依据,跳过主线程 OnVSync 阶段的重复测量。

关键技术点
CustomNode 自定义 OnMeasure / OnLayout,拦截系统对 CAPI Node Tree 的递归测量
使用 NODE_LAYOUT_RECT 一次性设置四边位置与尺寸
实施步骤

  1. 替换布局属性设置方式 设置 Node Tree 所有节点 top、left、width、height 时,由 NODE_WIDTH、NODE_HEIGHT、NODE_POSITION 改成 NODE_LAYOUT_RECT

ArkUI_NumberValue number[] = {0.0, 0.0,0.0, 0.0};
ArkUI_AttributeItem item = {number, 4};
number[0].i32 = (int32_t) std::floor(vp2PX(left));
number[1].i32 = (int32_t) std::floor(vp2PX(top));
number[2].i32 = (int32_t) std::floor(vp2PX(width));
number[3].i32 = (int32_t) std::floor(vp2PX(height));
ArkUI_NativeNodeAPI_1::Instance().setAttribute(nodeHandle, NODE_LAYOUT_RECT, &item);

  1. 根节点升级为 CustomNode 并注册拦截 CAPI 创建 Node Tree 的根节点 StackNode 替换成 CustomNode,注册自定义测量布局函数,拦截系统对 NodeTree 的递归测量布局

// 注册自定义事件监听接口
void CustomNode::EnableLayoutInterception() {
ArkUI_NativeNodeAPI_1::Instance().addNodeCustomEventReceiver(nodeHandle, OnStaticCustomEvent);
ArkUI_NativeNodeAPI_1::Instance().registerNodeCustomEvent(nodeHandle, ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE, 0, this);
ArkUI_NativeNodeAPI_1::Instance().registerNodeCustomEvent(nodeHandle, ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT, 0, this);
}

// 移除自定义事件监听接口
void CustomNode::DisableLayoutInterception() {
ArkUI_NativeNodeAPI_1::Instance().removeNodeCustomEventReceiver(nodeHandle, OnStaticCustomEvent);
ArkUI_NativeNodeAPI_1::Instance().unregisterNodeCustomEvent(nodeHandle, ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE);
ArkUI_NativeNodeAPI_1::Instance().unregisterNodeCustomEvent(nodeHandle, ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT);
}

// 系统测量布局事件处理
void CustomNode::OnStaticCustomEvent(ArkUI_NodeCustomEvent *event) {
auto customNode = (CustomNode *) OH_ArkUI_NodeCustomEvent_GetUserData(event);
auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event);
switch (type) {
case ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE:
customNode->OnMeasure(event);
break;
case ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT:
customNode->OnLayout(event);
break;
default:
break;
}
}

// 自定义测量
void CustomNode::OnMeasure(ArkUI_NodeCustomEvent *event) {
const ArkUI_AttributeItem *item = ArkUI_NativeNodeAPI_1::Instance().getAttribute(nodeHandle, NODE_LAYOUT_RECT);
ArkUI_NativeNodeAPI_1::Instance().setMeasuredSize(nodeHandle, item->value[2].i32, item->value[3].i32);
}

// 自定义布局
void CustomNode::OnLayout(ArkUI_NodeCustomEvent *event) { }
优化机制:CustomNode 自定义测量布局函数,拦截了系统对 CAPI 创建 NodeTree 的递归测量布局;设置节点的 NODE_LAYOUT_RECT 可以直接用于后续的绘制渲染。

边界场景与应对

优化效果
预加载阶段 C 区(文本测量布局)显著缩短
鸿蒙低端机平均帧率:43 fps → 57 fps,提升 ≈32.5%

方案二:复用字体测量布局缓存
原文本测量布局瓶颈分析
经过方案一优化后,系统仍会在主线程执行文本测量布局。我们梳理了文本节点的创建与属性设置流程,发现文本测量依赖于 ArkGraphics 2D 自研文本引擎〔1〕,用于处理复杂文本显示场景。该测量过程目前分布在两个阶段:

优化策略:文本测量前置化与结果缓存
将文本测量完全前移至 render 线程,并在该阶段缓存 ArkUI_StyledString 结果;主线程仅消费缓存值,不再重复测量。

实施步骤

  1. Render 线程测量并缓存 StyledString
    TextStyledString measureTextWithStyledString1(MeasureInfo measureInfo) {
    // 1.创建 TextStyle:设置 FontSize、FontHeight、FontWeight、Overflow、FontFamilies、FontStyle、Color、Decoration
    OH_Drawing_TextStyle* textStyle = OH_Drawing_CreateTextStyle();

    // 2.创建 TypographyStyle:设置 MaxLines、ellipsis overflow、TEXT_ALIGN
    OH_Drawing_TypographyStyle* typographyStyle = OH_Drawing_CreateTypographyStyle();

    // 创建 StyledString
    OH_Drawing_FontCollection* fontCollection = GetFontCollection();
    ArkUI_StyledString* styledString = OH_ArkUI_StyledString_Create(typographyStyle, fontCollection);

    // 添加文本
    OH_ArkUI_StyledString_PushTextStyle(styledString, textStyle);
    OH_ArkUI_StyledString_AddText(styledString, measureInfo.text.c_str());
    OH_ArkUI_StyledString_PopTextStyle(styledString);

    // TextStyle 在添加文本后不再需要,直接销毁
    OH_Drawing_DestroyTextStyle(textStyle);

    // 创建 Typography 并布局
    OH_Drawing_Typography* typography = OH_ArkUI_StyledString_CreateTypography(styledString);
    OH_Drawing_TypographyLayout(typography, vp2Px(measureInfo.maxWidth));

    // 获取测量结果
    size_t lineCount = OH_Drawing_TypographyGetLineCount(typography);
    double height = px2Vp(OH_Drawing_TypographyGetHeight(typography));
    double width = px2Vp(ceil(OH_Drawing_TypographyGetLongestLine(typography)));

    // 返回字体 styledString
    return TextStyledString(typographyStyle, typography, styledString);
    }

  2. 主线程直接应用缓存
    if (cachedStyledString_.IsValid()) {
    ArkUI_NativeNodeAPI_1::Instance().resetAttribute(nodeHandle, NODE_TEXT_CONTENT_WITH_STYLED_STRING);
    ArkUI_AttributeItem content = { .object = static_cast(cachedStyledString_.GetStyledString()) };
    ArkUI_NativeNodeAPI_1::Instance().setAttribute(nodeHandle, NODE_TEXT_CONTENT_WITH_STYLED_STRING, &content);
    }
    收益评估
    主线程耗时降低:≈ 4 ms+
    线程负载均衡:减轻主线程压力,提升帧稳定性
    一致性保障:同一文本仅测量一次,避免多线程结果差异
    总结与沉淀
    方法论沉淀
    本次实践形成了一套适用于 类 RN 架构 的通用提速范式——《类 RN 静态布局直渲提速》:

前置测量:在 render 线程完成布局计算
结果直通:通过 NODE_LAYOUT_RECT 将尺寸与位置一次性注入节点
拦截去重:借助 CustomNode 拦截系统重复测量
资源复用:对文本等资源建立跨线程缓存
该方法可横向迁移至其他产品的同构框架,显著提升低端设备渲染性能。

致谢
特别感谢 华为 2012 鸿蒙突击队 在本次问题联合攻关中的深度支持与协同分析。

共收到 1 条回复 时间 点赞
回复内容未通过审核,暂不显示
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up