已开启
fix:处理Screen转场周期事件误发导致周期回调异常的问题 #62
fix:处理Screen转场周期事件误发导致周期回调异常的问题 #62
已开启
yanPeng创建于 5月28日
yanPeng
yanPeng
5月28日

fix:处理Screen转场周期事件误发导致周期回调异常的问题

likedislike
合并受阻
yanPeng
yanPeng
5月28日 评论:

⚠️ 🤖 AI 代码检视报告 ⚠️

总体评估: NEEDS_ATTENTION

问题统计:

  • 总问题数: 4
  • 严重问题: 0
  • 高危问题: 0

摘要:
该PR引入了Screen转场生命周期事件过滤机制以解决误发问题,实现逻辑清晰,但存在setTimeout返回值类型强制转换的平台兼容性风险及部分代码规范问题。

📊 详细报告
查看完整的审查详情,包括具体的问题描述、建议和代码位置:
🔗 查看详细报告


此评论由 OpenHarmony Insight 代码审查系统自动生成

likedislike
cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreen.ets
已过期
@@ -1104,0 +1179,4 @@
1179+ this.skipNextAppearEmit = false;
1180+ return;
1181+ }
1182+ if (this.shouldDropNavTxToJs()) {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【严重】【基础代码问题】【代码逻辑错误】onAppear/onDisAppear 中 shouldDropNavTxToJs() 二次检查可能破坏生命周期事件配对一致性

● 问题:onWillAppear 通过 shouldDropNavTxToJs() 判断是否过滤,并将结果通过 skipNextAppearEmit 传递给 onAppear。但 onAppear 中又额外做了一次 shouldDropNavTxToJs() 检查(第1182行)。当两次回调之间 navTxSlot 发生变化时(如快速连续导航导致 slot 被覆盖、或定时器清除了 slot),会出现 willAppear 已发送但 appear 被跳过的情况。onDisAppear 中也存在同样的问题(第1232行的 else if (!this.shouldDropNavTxToJs()) 检查)。

触发路径:

  1. 转场 A→B:slot = {from:A, to:B}
  2. Screen B 的 onWillAppear:B 是 toId → shouldDropNavTxToJs() 返回 false → skipNextAppearEmit = false → 发送 "willAppear"
  3. 在 onWillAppear 和 onAppear 之间,slot 被新的转场覆盖或被定时器清除
  4. Screen B 的 onAppear:skipNextAppearEmit 为 false,但 shouldDropNavTxToJs() 此时返回 true → 跳过 "appear"

● 影响:React Native JS 侧依赖 willAppear/appear、willDisappear/disappear 的配对一致性。事件配对被打破后,JS 侧屏幕状态机可能卡在"正在出现"或"正在消失"的中间态,导致交互异常。

● 建议:移除 onAppear 和 onDisAppear 中的 shouldDropNavTxToJs() 二次检查,仅依赖 skipNextAppearEmit / skipNextDisappearEmit 标志来保证事件配对一致性。过滤决策仅在 onWillAppear / onWillDisappear 中做出一次,后续回调严格跟随:

onAppear 修改为:

if (this.skipNextAppearEmit) {
  this.skipNextAppearEmit = false;
  return;
}
this.eventEmitter!.emit("appear", {})

onDisAppear 修改为:

if (this.skipNextDisappearEmit) {
  this.skipNextDisappearEmit = false;
} else {
  this.eventEmitter!.emit("disappear", {})
}

这样 canAppear/appear 和 willDisappear/disappear 的配对由构造保证,不会因 slot 变化而打破。

likedislike
System
系统消息系统
5月28日 评论:

changed this line on 6628d730 view diff detail

cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreen.ets
已过期
@@ -208,0 +215,4 @@
215+ private skipNextAppearEmit: boolean = false;
216+ private skipNextDisappearEmit: boolean = false;
217+ 
218+ static setNavTransitionSlot(stackTag: number, fromId: string, toId: string): void {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【一般】【基础代码问题】【代码逻辑错误】快速连续导航时 navTxSlot 被覆盖导致前序转场屏幕的 disappear 事件被误过滤

● 问题:setNavTransitionSlot 使用单个 Map 条目(navTxSlotByStack[stackTag])记录当前转场的 from/to pageId。当快速连续导航发生时(如 A→B 转场尚未完成时又发起 B→C),setNavTransitionSlot 会用新转场的 {from:B, to:C} 覆盖原来的 {from:A, to:B}。此时 Screen A 的 onDisAppear 回调执行时,shouldDropNavTxToJs() 检查 A 是否为新 slot 的 fromId 或 toId,发现都不匹配,于是错误地过滤掉 A 的 "disappear" 事件。

触发路径:

  1. 转场 A→B:slot = {from:A, to:B}
  2. A 的 onWillDisappear:A 是 fromId → shouldDropNavTxToJs() 返回 false → 发送 "willDisappear" ✓
  3. 快速发起转场 B→C:slot 被覆盖为 {from:B, to:C}
  4. A 的 onDisAppear:A 不在 slot 中 → shouldDropNavTxToJs() 返回 true → "disappear" 被跳过 ✗

● 影响:JS 侧收到 A 的 willDisappear 但未收到 disappear,屏幕状态机进入不一致状态。此场景在编程式快速连续导航(如多个 useEffect 触发的 navigate)中可能出现。

● 建议:考虑以下方案之一:

  1. 将 navTxSlotByStack 从单条目改为多条目累积(如数组),在新转场开始时追加而非覆盖,转场结束后逐条清除
  2. 在 shouldDropNavTxToJs 中,除了检查当前 slot 外,也检查该 screen 的 onWillDisappear 是否曾经发送过——如果发送过 willDisappear,则配对的 disappear 也必须发送
  3. 至少在覆盖 slot 前,检查旧 slot 的 fromId 对应的屏幕是否已完成 onDisAppear,如果未完成则延迟覆盖
likedislike
System
系统消息系统
5月28日 评论:

changed this line on 6628d730 view diff detail

cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
已过期
@@ -775,2 +776,4 @@
776+ RNSScreen.clearNavTransitionSlot(this.tag);
775777 return undefined;
776778 }
779+ if (from.navDestinationId && to.navDestinationId) {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【建议】【基础代码问题】【验证问题】navDestinationId 为空时未设置 slot,过滤机制不生效

● 问题:customNavContentTransitionHandler 中,仅当 from.navDestinationId 和 to.navDestinationId 同时存在时才调用 setNavTransitionSlot 设置过滤 slot。当其中任一为空(如首页导航、NavPathStack.clear()/popToRoot() 等场景),slot 不会被设置,导致 shouldDropNavTxToJs() 始终返回 false,生命周期事件过滤机制完全失效。

● 影响:在这些边界场景下,原有 bug(转场期间非参与屏幕误发生命周期事件到 JS)仍会发生。不过当前行为不会比修改前更差(修改前也无过滤),属于功能覆盖不完全而非回退。

● 建议:作为已知限制,建议在代码注释中说明此边界情况。若需进一步完善,可考虑在 navDestinationId 缺失时使用其他标识(如 NavContentInfo.name 或 index)来建立 slot 映射。

likedislike
System
系统消息系统
5月28日 评论:

changed this line on 6628d730 view diff detail

yanPengyanPeng
5月28日 强制推送  1 个提交:6628d730-fix:处理Screen转场周期事件误发导致周期回调异常的问题
yanPeng
yanPeng
5月28日 评论:

⚠️ 🤖 AI 代码检视报告 ⚠️

总体评估: NEEDS_ATTENTION

问题统计:

  • 总问题数: 6
  • 严重问题: 0
  • 高危问题: 0

摘要:
本次PR引入了NavPathReorderLifecycleGate机制来解决Screen转场周期事件误发问题,架构设计合理,但存在未使用变量、空else块等代码质量问题,以及JSON.stringify比较和定时器类型处理等潜在隐患。

📊 详细报告
查看完整的审查详情,包括具体的问题描述、建议和代码位置:
🔗 查看详细报告


此评论由 OpenHarmony Insight 代码审查系统自动生成

likedislike
cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
已过期
@@ -686,0 +719,4 @@
719+ // screenTag入栈
720+ this.stackController?.pushPathByName(screenTag, null);
721+ // 栈排序,从上面插入位置将栈中按序移至栈顶,重新排序
722+ for (let i = index + 1; i <= paths.length; i++) {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【严重】【基础代码问题】【代码逻辑错误】sortScreens循环越界访问

● 问题:sortScreens方法中循环条件 i <= paths.length 会导致数组越界访问。paths 变量是在 runNavPathReorderMutation 调用之前捕获的旧路径快照,当 index = paths.length - 1 时,循环起始值 i = paths.length,此时 paths[paths.length]undefined,将 undefined 传入 moveToTop(undefined, false) 可能导致导航栈排序异常。

● 影响:严重。在屏幕排序场景下(如 onUpdate 触发 sortScreens),当新插入的 screenTag 大于所有已有路径时,index 会落在最后一个位置,触发越界访问,可能导致导航栈状态异常或运行时错误。

● 建议:将循环条件从 i <= paths.length 修改为 i < paths.length

for (let i = index + 1; i < paths.length; i++) {
  this.stackController?.moveToTop(paths[i], false);
}
likedislike
System
系统消息系统
5月28日 评论:

changed this line on cef54199 view diff detail

cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
@@ -103,0 +105,4 @@
105+ this.scheduleClearNavReorderLifecycleGate(1200);
106+ }
107+ 
108+ private clearNavReorderLifecycleGate(): void {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【一般】【基础代码问题】【资源使用问题】aboutToDisappear未清理NavPathReorderLifecycleGate静态状态

● 问题:clearNavReorderLifecycleGate() 方法已定义(第108行),但 aboutToDisappear() 中并未调用它。当 RNSScreenStack 组件销毁时,NavPathReorderLifecycleGate 中以 this.tag 为 key 的静态 Map 条目(transitionParticipantByStacksuppressTransitionLifecycleByStacklastReorderAffectedByStackpathSnapshotByStackclearTimerByStack)不会被清理。虽然 scheduleClear 定时器在大多数场景下最终会触发清理,但如果组件销毁时定时器已经触发完毕且无新操作,这些条目将永久驻留。

● 影响:一般。多个 RNSScreenStack 实例创建和销毁后,静态 Map 中累积的陈旧条目造成内存泄漏,长时间运行可能影响性能。

● 建议:在 aboutToDisappear() 中添加清理调用:

aboutToDisappear() {
  this.clearNavReorderLifecycleGate();
  // ... 其余清理逻辑
}
likedislike
yanPeng
yanPeng
5月28日 评论:

已修改

cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
已过期
@@ -475,2 +514,4 @@
475514 onDescriptorChange() {
476515 const newChildren = this.descriptor.childrenTags;
516+ const cmd = RNSScreenStack.screenIndexObjectMap.get(this.tag);
517+ const action: string = cmd?.action ?? '?';
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【建议】【基础代码问题】【可读性问题】onDescriptorChange中action变量未使用

● 问题:onDescriptorChange() 中新增的 action 变量(const action: string = cmd?.action ?? '?')被赋值后未在任何地方使用,属于死代码。

● 影响:建议。不影响功能,但降低了代码可读性,可能误导维护者以为该变量有后续用途。

● 建议:如果该变量是为后续功能预留,请添加使用逻辑;否则移除以下两行:

const cmd = RNSScreenStack.screenIndexObjectMap.get(this.tag);
const action: string = cmd?.action ?? '?';
likedislike
System
系统消息系统
5月28日 评论:

changed this line on cef54199 view diff detail

cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
已过期
@@ -441,29 +477,30 @@
441477 return;
442478 }
443- this.stackController.clear();
444- for (let i = differentElementIndex; i < newChildren.length; i++) {
445- this.stackController.pushPathByName(newChildren[i].toString(), null, false)
446- }
479+ this.runNavPathReorderMutation('updateStack.clear+push', () => {
480+ this.stackController.clear();
481+ for (let i = differentElementIndex; i < newChildren.length; i++) {
482+ this.stackController.pushPathByName(newChildren[i].toString(), null, false)
483+ }
484+ });
447485 this.isReset = true;
448486 return;
449487 }
@@ -455,8 +493,8 @@ export struct RNSScreenStack {
455493 this.stackController.pushPathByName(newChildren[i].toString(), null)
456494 }
457495 } else {
458- this.stackController.popToIndex(differentElementIndex === -1 ? newChildren.length - 1 :
459- differentElementIndex - 1)
496+ const popIdx = differentElementIndex === -1 ? newChildren.length - 1 : differentElementIndex - 1;
497+ this.stackController.popToIndex(popIdx)
460498 for (let i = differentElementIndex; i < newChildren.length; i++) {
461499 this.stackController.pushPathByName(newChildren[i].toString(), null)
462500 }
@@ -469,11 +507,14 @@ export struct RNSScreenStack {
469507 }
470508 }
471509 }
510+ } else {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【建议】【软件设计】【冗余重复代码】空的else分支无实际作用

● 问题:updateStack() 方法中新增的空 else { } 分支(第510-511行)没有任何逻辑,属于冗余代码。

● 影响:建议。不影响功能,但降低代码可读性。

● 建议:移除空的 else 分支,保持代码简洁:

if (differentElementIndex !== -1) {
  // ... 现有逻辑
}
// 无需 else 分支
likedislike
System
系统消息系统
5月28日 评论:

changed this line on cef54199 view diff detail

yanPengyanPeng
5月28日 强制推送  1 个提交:cef54199-fix:处理Screen转场周期事件误发导致周期回调异常的问题
yanPengyanPeng
5月28日 强制推送  1 个提交:ae527ab1-fix:处理Screen转场周期事件误发导致周期回调异常的问题
cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
@@ -103,0 +113,4 @@
113+ NavPathReorderLifecycleGate.scheduleClear(this.tag, delayMs);
114+ }
115+ 
116+ static shouldEmitScreenTransitionLifecycle(stackTag: number, screenTag: number): boolean {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【致命】【基础代码问题】【代码逻辑错误】shouldEmitScreenTransitionLifecycle 已定义但从未被调用,NavPathReorderLifecycleGate 抑制机制完全无效

● 问题:shouldEmitScreenTransitionLifecycle 静态方法已在 RNSScreenStack(第116行)中定义,它委托给 NavPathReorderLifecycleGate.shouldEmitTransitionLifecycleToJs() 来判断某个 Screen 的生命周期事件是否应发送到 JS 侧。然而,RNSScreen.ets 中的生命周期回调(onWillAppear、onAppear、onWillDisappear、onDisAppear,约第1097-1141行)均无条件地 emit 事件,从未调用此门控方法。RNSScreen.ets 第41行已 import RNSScreenStack,具备调用条件,但未接入。

这意味着 NavPathReorderLifecycleGate 正确地构建了 suppress 集合和 participants 集合,但这些数据从未被消费——所有 Screen 的 onWillAppear/onAppear/onWillDisappear/onDisAppear 事件仍会被无条件发送到 JS 侧,与未引入该 Gate 之前行为完全相同。整个 NavPathReorderLifecycleGate 模块的存在没有任何实际效果。

● 影响:致命。PR 的目标是修复 Screen 导航/重排时的生命周期事件问题,但核心门控逻辑未被接入,问题实际上没有被修复。当 NavPathStack 发生重排(如 stackUpadate、sortScreens)时,不应发送到 JS 侧的 onAppear/onDisAppear 事件仍然会被发送,可能导致 JS 侧的导航状态与原生侧不一致,进而引发页面闪烁、导航栈错乱等问题。

● 建议:在 RNSScreen.ets 的生命周期回调中加入门控检查。示例:

onWillAppear 回调:

.onWillAppear(() => {
  if (RNSScreenStack.shouldEmitScreenTransitionLifecycle(this.parentTag, this.tag)) {
    this.eventEmitter!.emit("willAppear", {})
  }
})

onAppear 回调:

.onAppear(() => {
  if (RNSScreenStack.shouldEmitScreenTransitionLifecycle(this.parentTag, this.tag)) {
    this.eventEmitter!.emit("appear", {})
  }
})

onWillDisappear 和 onDisAppear 回调同理。注意 onDisAppear 中除了 emit 事件外还有其他清理逻辑(如 releaseScreenOrientation、scheduleDeferredUnregister),这些清理逻辑不应被门控影响,仅门控 emit 调用即可。

likedislike
yanPeng
yanPeng
5月28日 评论:

已修改

cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
@@ -172,3 +203,4 @@
203+ }
172204 }
173205 break;
174206 case 'REPLACE': {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【严重】【基础代码问题】【代码逻辑错误】updateScreenStack 中 REPLACE/POP_TO_TOP/POP_TO/RESET 操作缺少 mergeTransitionParticipants 调用

● 问题:在 updateScreenStack 方法中,GO_BACK(第186-191行)和 POP(第198-203行)操作调用了 NavPathReorderLifecycleGate.mergeTransitionParticipants(),将当前栈顶 Screen 和新栈顶 Screen 注册为转场参与者。然而,REPLACE(第206-215行)、POP_TO_TOP(第216-220行)、POP_TO(第221-228行)、RESET(第229-257行)操作均未调用 mergeTransitionParticipants

关键时序问题:stackUpadate 方法(第303-318行)在执行路径变更前调用了 suppressNavPathExceptParticipants,此时如果 updateScreenStack 中未注册任何参与者,则 suppress 集合将包含 NavPathStack 中的所有 Screen。随后 customNavContentTransitionHandler 会注册 from/to 参与者,但对于使用 pushPathByName(name, null, false)(无动画)的 stackUpadate 场景,customNavContentTransitionHandler 可能不会被触发,导致参与者始终为空,所有 Screen 的生命周期事件被持续抑制直到 scheduleClear 定时器触发(1200ms)。

● 影响:严重。在前述 shouldEmitScreenTransitionLifecycle 接入后,REPLACE/POP_TO_TOP/POP_TO/RESET 操作可能导致:(1) 活跃转场 Screen 被错误抑制(如果 stackUpadate 先于 customNavContentTransitionHandler 执行);(2) 无动画场景下参与者永远不被注册,导致 1200ms 窗口内所有生命周期事件被错误抑制。

● 建议:在 updateScreenStack 的 REPLACE、POP_TO_TOP、POP_TO、RESET 分支中补充 mergeTransitionParticipants 调用,注册活跃转场参与者。示例:

REPLACE 分支:

case 'REPLACE': {
  if (this.screenStackPath && this.screenStackPath.length >= 1) {
    NavPathReorderLifecycleGate.mergeTransitionParticipants(this.tag, [
      this.screenStackPath[this.screenStackPath.length - 1],
      newChild[newChild.length - 1]
    ]);
    // ... 原有逻辑
  }
}

POP_TO_TOP、POP_TO、RESET 分支同理,应根据实际转场的 from/to Screen 注册参与者。

likedislike
yanPeng
yanPeng
5月28日 评论:

POP_TO_TOP/POP_TO/RESET/REPLACE类型暂不注册,这里的栈更新流程和pop/goback不同

zhuyongxiang
zhuyongxiang
5月28日 评论:

已检视,无问题

likedislike
yanPengyanPeng
5月28日 强制推送  1 个提交:1c672d61-fix:处理Screen转场周期事件误发导致周期回调异常的问题
cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
@@ -278,3 +304,1 @@
278- for (let i = 0; i < newChildren.length - 1; i++) {
279- if(this.stackController?.getAllPathName().indexOf(newChildren[i].toString()) === -1){
280- this.stackController.pushPathByName(newChildren[i].toString(), null, false)
304+ NavPathReorderLifecycleGate.suppressNavPathExceptParticipants(this.tag, this.stackController);
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【严重】【基础代码问题】【代码逻辑错误】stackUpadate 在参与者未注册时抑制所有 Screen 生命周期事件

● 问题:stackUpadate 方法在执行导航路径变更前调用 suppressNavPathExceptParticipants,该方法将所有不在 transitionParticipantByStack 中的路径加入 suppress 集合。但 transitionParticipantByStack 的参与者仅在 updateScreenStack 的 GO_BACK/POP 分支和 customNavContentTransitionHandler 中注册。以下场景会导致参与者未注册:

  1. updateScreenStack 未被调用(isCommandReceivedFlagisStackUpdateFlag 为 false 时跳过)
  2. 操作类型为 REPLACE/POP_TO_TOP/POP_TO/RESET 时,updateScreenStack 中未调用 mergeTransitionParticipants

当参与者集合为空时,所有路径均被加入 suppress 集合,导致所有 Screen 的 willAppear/appear/willDisappear/disappear 事件在 1200ms 内全部被抑制。这些事件在 suppress 清除后不会重新触发,JS 侧将永久丢失这些生命周期通知。

● 影响:严重。在 REPLACE/POP_TO_TOP/POP_TO/RESET 等场景下,目标 Screen 的 appear 事件被错误抑制,JS 侧 useEffect 等依赖生命周期事件的逻辑不会执行,可能导致页面状态不一致或功能异常。

● 建议:在 stackUpadate 中根据当前操作类型显式注册参与者,或在 updateScreenStack 的 REPLACE/POP_TO_TOP/POP_TO/RESET 分支中补充 mergeTransitionParticipants 调用。例如在 REPLACE 分支中:

case 'REPLACE': {
  if (this.screenStackPath && this.screenStackPath.length >= 1) {
    NavPathReorderLifecycleGate.mergeTransitionParticipants(this.tag, [
      this.screenStackPath[this.screenStackPath.length - 1],
      newChild[newChild.length - 1]
    ]);
    // ... 原有逻辑
  }
}

对于 POP_TO_TOP/POP_TO/RESET,应将目标栈顶 Screen 和当前栈顶 Screen 作为参与者注册。

likedislike
yanPeng
yanPeng
5月28日 评论:

POP_TO_TOP/POP_TO/RESET/REPLACE类型暂不注册,这里的栈更新流程和pop/goback不同

cpf-manager
cpf-manager5月28日进行代码检视2
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
已过期
@@ -781,0 +822,4 @@
822+ ]);
823+ if (operation === NavigationOperation.POP) {
824+ NavPathReorderLifecycleGate.suppressNavPathExceptParticipants(this.tag, this.stackController);
825+ this.scheduleClearNavReorderLifecycleGate(1500);
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【一般】【基础代码问题】【稳定性问题】POP 操作的 suppress 定时器可被后续 Mutation 替换导致过早解除

● 问题:在 customNavContentTransitionHandler 中,POP 操作设置了 scheduleClearNavReorderLifecycleGate(1500) 定时器。但随后 updateStack 可能调用 runNavPathReorderMutation,后者内部调用 scheduleClearNavReorderLifecycleGate(1200)。由于 scheduleClear 会取消前一个定时器并设置新定时器,1500ms 定时器被替换为 1200ms 定时器,导致 suppress 状态可能比预期提前 300ms 解除。

如果动画正在执行且尚未完成(例如 transitionDuration 设置了较长时长),suppress 提前解除可能允许非参与者 Screen 发出本应被抑制的生命周期事件,导致 JS 侧收到错误的 willAppear/appear 或 willDisappear/disappear 事件。

● 影响:一般。在快速连续操作或长动画场景下,JS 侧可能收到冗余或错误的 Screen 生命周期事件,导致短暂的状态不一致。

● 建议:考虑在 runNavPathReorderMutation 中取当前定时器剩余时间和新延迟的最大值,而非直接替换。或在 scheduleClear 中增加逻辑,仅在新的延迟大于剩余时间时才替换定时器:

private scheduleClearNavReorderLifecycleGate(delayMs: number = 500): void {
  // 可考虑:仅在需要更长延迟时才替换现有定时器
  NavPathReorderLifecycleGate.scheduleClear(this.tag, delayMs);
}

或者在 NavPathReorderLifecycleGate.scheduleClear 中增加策略参数,避免短延迟覆盖长延迟。

likedislike
System
系统消息系统
5月31日 评论:

changed this line on 923c9f0f view diff detail

cpf-manager
cpf-manager5月28日进行代码检视1
tester/harmony/screens/src/main/ets/utils/NavPathReorderLifecycleGate.ets
@@ -0,0 +129,4 @@
129+ commonAfter.push(t);
130+ }
131+ });
132+ if (JSON.stringify(commonBefore) !== JSON.stringify(commonAfter)) {
cpf-manager
cpf-manager5月28日评论:

【AI-Review】【建议】【基础代码问题】【性能和效率问题】computeReorderAffected 使用 JSON.stringify 比较数组顺序

● 问题:computeReorderAffected 方法在第 132 行使用 JSON.stringify(commonBefore) !== JSON.stringify(commonAfter) 来判断两个数组的元素顺序是否一致。这种方式存在两个问题:

  1. 性能:JSON.stringify 需要遍历整个数组并生成字符串,时间复杂度 O(n),加上字符串比较 O(n),总复杂度较高。此方法已在 allTags.forEach 循环中使用了 indexOf(O(n)),使得整体复杂度为 O(n²)。
  2. 鲁棒性:JSON.stringify 的结果依赖于元素的字面量表示,如果 tag 值包含特殊字符(虽然当前为数字字符串,不构成实际问题),可能导致比较结果不符合预期。

● 影响:建议。当前 NavPathStack 路径数量通常较小(< 20),性能影响有限。但使用直接循环比较更清晰、更高效。

● 建议:替换为直接循环比较:

let orderChanged = false;
if (commonBefore.length !== commonAfter.length) {
  orderChanged = true;
} else {
  for (let i = 0; i < commonBefore.length; i++) {
    if (commonBefore[i] !== commonAfter[i]) {
      orderChanged = true;
      break;
    }
  }
}
if (orderChanged) {
  commonBefore.forEach((t: string) => affected.add(t));
  commonAfter.forEach((t: string) => affected.add(t));
}
likedislike
yanPeng
yanPeng
5月29日 评论:

start build

likedislike
openharmony_ci
openharmony_ci成员
5月29日 评论:

该仓未配置门禁,无法触发构建。

likedislike
yanPengyanPeng
5月31日 强制推送  1 个提交:923c9f0f-fix:处理Screen转场周期事件误发导致周期回调异常的问题
cpf-manager
cpf-manager5月31日进行代码检视1
tester/harmony/screens/src/main/ets/components/RNSScreen.ets
@@ -1102,2 +1119,4 @@
1119+ this.eventEmitter!.emit("appear", {})
1120+ }
11021121 })
11031122 .onShown(async () => {
cpf-manager
cpf-manager5月31日评论:

【AI-Review】【严重】【基础代码问题】【稳定性问题】onShown回调未受Gate保护,NavPathStack重排时可能污染全局导航状态

● 问题:PR对onWillAppearonAppearonWillDisappearonDisAppear四个生命周期回调都加了shouldEmitTransitionLifecycleToJs()守卫,但onShown回调(第1122行)未做同样的守卫检查。onShown中更新了RNSScreen.TOP_PAGEIDRNSScreen.TOP_PAGEID_ARRRNSScreen.TOP_PAGE_PRESENTATION_MAPRNSScreen.TOP_PAGE_PREVENT_MAPRNSScreen.TOP_PAGE_TAG_MAP等全局静态状态。

当NavPathStack发生重排(如stackUpadatesortScreens)时,ArkUI Navigation框架可能触发中间屏幕的onShown回调。此时非栈顶屏幕的onShown会将TOP_PAGEID设置为错误的pageId,导致:

  1. 后续onBackPressed回退到错误的屏幕
  2. isFirstScreenInStack判断错误
  3. Modal/dismiss行为异常(因为TOP_PAGE_PRESENTATION_MAPTOP_PAGE_PREVENT_MAP被错误覆盖)

● 影响:严重。在NavPathStack重排场景下,全局导航状态被中间屏幕污染,导致后续导航操作(返回、dismiss等)指向错误目标,可能造成页面卡死或闪退。

● 建议:对onShown中的全局状态更新也加入shouldEmitTransitionLifecycleToJs()守卫,仅允许真正的转场参与屏幕更新全局导航状态:

.onShown(async () => {
  if (this.shouldEmitTransitionLifecycleToJs()) {
    RNSScreen.TOP_PAGEID = this.pageId;
    if(!RNSScreen.TOP_PAGEID_ARR.some((num)=> num === this.pageId)) {
      RNSScreen.TOP_PAGEID_ARR.push(this.pageId)
    } else {
      let arr = RNSScreen.TOP_PAGEID_ARR;
      RNSScreen.TOP_PAGEID_ARR = arr.slice(0, arr.indexOf(this.pageId)+1)
    }
    RNSScreen.TOP_PAGE_PRESENTATION_MAP.set(this.pageId, this.stackPresentationMode)
    RNSScreen.TOP_PAGE_PREVENT_MAP.set(this.pageId, this.preventNativeDismiss)
    RNSScreen.TOP_PAGE_TAG_MAP.set(this.pageId, this.tag)
  }
  // 屏幕自身的状态更新可保留,不受Gate影响
  await this.updateState(true);
  await this.updateHeaderConfigState();
  this.setScreenOrientation()
  // ...
})

注意:Gate清理(scheduleClear)触发后,被抑制的全局状态不会自动补偿。如果此场景存在,需在scheduleClear回调中补充对当前真实栈顶屏幕的状态刷新逻辑。

likedislike
cpf-manager
cpf-manager5月31日进行代码检视1
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
@@ -100,2 +148,4 @@
148+ }
149+ 
100150 // ===== 修改:动画完成处理 =====
101151 private onAnimationComplete() {
cpf-manager
cpf-manager5月31日评论:

【AI-Review】【严重】【基础代码问题】【代码逻辑错误】REPLACE操作完成时screensNativeStackfinishTransitioning事件被重复发送

● 问题:当REPLACE操作伴随原生返回键触发(backPressedFlag=true)时,screensNativeStackfinishTransitioning事件会被发送两次:

  1. animateToonFinish回调先调用this.backBtnPressed()(第993/1006行),backBtnPressed内部调用this.onAnimationComplete()
  2. onAnimationComplete()(第151行)发送一次screensNativeStackfinishTransitioning,包含operationIdduration字段,然后将this.currentReplaceOperationId置为null
  3. 随后onFinish继续调用this.emitFinishTransitioningToJs(actionType, opId)(第996/1009行),此时opId已被置为null,emitFinishTransitioningToJs再次发送screensNativeStackfinishTransitioning,但缺少_operationId

JS侧会收到两次screensNativeStackfinishTransitioning事件:第一次包含完整的操作ID,第二次缺失操作ID。第二次事件可能被JS侧误认为是另一个未跟踪的转场完成,导致导航状态混乱。

● 影响:严重。JS侧可能因为重复的完成事件导致导航栈状态不一致,具体表现为:_operationId匹配失败导致操作确认遗漏,或重复的状态更新引发竞态条件。

● 建议:将onAnimationComplete中的screensNativeStackfinishTransitioning发送逻辑合并到emitFinishTransitioningToJs中,消除重复发送。在emitFinishTransitioningToJs中补充operationIdduration字段:

private emitFinishTransitioningToJs(actionType: string, operationId: string | null = null): void {
  this.eventEmitter!.emit("finishTransitioning", {});
  this.scheduleClearNavReorderLifecycleGateAfterTransitionEnd();
  const payload: ESObject = {
    flag: true,
    tag: this.screenTag || this.tag,
    action: {
      type: actionType,
      ...(operationId ? { _operationId: operationId } : {})
    }
  };
  if (actionType === 'REPLACE' && operationId) {
    payload.operationId = operationId;
    payload.duration = Date.now() - this.replaceStartTime;
  }
  this.ctx.rnInstance.emitDeviceEvent('screensNativeStackfinishTransitioning', payload);
}

同时将onAnimationComplete简化为仅做操作记录和清理,不再单独发送事件。

likedislike
cpf-manager
cpf-manager5月31日进行代码检视1
tester/harmony/screens/src/main/ets/components/RNSScreenStack.ets
@@ -103,0 +121,4 @@
121+ }
122+ 
123+ private emitFinishTransitioningToJs(actionType: string, operationId: string | null = null): void {
124+ this.eventEmitter!.emit("finishTransitioning", {});
cpf-manager
cpf-manager5月31日评论:

【AI-Review】【一般】【基础代码问题】【稳定性问题】emitFinishTransitioningToJs中eventEmitter非空断言存在崩溃风险

● 问题:emitFinishTransitioningToJs(第123行)中this.eventEmitter!.emit("finishTransitioning", {})使用了非空断言!。该方法被animateToonFinish回调(第996/1009行)调用,该回调通过闭包捕获了this。如果RNSScreenStack组件在动画执行期间被销毁(如页面快速切换、模块卸载),aboutToDisappear已被调用但eventEmitter未被显式置空,此时onFinish回调仍会触发并访问可能已失效的eventEmitter,导致崩溃。

类似地,this.ctx.rnInstance.emitDeviceEvent(...)中的this.ctx也存在同样风险。

● 影响:一般。在快速切换页面或模块卸载的时序下,组件销毁与动画完成回调存在竞态,可能导致应用崩溃。

● 建议:添加防御性检查:

private emitFinishTransitioningToJs(actionType: string, operationId: string | null = null): void {
  if (!this.eventEmitter) {
    return;
  }
  this.eventEmitter.emit("finishTransitioning", {});
  // ...
}

或在aboutToDisappear中取消正在执行的动画并清理回调引用。

likedislike