Android Complex Demo 只显示标题 — 根因分析与修复
日期
2026-07-07
现象
Android 上加载 complex 组件时白屏/只显示标题区域,其余所有内容不可见。
根因(三层问题叠加)
1. Taitank 不支持百分比字符串 → 所有节点 width=0
JS normalizeStyleForNative() 保留 "100%" 字符串以兼容 Yoga(OHOS),但 Taitank(Android)只能处理数值。"100%" 到达 TaitankLayoutNode::Parser() → IsNumber()=false → SetWidth(NAN) → 所有子节点 layout width=0。
- Yoga:
YG_SET_NUMBER_PERCENT_AUTO_DECL宏 →YGNodeStyleSetWidthPercent() - Taitank:
CheckValueType()仅 WARNING,值被静默丢弃
2. ScrollView 多子节点 → NestedScrollView 异常
NativeBackendElement.appendChild() 中的动态 wrapper 机制调用 HippyBridge.moveNode() 跨 parent 移动子节点,但:
- C++
MoveRenderNode通过 JNI 调用 JavamoveNode(int, byte[])→NativeRenderProvider→RenderManager.moveNode(rootId, pid, list)(同 parent 重排序重载) - 跨 parent 移动在 Java
RenderNodetree 中不生效 NestedScrollView.addView()抛出IllegalStateException: ScrollView can host only one direct child
3. triggerRender 同步调用 → batch 分裂 → CreateDomNodes SKIP
这是最关键的问题。模板初始化期间,triggerRender → render() 同步调用 flushAndBuild → flushQueue。此时只有部分元素已完成 appendChild,其余元素尚未创建。结果:
Batch 1 (1 node): wrapper(id=3)→ SKIP (parent id=2 不存在)
Batch 2 (3 nodes): children → ALL SKIP
Batch 3 (290 nodes): 2 created (id=1,2), 288 SKIP
C++ CreateDomNodes 使用 needSortByIndex=true 排序。同一 batch 内 parent 不存在的节点 depth=-1 排在最前 → "PARENT NOT FOUND" → SKIP。且 createNodeWithId 是一次性的——SKIP 后不会再重试。
最终修复方案
修复 1:模板层包装(index.hxml)— React 模式
为 complex 组件的两个 <scroll-view> 添加 wrapper <view>,确保 NestedScrollView 只有一个直接子节点:
<scroll-view>
<view style="width:100%"> ← wrapper (外层)
<view class="header">...</view>
<scroll-view scroll-x> ← tab bar
<view style="flex-direction:row"> ← wrapper (内层)
<view class="tab">全部</view>
...
</view>
</scroll-view>
<view class="content">...</view>
<view class="footer">...</view>
</view>
</scroll-view>
React hippy-react 始终为 ScrollView 包裹 wrapper View——这是已验证的模式。
修复 2:JS 层防止重复包装(NativeBackendElement.ts)
在动态 wrapper 代码中增加检查:如果已有子节点是 <view>(模板级 wrapper),不再创建额外 wrapper:
const existingIsView = existing.tagName === 'view';
if (!isWrapped && !existingIsView) {
// create wrapper...
}
修复 3:JS 层 recreate-on-flush — 解决 batch 分裂(NativeBackendElement.ts)
这是解决白屏的核心修复。triggerRender 同步调用会导致部分元素在 parent 不存在时被 SKIP。修复方案:
appendChild中不设置_isMounted=true(C++ create 可能被 SKIP)flush()中:如果_justCreated=true,每次 flush 都重新发出createNodeWithIdflush()末尾设置_isMounted=true+ 清除_justCreated
// flush() 中:
if (this._justCreated) {
// 每次 flush 都重发 create,直到 parent 存在、C++ create 成功
HippyBridge.createNodeWithId(...);
}
// ... 然后
HippyBridge.updateNode(...);
this._isMounted = true;
this._justCreated = false;
这样即使第一个 batch 的 create 被 SKIP,后续 batch 会重试,直到 parent 存在。
修复 4:JS 层根元素像素值(NativeBackendElement.ts)
appendChild ROOT-DIM-FIX:将 "100%" → getWindowWidth()/getWindowHeight() 像素值。
修正根元素检测条件:增加 parentIsOutsideJsTree(parent 不在 elementMap 中 = root-level)。
修复 5:诊断日志
C++: CheckValueType DLOG→LOG, SET_STYLE_VALUE_DIM 宏, DomNode::SetLayoutSize/DoLayout, RootNode::DoAndFlushLayout
Java: NativeRenderer createNode/updateLayout/endBatch/moveNode, RenderManager batch
探索过的无效方案
| 方案 | 问题 |
|---|---|
setContext() pre-create wrapper + 立即 flush |
C++ batch 排序导致 wrapper (depth=正确) 排在 children (depth=-1) 之后 → children 先处理 → SKIP |
render() 延迟到 microtask |
无法阻止同步 triggerRender 调用, batch 仍分裂 |
| JS wrapper + moveNode (原有方案) | moveNode 在 Java RenderNode tree 不生效 |
| 仅模板 wrapper (无 recreate-on-flush) | batch 分裂导致 288/290 节点 SKIP → 白屏 |
参考
- React
hippy-react/src/components/scroll-view.tsx:始终在 render() 中包裹<View>wrapper - Vue 3
hippy-vue-next:仅 warn,不处理多子节点 - 上游 Hippy Android 原生层:
NestedScrollView/HorizontalScrollView要求恰好一个直接子节点,不做自动包装
涉及文件
| 文件 | 变更 |
|---|---|
demos/ascf-ai-demo/ascf/ascf_src/skills/apidemos/components/complex/index.hxml |
为两个 ScrollView 添加 wrapper View |
driver/js/packages/hippy-ascf/src/native-backend/NativeBackendElement.ts |
ROOT-DIM-FIX 像素值, existingIsView 检查, recreate-on-flush |
dom/src/dom/taitank_layout_node.cc |
诊断日志 + SET_STYLE_VALUE_DIM 宏 + GetRootStyleWidth/Height |
dom/src/dom/dom_node.cc |
SetLayoutSize/DoLayout/TransferLayoutOutputsRecursive 日志 |
dom/src/dom/root_node.cc |
DoAndFlushLayout 日志 |
dom/include/dom/taitank_layout_node.h |
GetRootStyleWidth/GetRootStyleHeight 声明 |
renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java |
createNode/updateNode/updateLayout/endBatch/moveNode 日志 |
renderer/native/android/src/main/java/com/tencent/mtt/hippy/uimanager/RenderManager.java |
batch 日志 |