From b5d837cc45bb67ff51ce085e7e9b7b6cecdae9e6 Mon Sep 17 00:00:00 2001 From: clz Date: Mon, 24 Aug 2026 17:13:00 +0800 Subject: [PATCH] docs: add stability remediation evidence --- AGENTS.md | 35 +++++++ change-notes.zip | Bin 0 -> 40257 bytes ...ble-request-client-subscription-cleanup.md | 44 +++++++++ ...-request-client-notification-coalescing.md | 37 +++++++ ...12-trade-sdk-market-price-cache-cleanup.md | 42 ++++++++ .../13-webview-overview-request-cleanup.md | 37 +++++++ ...munication-request-buffer-frame-cleanup.md | 41 ++++++++ .../15-ads-ball-pixelmap-id-cleanup.md | 43 +++++++++ .../01-communication-source-dependency.md | 38 ++++++++ .../change-notes/02-xml-taskpool-refactor.md | 45 +++++++++ ...03-communication-taskpool-decompression.md | 59 ++++++++++++ .../change-notes/04-curve-parsing-taskpool.md | 33 +++++++ ...05-trade-sdk-native-lock-app-mitigation.md | 57 +++++++++++ .../06-communication-worker-ab-performance.md | 39 ++++++++ .../07-trade-sdk-match-push-optimization.md | 63 ++++++++++++ .../08-trade-page-online-status-cache.md | 55 +++++++++++ .../09-drawline-dedup-optimization.md | 35 +++++++ .../10-top10-online-sampling-followups.md | 39 ++++++++ .../change-notes/11-top10-followup-fixes.md | 90 ++++++++++++++++++ 需求开发文档.md | 80 ++++++++++++++++ 20 files changed, 912 insertions(+) create mode 100644 AGENTS.md create mode 100644 change-notes.zip create mode 100644 harmony-ths-futures-apm-oom/doc/change-notes/10-table-request-client-subscription-cleanup.md create mode 100644 harmony-ths-futures-apm-oom/doc/change-notes/11-table-request-client-notification-coalescing.md create mode 100644 harmony-ths-futures-apm-oom/doc/change-notes/12-trade-sdk-market-price-cache-cleanup.md create mode 100644 harmony-ths-futures-apm-oom/doc/change-notes/13-webview-overview-request-cleanup.md create mode 100644 harmony-ths-futures-apm-oom/doc/change-notes/14-communication-request-buffer-frame-cleanup.md create mode 100644 harmony-ths-futures-apm-oom/doc/change-notes/15-ads-ball-pixelmap-id-cleanup.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/01-communication-source-dependency.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/02-xml-taskpool-refactor.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/03-communication-taskpool-decompression.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/04-curve-parsing-taskpool.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/05-trade-sdk-native-lock-app-mitigation.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/06-communication-worker-ab-performance.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/07-trade-sdk-match-push-optimization.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/08-trade-page-online-status-cache.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/09-drawline-dedup-optimization.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/10-top10-online-sampling-followups.md create mode 100644 harmony-ths-futures-freeze/doc/change-notes/11-top10-followup-fixes.md create mode 100644 需求开发文档.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..24da961 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,35 @@ +# Repository Guidelines + +## Project Structure & Scope + +This repository is an evidence bundle for HarmonyOS futures-app freeze and OOM remediation; it does not contain buildable application source code. + +- `harmony-ths-futures-freeze/doc/change-notes/` contains freeze/performance remediation notes, numbered `01`–`11`. +- `harmony-ths-futures-apm-oom/doc/change-notes/` contains memory-retention remediation notes, numbered `10`–`15`. +- `change-notes.zip` is the distributable archive of those two directories. Keep it synchronized only when a deliverable archive is requested. + +Treat notes as technical records: distinguish changes made in the app, communication HAR, and trade SDK repositories, and do not imply that an upstream change is present here. + +## Documentation Style & Naming + +Write Markdown in Chinese, matching the existing material. Use direct `#` and `##` headings and concise paragraphs. New notes should follow the established numbered, kebab-case pattern, for example `12-new-memory-audit.md`. + +Use fenced code blocks for commands and backticks for paths, module names, APIs, package versions, branches, and commit IDs. Preserve exact measurements, units, dates, device details, and command output; identify estimates and limitations explicitly. Prefer tables for baseline-versus-result comparisons. + +## Validation and Development Commands + +There is no local build, formatter, linter, or test suite. Do not run application builds from this repository. Record validation performed in the relevant upstream HarmonyOS checkout, for example: + +```sh +devecocli build --modules entry@default +devecocli check lint +devecocli log --crash --bundle-name +``` + +State whether each command was actually run, its target (module/device), and any skipped coverage. Do not present a suggested command as a completed verification. + +## Change Notes and Review + +Each remediation note should include: the fix commit(s) and branch(es), problem statement, changed scope, implementation/mitigation, behavior and risk boundaries, and concrete verification results. Keep unrelated cleanup and temporary test artifacts out of documented production changes. + +This bundle has no Git metadata, so no local commit-message convention can be inferred. Use a short imperative subject such as `docs: add table request cleanup note`. Pull requests should name the affected upstream repository and commit, link the incident or ticket when available, and include before/after metrics or screenshots/log excerpts for behavior claims. diff --git a/change-notes.zip b/change-notes.zip new file mode 100644 index 0000000000000000000000000000000000000000..e5f9a66fd8918e70071ae8708805353020344c23 GIT binary patch literal 40257 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,销毁后清空;兜底 dismiss 仍按历史登记 ID 匹配当前页面 dialog。 +5. 对同一 PixelMap 被同时作为完整图和半图的异常情况只调用一次 `release()`。 + +## 影响范围 + +- 仅修改首页广告悬浮球的图片与 dialog 生命周期管理。 +- 不修改广告数据、图片地址、展示样式、点击行为或 300 ms 创建防抖。 +- 延迟释放只持有已 dismiss 的当前两张图片 300 ms,避免立即释放与弹窗关闭动画竞争。 + +## 预期收益与边界 + +成功展示的每个悬浮球最多持有当前 2 张 PixelMap,销毁后释放;单图失败不再残留另一张;ID 集合在销毁后归零。Native 图片内存收益取决于图片尺寸、像素格式和系统解码缓存,本次未取得真实图片尺寸与设备 PSS,因此不估算具体 MB。 + +该修复不治理其他广告弹窗、网络图片缓存、系统图形缓存或 GPU/DMA 内存,也不代表 Native PSS 会在 release 后立即同步下降。 + +## 验证结果 + +1. 对实际 `AdsBallManager.ets` 转译后执行 5,000 轮成功创建/销毁、5,000 轮半图失败和 1 轮异步过期压力测试。 +2. 共创建 15,001 个模拟 PixelMap,全部恰好释放一次;最终 retained dialog ID 为 0、retained dialog 为 0。 +3. 新实现随主工程通过直接 Hvigor `entry@default` Debug HAP 构建,完整 ArkTS 编译、打包和签名成功;未执行安装。 +4. 压力测试中将 300 ms 延迟计时器同步触发以校验所有权计数;真实设备关闭动画时序、Native PSS 回落和 OOM 日志未验证。 +5. 临时压力测试代码与日志已删除,未在生产代码中保留调试计数。 +6. 2026-08-21 在 Pura 90 API 24 模拟器确认首页 VIP 广告真实展示并成功关闭,关闭后底部 Tab 恢复可操作;同次测试未发现 OOM、JS Crash、CppCrash、SIGSEGV 或 SIGABRT。 +7. 本次设备操作只完成 1 次真实广告展示/关闭,未取得 PixelMap Native 归属计数,也未完成 50~100 轮重复广告创建,因此不宣称真实设备 Native PSS 稳态已经验证;重复释放正确性仍以 15,001 个模拟 PixelMap 的白盒压力测试为依据。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/01-communication-source-dependency.md b/harmony-ths-futures-freeze/doc/change-notes/01-communication-source-dependency.md new file mode 100644 index 0000000..06bb8f9 --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/01-communication-source-dependency.md @@ -0,0 +1,38 @@ +# 通信库源码联调依赖说明 +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `ohos_mobile_lib_communication` | `work-freeze-lib-communication-1.1.1` | `031d4cf221b4da6d9aaf2db443ba293559a0d4a5` | 通信 Worker 解码与队列优化(本说明所支撑的修复) | + +> 本笔记为通信库源码联调/依赖说明,非生产修复本身;其支撑的修复见 `03`、`04`。 + +## 目的 + +解压与曲线修复基于 `lib_communication` `1.1.1-beta.1`,基线提交为 `7d3f126`,与应用原有 `lib_communication20250903.har` 的源码版本一致。性能验证期间曾将相邻仓库中的通信模块复制到当前工程 `lib_communication/`,以便临时加日志、打点和抓取 trace。 + +## 修改文件 + +- `oh-package.json5` +- `build-profile.json5` +- `base_ugc/oh-package.json5` +- `biz_forecast/oh-package.json5` +- `service_aigroup/oh-package.json5` +- `lib_communication/` + +## 接入配置 + +验证期间,根依赖和 `overrides` 使用 `file:./lib_communication`,模块内直接依赖使用 `file:../lib_communication`,根 `build-profile.json5` 将其注册为 HAR 模块。构建 `entry` 时直接编译这份通信源码。 + +新增 Worker 的运行时导入闭包在验证期间按最小范围登记到子线程门禁: + +- `ConcurrentDecodeWorker.ts -> @ohos.worker` +- `ConcurrentDecodeWorker.ts -> HXZip.ts` + +## 影响与风险 + +本地源码保留原公开接口,包含 Worker 解压和曲线解析实现。该目录仅用于性能验证,不应作为正式交付方式。验证结束后已删除根 `build-profile.json5` 中的模块注册,并将根工程及三个业务模块的依赖恢复为 `libs/lib_communication20250903.har`。 + +## 验证 + +同一源码构建的通信库 debug HAR 及 `entry@default` debug HAP 均已完成构建、安装和运行验证。三轮 A/B 测试确认主线程 CPU 时间中位数下降 20.2%,详细数据见 `06-communication-worker-ab-performance.md`;正式交付仍需发布并接入包含修复的新版 HAR。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/02-xml-taskpool-refactor.md b/harmony-ths-futures-freeze/doc/change-notes/02-xml-taskpool-refactor.md new file mode 100644 index 0000000..d215d83 --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/02-xml-taskpool-refactor.md @@ -0,0 +1,45 @@ +# 网络响应 XML 异步解析 + +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `harmony-ths-futures` | `work-20260818-clz-FUHM-1979-apm-appfreeze` | `e16813f86b8dbabcf6e6e4a18df72c80c69cc9db` | 认证 XML 解析迁移至 TaskPool(`ConcurrentXmlParser`) | + +## 背景与目标 + +Top 20 冻屏日志中,主线程采样命中 Upass 响应的 GBK 解码和 `XmlPullParser.parse()`。进一步扫描发现验证码响应、账户手机号查询也在通信回调中同步解码并解析 XML。本次统一将三条活动链路迁移到 TaskPool,避免响应体异常放大时阻塞主线程。 + +## 修改范围 + +- `biz_common/src/main/ets/utils/xml/ConcurrentXmlParser.ets` +- `biz_hxservice/src/main/ets/cookie/UpassSessionNetClient.ets` +- `biz_auth/src/main/ets/auth/verifycode/CheckCodeLoginClient.ets` +- `biz_auth/src/main/ets/auth/UserQueryClient.ets` +- `claude_tool/subthread-import-guard/subthread-import-policy.json` + +`biz_auth/src/main/ets/auth/UpassSessionNetClient.ets` 未发现导出或调用关系,属于当前不可达的遗留实现,本次不修改。 + +## 通用方案 + +新增 `parseXmlAttributes()` 并发方法,参数为原始字节、字符编码、目标属性列表和可选结束标签。TaskPool 内完成文本解码与 XML 扫描,统一关闭 DOCTYPE;没有指定结束标签时,获取全部目标属性后立即停止。主线程只处理返回值,不向子线程传递业务对象。 + +三处调用配置: + +- Upass:`gbk`,提取 `sessionid`。 +- 验证码:`gbk`,提取 `code`、`msg`,遇到 `wlh_thsreg_modify` 结束标签停止。 +- 用户查询:`utf-8`,提取 `ckmobile`。 + +并发入口只包含 `@ohos.util` 和 `@ohos.xml` 两条运行时依赖边,已精确登记到子线程导入策略。 + +## 功能影响 + +- Upass SessionId 获取、持久化及 Cookie 建立。 +- 手机号登录或注册时的验证码结果回调。 +- 登录完成或推送更新时的账户手机号回填与偏好存储。 + +请求协议、公开回调接口和存储键不变。解析失败会进入原有失败处理;用户查询异步期间若发生用户切换,会丢弃旧响应。验证码日志不再输出完整 XML,只记录状态码和消息。 + +## 验证 + +`node claude_tool/subthread-import-guard/check-subthread-imports.mjs` 已通过:通用入口包含 1 个本地闭包文件和 2 条运行时依赖边。按当前要求未执行完整编译。运行期仍需覆盖 Upass 登录态、验证码成功/失败、手机号回填、用户切换和损坏 XML 场景,并确认主线程采样栈不再出现 XML 解析热点。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/03-communication-taskpool-decompression.md b/harmony-ths-futures-freeze/doc/change-notes/03-communication-taskpool-decompression.md new file mode 100644 index 0000000..7e672c0 --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/03-communication-taskpool-decompression.md @@ -0,0 +1,59 @@ +# 通信解压迁移至 Worker + +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `ohos_mobile_lib_communication` | `work-freeze-lib-communication-1.1.1` | `031d4cf221b4da6d9aaf2db443ba293559a0d4a5` | `HXZip` 解压与曲线解析迁移到通信 HAR 内持久 Worker,并重构队列分发 | +| `harmony-ths-futures` | `work-20260818-clz-FUHM-1979-apm-appfreeze` | `ce0719178530b5ca64272c6c1e90b5bcdb8297fc` | 重建并接入 `libs/lib_communication20250903-worker-opt.har` | + +## 目的 + +冻屏采样栈显示主线程在 `HXZip.unzip()` 及解压后的行列转置循环中持续执行。压缩行情包越大,阻塞时间越长。 + +## 线上真实数据补充 + +2026-08-21 补充的 TOP10 耗时函数线上统计中,通信库仍有两条采样命中: + +| TOP | 函数 | 故障出现次数 | 调用次数 | 总耗时 | +| ---: | --- | ---: | ---: | ---: | +| 1 | `writeReceiveTime entry\|@kernel/lib_communication\|1.1.1-beta.1\|src/main/ets/components/communication/protocol/MiniDataHead.ts` | 1 | 2 | 600ms | +| 2 | `anonymous entry\|@kernel/lib_communication\|1.1.1-beta.1\|src/main/ets/request/QueueManagement.ts` | 1 | 2 | 600ms | + +这两条不是 `HXZip.unzip()` 本身,但说明同一通信回调链路在线上仍能出现在主线程耗时采样中。`writeReceiveTime()` 只是写包头的短函数,单点优化价值低;`QueueManagement` 当前仍通过 `Map` 全量遍历完成注册、映射和分发查找,后续可将 `instanceId -> clients` / companion 映射拆成索引,减少行情响应分发阶段的线性扫描。 + +## TOP20 线上数据补充 + +同日 TOP20 统计中,通信库出现同一条分层回调链: + +| TOP | 函数 | 故障出现次数 | 调用次数 | 总耗时 | +| ---: | --- | ---: | ---: | ---: | +| 7 | `anonymous entry\|@kernel/lib_communication\|1.1.1-beta.1\|.../handler/Response...` | 59 | 590 | 177000ms | +| 8 | `fireConnectionRead entry\|@kernel/lib_communication\|1.1.1-beta.1\|.../connection/Abstract...` | 58 | 580 | 174000ms | +| 9 | `connectionRead entry\|@kernel/lib_communication\|1.1.1-beta.1\|.../communication/handler/...` | 58 | 580 | 174000ms | +| 10 | `invokeChannelRead entry\|@kernel/lib_communication\|1.1.1-beta.1\|.../connection/Abstract...` | 58 | 580 | 174000ms | + +上述 4 项是同一次通信管线逐层转发的采样帧,不能将 699000ms 视为 4 个独立耗时问题。统计来自旧版 `1.1.1-beta.1`;当前工程已接入 Worker 解码和队列索引优化 HAR,需用新包重新采样确认是否仍上榜。 + +## 修改文件 + +- `lib_communication/.../protocol/ConcurrentDecodeClient.ts` +- `lib_communication/.../protocol/ConcurrentDecodeWorker.ts` +- `lib_communication/.../protocol/MobileDataProcessor.ts` +- `lib_communication/.../protocol/ReceiveDataProcess.ts` +- `lib_communication/.../handler/ResponseStructDecodeHandler.ts` +- `lib_communication/build-profile.json5` + +以上修改基于相邻的 `ohos_mobile_lib_communication` 源码仓库,验证时临时复制到当前工程,验证结束后已移除。 + +## 实现 + +通信库主体为 `.ts`,不能直接导入只允许在 `.ets` 中声明的 `@Concurrent` 任务。因此改为复用一个 HAR 内持久 Worker,通过请求 ID 匹配结果,在 Worker 中完成 `HXZip` 解压及行列转置。解码调用链改为异步;每个 `ResponseStructDecodeHandler` 使用 Promise 队列串行处理响应,避免并发完成导致消息乱序。 + +## 影响与风险 + +主线程只负责组装 `HXDataInputStream` 和继续分发。Worker 参数与结果均为可序列化的基础类型或 `Uint8Array`。单次消息仍受 16 MB 序列化限制,Worker 异常会拒绝等待中的请求,响应队列捕获错误后继续处理后续消息。 + +## 验证 + +基于 `1.1.1-beta.1` 的通信库 HAR 和默认 debug HAP 已构建、安装并完成行情周期切换验证。三轮 A/B 测试中,主线程 CPU 时间中位数下降 20.2%,8 ms 以上连续运行片段减少 53.3%;详细结果与限制见 `06-communication-worker-ab-performance.md`。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/04-curve-parsing-taskpool.md b/harmony-ths-futures-freeze/doc/change-notes/04-curve-parsing-taskpool.md new file mode 100644 index 0000000..422c8b5 --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/04-curve-parsing-taskpool.md @@ -0,0 +1,33 @@ +# 曲线解析迁移至 Worker +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `ohos_mobile_lib_communication` | `work-freeze-lib-communication-1.1.1` | `031d4cf221b4da6d9aaf2db443ba293559a0d4a5` | `HXZip` 解压与曲线解析迁移到通信 HAR 内持久 Worker | +| `harmony-ths-futures` | `work-20260818-clz-FUHM-1979-apm-appfreeze` | `ce0719178530b5ca64272c6c1e90b5bcdb8297fc` | 重建并接入 `libs/lib_communication20250903-worker-opt.har` | + +## 目的 + +冻屏日志显示曲线解析在主线程执行 `points × fields` 双重循环,并反复完成整数读取与 HXLong 浮点转换,是曲线点数较大时的 CPU 热点。 + +## TOP20 线上数据补充 + +TOP20 的 TOP7–TOP10 命中的是通信响应读取、连接转发和通道回调的分层函数,没有直接命中曲线解析函数。由于这些函数属于同一条响应管线,不能据此证明曲线解析仍是独立瓶颈;当前 Worker 版本的收益仍需使用新包按曲线大包场景重新采样确认。 + +## 修改文件 + +- `lib_communication/.../protocol/ConcurrentDecodeClient.ts` +- `lib_communication/.../protocol/ConcurrentDecodeWorker.ts` +- `lib_communication/.../protocol/MobileDataProcessor.ts` + +## 实现 + +扩展数据仍按原逻辑解析;剩余曲线字节、字段类型和点数提交给通信 HAR 内的持久 Worker。子线程保持原协议的小端读取、`MD_TYPE_MASK` 分支、HXLong 符号/指数/空值语义,返回按字段排列的数值列。宿主线程仅按字段 ID 重建 `dataTable` 和 `typeTable`。 + +## 影响与风险 + +公开的 `StuffCurveStruct` 数据结构不变,重复字段 ID 仍以后出现的列为准。若实际单包接近 Worker 的 16 MB 序列化上限,需要进一步改为分块或共享缓冲区。 + +## 验证 + +基于 `1.1.1-beta.1` 的通信库 HAR 和默认 debug HAP 已构建、安装并完成行情周期切换验证。当前结果证明通信解析阶段的主线程负载下降,但 `CurveView` 刷新耗时和超时帧没有稳定改善;详细结果与限制见 `06-communication-worker-ab-performance.md`。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/05-trade-sdk-native-lock-app-mitigation.md b/harmony-ths-futures-freeze/doc/change-notes/05-trade-sdk-native-lock-app-mitigation.md new file mode 100644 index 0000000..74c914e --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/05-trade-sdk-native-lock-app-mitigation.md @@ -0,0 +1,57 @@ +# 交易 SDK Native 锁竞争应用侧优化 +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `harmony-ths-futures` | `work-20260818-clz-FUHM-1979-apm-appfreeze` | `d04f7720c13b130bd9bdab69109d6a93c6edcfca` | 应用侧减少交易 SDK Native 锁竞争(`FuturesApiInterceptor`/`TradeApiManager`) | +| `futures_trade_sdk`(原生 lib-weituosdk) | `work-clz-FUHM-1979-native-lock-opt` | `1ba241ef8d9aae16dde1b0c4e406d6cd41cca216` | 链接复用计数由 `recursive_mutex` 改为 `std::atomic`,降低锁竞争 | + +Native 锁优化 SO(`futures-trade-sdk-so-1.2.0-lock-opt.har`)由上述 SDK 提交构建(lib-weituosdk 的 `futures_spi_api_manager.cpp`/`futures_quants_spi.cpp`/`.h`)。应用侧止血见上表首行;本工程 `oh-package.json5` 指向该 SO 的接入改动尚未提交。 + + + +## 问题 + +冻屏日志显示,主线程调用交易 SDK 时阻塞在 `getReuseSpiApiByMode`、`hasLoggingInRequest` 等 Native 锁上。高频入口包括交易请求发送前检查和自动重登检查。 + +## 线上真实数据补充 + +2026-08-21 补充的 TOP10 耗时函数线上统计中,交易 SDK 发送链路仍被采样命中: + +| TOP | 函数 | 故障出现次数 | 调用次数 | 总耗时 | +| ---: | --- | ---: | ---: | ---: | +| 7 | `sendMsg entry\|@b2c-f/futures_ohos_trade_sdk\|1.0.13\|src/main/ets/trade/api/rpc/RspSendMsgCenterImpl.ts` | 1 | 10 | 3000ms | +| 4 | `isNumber entry\|@b2c-f/futures_ohos_trade_sdk\|1.0.13\|src/main/ets/trade/util/StringUtil.ts` | 1 | 2 | 600ms | + +`sendMsg()` 是交易请求进入 SDK/Native 的关键同步入口,和本 notes 记录的 Native 锁竞争止血方向一致。`isNumber()` 为短函数,单独看不像根因,更可能是交易列表或请求参数处理链路中的采样帧;应结合完整调用栈判断是否被 `sendMsg()` 或列表刷新长任务包裹。 + +同日 TOP20 统计进一步命中交易 SDK 的两组调用链: + +| TOP | 函数/调用链 | 故障出现次数 | 调用次数 | 总耗时 | +| ---: | --- | ---: | ---: | ---: | +| 1–4 | `getReuseIpForLogin → getRequestIpForLogin → libweituo_sdk_ohos.so` | 75 | 750 | 900000ms(分层重复采样) | +| 5–6 | `FuturesApiInterceptor.interceptSendMsg*` | 67/64 | 670/640 | 201000/192000ms | +| 11–18 | `enqueue → awaitEnqueue → innerEnqueue → startSendRequest → proceedSendMsg` | 47–48 | 470–480 | 141000–144000ms/项(分层重复采样) | +| 19–20 | `TradeApiManager.tryToReLoginAccount` 及其匿名闭包 | 44 | 440 | 132000ms/项 | + +TOP1–4 和 TOP19–20 共同指向自动重登录入口的同步复用 IP 查询;TOP11–18 是同一请求链的多层采样,不能直接累加为独立损耗。该 TOP20 文件记录的是旧版 `1.0.11/1.1.1`,当前工程已接入 `1.0.13/1.2.0` 锁优化 SO;应用侧仍保留首次同步 `getReuseIpForLogin()` 风险,需新包线上复采确认。 + +## 优化方案 + +1. 删除 `FuturesApiInterceptor` 中无实际拦截效果的请求前检查。原逻辑会在每次交易请求前同步查询登录中、在线状态,但两个判断分支中的拦截代码均已注释。删除后不改变当前请求行为,同时避免业务请求重复进入 Native 锁域。 +2. `TradeApiManager` 按交易账号复用正在执行的重登 Promise。同一账号由认证、交易页或推送重复触发重登时,只执行一次 SDK 登录,其余调用复用结果并保留各自成功、失败回调。 +3. 在线检查和复用连接查询前先判断该账号是否正在重登,避免登录过程中再次同步查询 Native 状态。 + +## 影响范围 + +- 交易接口发送、自动重登、推送触发的在线检查和交易页切换重登。 +- 不修改交易 SDK、Native SO、登录参数、失败重试次数及成功/失败业务处理。 +- 不将 SDK 对象迁移到 Worker 或 TaskPool,不改变其线程归属。 + +## 预期收益与边界 + +优化可减少主线程进入交易 SDK 互斥锁的频率,并阻止同账号重复登录放大锁竞争。`tryToReLoginAccount` 首次判断仍需调用一次 `getReuseIpForLogin`,因此这属于应用侧止血;彻底消除锁等待仍需要 SDK 缩小 Native 锁范围并发布新的 SO。 + +## 验证建议 + +在持续发送交易请求时反复执行断网重连、前后台切换、推送唤起和交易账号切换,确认同账号并发登录数不超过 1,并检查冻屏主线程栈中是否仍出现 `mutex::lock → getReuseSpiApiByMode/hasLoggingInRequest`。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/06-communication-worker-ab-performance.md b/harmony-ths-futures-freeze/doc/change-notes/06-communication-worker-ab-performance.md new file mode 100644 index 0000000..7e1035e --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/06-communication-worker-ab-performance.md @@ -0,0 +1,39 @@ +# 通信 Worker A/B 性能验证 +## 验证对象(对应修复) + +该验证针对 `HXZip` 解压与曲线解析迁移至 Worker 的收益,属于笔记 `03`、`04` 的量化回放,完整修复提交见 `03`/`04`(`ohos_mobile_lib_communication` `031d4cf221b4da6d9aaf2db443ba293559a0d4a5`、`harmony-ths-futures` `ce0719178530b5ca64272c6c1e90b5bcdb8297fc`)。 + +## 验证目标 + +对比 `lib_communication` `1.1.1-beta.1` 在同步解压/曲线解析与 Worker 异步处理下的主线程负载。测试只评价通信解压和曲线解析改造,不包含 XML、交易 SDK 或图表绘制优化。 + +## 测试方法 + +- 时间:2026-08-18 +- 设备:Pura 90 手机模拟器,`127.0.0.1:5555` +- 页面:国内行情“乙二醇2609”详情页 +- 操作:依次点击 `1分 -> 5分 -> 15分 -> 日K -> 分时`,连续执行 3 组,共 15 次切换 +- 轮次:旧同步版和 Worker 新版各 3 轮;每轮重新启动应用进程并采集 20 秒 hitrace +- 分析:使用 DevEco `trace_streamer` 转换为 SQLite,统计目标进程 `lat.hmn.futures` 的主线程调度片段 + +## 测试结果 + +下表使用三轮中位数,括号内为三轮范围。 + +| 指标 | 旧同步版 | Worker 新版 | 变化 | +| --- | ---: | ---: | ---: | +| 主线程 CPU 时间 | 2959 ms(2923~3309) | 2361 ms(2330~2512) | -20.2% | +| 进程总 CPU 时间 | 3765 ms(3731~4201) | 3075 ms(3069~3250) | -18.3% | +| 主线程 `>= 8 ms` 连续运行片段 | 30 次(23~36) | 14 次(14~17) | -53.3% | +| 主线程调度片段 P99 | 5.747 ms(5.304~5.865) | 4.644 ms(4.452~4.692) | -19.2% | +| 主线程最大连续运行片段 | 23.698 ms(13.252~31.006) | 19.360 ms(12.924~24.345) | -18.3% | + +## 结论与限制 + +Worker 改造使主线程计算量下降约 20%,8 ms 以上连续运行片段减少一半以上,主要收益是降低大包解压和曲线解析占用主线程导致的冻屏风险。 + +`CurveView` 刷新总耗时中位数由 221.5 ms 变为 236.0 ms,没有稳定改善。超过 16.67 ms 的帧中位数由 9 次变为 13 次,帧尾部数据也未呈现一致收益。因此本次结果不能解释为整体帧率提升,优化范围仅限通信解析阶段。 + +自动框架识别未生成应用绘帧记录,因此未套用 ArkUI 丢帧分类结果。后续建议在真机上使用固定响应包、增加解压和曲线解析起止 trace 点,并扩大轮次,以进一步隔离网络波动、模拟器调度和图表渲染噪声。 + +原始 trace 和 SQLite 数据未纳入仓库,测试时临时保存在 `/tmp/sdk-worker-ab.psA7Sp/`。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/07-trade-sdk-match-push-optimization.md b/harmony-ths-futures-freeze/doc/change-notes/07-trade-sdk-match-push-optimization.md new file mode 100644 index 0000000..4285e8c --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/07-trade-sdk-match-push-optimization.md @@ -0,0 +1,63 @@ +# 交易 SDK 成交推送去重与排序优化 +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `harmony-ths-futures` | `work-20260818-clz-FUHM-1979-apm-appfreeze` | `ce0719178530b5ca64272c6c1e90b5bcdb8297fc` | 接入本地 `libs/FuTrade-1.0.13-match-push-opt.har`(含成交推送去重与排序) | +| `harmony_futures_trade_sdk` | `feature/FUHM-1979-match-push-optimization` | `9e56923cfaadb14fff0fa928cabe4e5aef615490` | 优化成交推送处理(去重与稳定排序) | +| `harmony_futures_trade_sdk` | `feature/FUHM-1979-match-push-optimization` | `31256eff45b51a96551a2ba8f2b31bb02e4258af` | 优化推送缓存查重 | + +SDK 侧成交推送去重/排序来源于 `harmony_futures_trade_sdk` 仓库上述源码提交,本仓库以本地 HAR 携带,接入提交见上表首行。 + +## 问题 + +成交推送进入 `MatchOrderSummary.updateMatchOrders()` 时,旧逻辑需要遍历全部成交记录判断重复成交,并在每次新增后对完整数组执行一次排序。成交推送频繁或历史成交较多时,会重复消耗 CPU,并增加成交列表更新的耗时。 + +## 线上真实数据补充 + +2026-08-21 补充的 TOP10 耗时函数线上统计没有直接命中 `MatchOrderSummary.updateMatchOrders()`,但命中了同属交易 SDK 的 `RspSendMsgCenterImpl.sendMsg()`:1 次故障、10 次调用、总耗时 3000ms。该数据不能证明成交推送优化已覆盖此线上样本,只能说明交易 SDK 1.0.13 仍存在主线程高频 SDK 调用采样;成交推送优化需要继续依赖新 HAR 接入后的成交推送专项回归和新日志闭环。 + +TOP20 统计同样没有直接命中 `MatchOrderSummary.updateMatchOrders()`。TOP11–TOP18 命中的是交易请求发送链的 `enqueue/awaitEnqueue/proceedSendMsg` 分层函数,属于请求排队和发送链路,不足以证明成交推送去重与排序优化已经生效或失效;仍需通过成交推送专项采样验证。 + +## 优化方案 + +1. 在 `MatchOrderSummary` 中增加 `Set`,缓存已有成交的 `matchIdentify().identify`。重复推送直接通过 `Set.has()` 过滤,避免遍历成交数组。 +2. 首次新增推送仍执行完整排序,确保查询结果初始顺序不确定时可以恢复到成交排序规则。 +3. 首次排序完成后,后续推送使用二分查找确定插入位置,再插入 `matchOrderRsps`,避免每次对全部成交记录重新排序。 +4. 二分插入在比较结果相同时继续向后查找,使相同成交时间和开平方向的记录保持稳定顺序。 +5. 增加单元测试,覆盖重复成交、稳定排序插入和不同账号推送过滤。 + +## 影响范围 + +- 交易 SDK 的成交汇总、成交推送和成交列表排序。 +- 当前项目通过本地 `libs/FuTrade-1.0.13-match-push-opt.har` 使用该版本,依赖配置位于根目录 `oh-package.json5`。 +- 成交量、平仓盈亏、盯市平仓盈亏的汇总规则不变。 +- 账号过滤规则和成交唯一标识规则不变;`Set` 仅用于查重,不参与成交列表排序。 + +## 预期收益与边界 + +重复推送判断由数组遍历变为近似 O(1),后续新增成交由全量排序变为二分查找加数组插入。优化主要针对高频成交推送和较大历史成交列表,业务展示顺序不应发生变化。 + +`matchOrderRsps` 仍是公开可变数组。如果外部代码直接修改、排序或替换数组内容,可能导致成交数组、去重 `Set` 和有序状态不一致;业务代码应通过 SDK 的成交汇总接口读取,避免直接修改该数组。初始查询数据中的重复项也不会由本次改动主动清理。 + +## 验证建议 + +### 单元测试 + +在交易 SDK 的 `FuTrade` 模块运行 `MatchOrderSummary` 测试,确认以下场景: + +- 相同成交推送只保留一条,汇总值不重复增加; +- 首次推送后成交记录按既有时间和开平方向规则排序; +- 后续推送插入到正确位置,相同排序值保持稳定顺序; +- 不同账号的成交推送被过滤; +- 空初始列表、白盘/夜盘边界和不同开平方向均可正常插入。 + +### 当前项目验证 + +确认 `oh-package.json5` 指向本地 HAR 后,执行: + +```bash +./claude_tool/claude_compile.sh +``` + +并在成交页面回归查询成交、接收新成交推送、重复推送和账号切换,检查成交条数、成交量、盈亏汇总及列表顺序均保持正确。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/08-trade-page-online-status-cache.md b/harmony-ths-futures-freeze/doc/change-notes/08-trade-page-online-status-cache.md new file mode 100644 index 0000000..93020fc --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/08-trade-page-online-status-cache.md @@ -0,0 +1,55 @@ +# 交易页前台在线状态查询改为读缓存 +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `harmony-ths-futures` | `work-20260818-clz-FUHM-1979-apm-appfreeze` | `8c3170778f50bf767c9146ea177e82080d629da2` | 交易页前台在线状态改为读缓存 `currentAccount.isOnline` | + +## 问题 + +冻屏日志聚类 20020090(3 次):应用切回前台触发 `TradePage.isAbilityForeGroundChanged()` 时,主线程同步调用 `ApiCommon.isOnlineAccount()`,经 JSI 桥穿透到 `libweituo_sdk_ohos.so` 的 `SpiApiManager::getLoginSuccSpiApi` 并等待 `recursive_mutex`。当 SDK 网络线程(asio 断线/连接回调链路)持有该锁时,主线程卡在 `__timedwait_cp` 等锁 3.1/6.2 秒,3S/6S 抓栈逐帧一致,触发冻屏上报。 + +交易 SDK 1.0.13 的 `TradeAccount` 已维护应用侧在线状态:登录成功(`setLoginSuccess`)、断网(`checkAccountOnline`)、重登成功/失败(`TradeApiManager`)及退出登录(`resetUnLogined`)时经 `updateOnlineStatus()` 更新 `isOnline` 字段并广播 `ACCOUNT_ONLINE_STATUS_CHANGED` 事件。交易页已监听该事件,前台切换所需的"是否在线"用缓存即可回答,无需同步穿透 Native 查询。 + +## 线上真实数据补充 + +2026-08-21 补充的 TOP10 耗时函数线上统计中,交易页前台链路仍被采样命中: + +| TOP | 函数 | 故障出现次数 | 调用次数 | 总耗时 | +| ---: | --- | ---: | ---: | ---: | +| 10 | `onForeground entry\|biz_trade\|1.0.0\|src/main/ets/views/tab/TradeGuaDanListView.ts` | 1 | 10 | 3000ms | + +该样本不是 `TradePage.isAbilityForeGroundChanged()` 的同一函数,但同属交易页前台恢复路径。当前 `TradeGuaDanListView.onForeground()` 会立即触发 `GuaDanViewModel.sendRequest()`,其中包含条件单标签查询和挂单查询;如果前后台切换、刷新和交易推送相互叠加,仍可能造成主线程连续调度和 SDK 请求入口放大。后续可对前台刷新增加去重、节流或复用正在进行的查询。 + +## TOP20 线上数据补充 + +TOP20 未直接命中 `TradeGuaDanListView.onForeground()`,但命中了交易页自动重登录入口: + +| TOP | 函数 | 故障出现次数 | 调用次数 | 总耗时 | +| ---: | --- | ---: | ---: | ---: | +| 19 | `tryToReLoginAccount entry\|biz_trade\|1.0.0\|.../TradeApiManager.ts` | 44 | 440 | 132000ms | +| 20 | `anonymous entry\|biz_trade\|1.0.0\|.../TradeApiManager.ts` | 44 | 440 | 132000ms | + +该结果说明前台恢复、推送和交易页刷新仍可能汇聚到自动重登录链路,但两个函数属于同一入口的分层采样,不能重复计为两项独立耗时。`TradePage` 的在线状态查询已改为读缓存;`TradeApiManager` 的同步复用 IP 查询仍需结合新版 SDK 线上采样继续确认。 + +## 优化方案 + +1. `TradePage.isAbilityForeGroundChanged()` 中的 `ApiCommon.isOnlineAccount(currentAccount)` 替换为读缓存 `currentAccount.isOnline`。 +2. 删除 `TradePage` 中不再使用的 `ApiCommon` import(全文件仅此一处使用)。 + +## 影响范围 + +- 交易页前台切换时在线状态判断的数据来源,由 Native 同步查询改为应用侧缓存。 +- 条件单查询时机不变:在线时立即 `checkConditionTriggeredForCurrentAccount()`,离线时置 `needQueryConditionTriggeredAfterTradeLogin`,登录成功后由 `onTradePushLoginResult`、切换账户后由 `onTradeLogin` 补查。 +- 不修改交易 SDK、Native SO、`isOnline` 状态的维护链路及其事件广播。 + +## 预期收益与边界 + +消除该主线程等锁入口,前台切换不再同步进入交易 SDK 锁域。`isOnline` 为应用侧维护状态,SDK 断线回调尚未到达的极短窗口内可能读到旧值,最多导致多查一次条件单(请求失败无害);彻底消除等锁风险仍需要 SDK 缩小 Native 锁范围并发布新的 SO。 + +## 验证建议 + +1. 执行 `./claude_tool/claude_compile.sh` 编译通过。 +2. 真机登录后反复前后台切换,确认条件单查询行为与改动前一致,hilog 中不再出现主线程 `isLoggedInAccount` 同步调用。 +3. 断网后回前台、恢复网络、重登成功,确认 `onTradePushLoginResult` 兜底补查条件单的路径正常。 +4. 检查冻屏主线程栈中是否仍出现 `mutex::lock → SpiApiManager::getLoginSuccSpiApi`。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/09-drawline-dedup-optimization.md b/harmony-ths-futures-freeze/doc/change-notes/09-drawline-dedup-optimization.md new file mode 100644 index 0000000..3c1ff63 --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/09-drawline-dedup-optimization.md @@ -0,0 +1,35 @@ +# 画线绘制候选集去重 O(n²) → O(n) +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `hmdrawlinebasicsdk` | `work-clz-fuhm-1979-drawline-dedup` | `c3f8e6bc2ca44af9afa9bf0163d88d2508d55a0a` | 画线绘制候选集去重 O(n²)→O(n) | +| `harmony-ths-futures` | `work-20260818-clz-FUHM-1979-apm-appfreeze` | `ce0719178530b5ca64272c6c1e90b5bcdb8297fc` | 接入 `libs/drawlinebasic-1.2.1-dedup-opt.har` 及主工程 overrides | + +## 问题 + +冻屏聚类 20020217(5 次)、20020340(3 次):主线程栈停在 `DrawingApiImpl.appendUniqueLines → findIndex → isSameLine`。`drawLines()` 每帧渲染都会调用 `getLinesToDraw()` 重建绘制候选集,其中 `appendUniqueLines` 用 `source.forEach` 嵌套 `target.findIndex` 判重,复杂度 O(n·m)。画线数量较多或跨周期线合并时,主线程持续繁忙触发 3S/6S 冻屏上报。 + +判重语义(原 `isSameLine`):同对象引用、本地 ID 或远端 ID 任一非空且相等即判为同一条线。注意本地 ID 不同但远端 ID 相同的两条线也判同一条,因此不能用单一 key 去重。 + +## 优化方案 + +1. `appendUniqueLines` 改为三个 Set(对象引用 `refSet`、本地 ID `localIdSet`、远端 ID `lineIdSet`)一次遍历完成判重,复杂度降为 O(n);判重语义与原 `isSameLine` 逐条等价,目标列表插入顺序不变;原私有方法 `isSameLine` 随之删除。 +2. 补丁基于 `hmdrawlinebasicsdk` 仓库 `feature-zyh-20260708-FUHM-1578-cross-period` 分支(画线 SDK 1.2.1 的发布源,与 ohpm 发布包源码树逐字节一致;master 仍为 1.1.2,无此代码),仓库内 commit `c3f8e6b`,本地构建产物为 `libs/drawlinebasic-1.2.1-dedup-opt.har`。 +3. 主工程三处接入:根 `oh-package.json5` 依赖与 `overrides`、`biz_quote/oh-package.json5` 依赖均改为 `file:` 引用本地 HAR。`overrides` 同时将 `drawlinepane@1.1.0` 传递依赖的 `drawlinebasic@1.2.0` 强制指向同一本地 HAR,保证最终产物只有一份 drawlinebasic。 + +## 影响范围 + +- 仅画线绘制候选集的去重实现;画线数据模型、持久化、云端合并、绘制样式与命中逻辑不变。 +- 每帧绘制候选集重建仍会分配三个 Set,列表本身不缓存;进一步可考虑按 cache 版本缓存合并结果(本次未做)。 + +## 预期收益与边界 + +候选集去重由 O(n·m) 降为 O(n),画线数量越大收益越明显,直接消除该冻屏聚类的热点函数。判重语义与原实现逐条等价(含"本地 ID 不同但远端 ID 相同判同一条"的边界情形),重复描边、线条层级行为不变。画线 SDK 侧彻底根治仍建议上游将 `getLinesToDraw` 的合并结果按缓存版本缓存,避免每帧重建。 + +## 验证建议 + +1. 执行 `./claude_tool/claude_compile.sh` 编译通过。 +2. 真机在画线较多(数十条以上)的 K 线页面滑动、切换周期,确认绘制行为与改动前一致,冻屏主线程栈不再出现 `appendUniqueLines → findIndex`。 +3. 回归跨周期画线:新建、云端合并、编辑态切换、删除场景下无重复描边,线条层级与命中结果不变。 +4. 检查最终产物中 `@b2c-f/drawlinebasic` 仅存在一份(overrides 生效)。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/10-top10-online-sampling-followups.md b/harmony-ths-futures-freeze/doc/change-notes/10-top10-online-sampling-followups.md new file mode 100644 index 0000000..86a4071 --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/10-top10-online-sampling-followups.md @@ -0,0 +1,39 @@ +# TOP10 线上耗时函数后续候选项 +## 修复说明 + +本笔记为线上 TOP10 耗时函数的分析、候选项与建议修复顺序,未包含生产修复本身;候选的实现见 **`11-top10-followup-fixes.md`**(对应本仓库 commit `0d10cf71a01c8c5d875ca4c950d6d2fd71d3fd42`)。 + +## 数据来源 + +2026-08-21 从 `~/Downloads/TOP10耗时函数列表.md` 补充线上真实 TOP10 耗时函数统计。该列表是函数采样聚合,不等价于完整冻屏根因;短函数命中通常表示它处在更长的调用链上,需要结合完整 faultlog/采样栈判断。 + +| TOP | 函数 | 故障出现次数 | 调用次数 | 总耗时 | +| ---: | --- | ---: | ---: | ---: | +| 1 | `writeReceiveTime entry\|@kernel/lib_communication\|1.1.1-beta.1\|src/main/ets/components/communication/protocol/MiniDataHead.ts` | 1 | 2 | 600ms | +| 2 | `anonymous entry\|@kernel/lib_communication\|1.1.1-beta.1\|src/main/ets/request/QueueManagement.ts` | 1 | 2 | 600ms | +| 3 | `anonymous entry\|@b2b/hq_table\|1.0.0-rc.9\|src/main/ets/model/data/TableDataSource.ts` | 1 | 10 | 3000ms | +| 4 | `isNumber entry\|@b2c-f/futures_ohos_trade_sdk\|1.0.13\|src/main/ets/trade/util/StringUtil.ts` | 1 | 2 | 600ms | +| 5 | `transFormServerColor entry\|@b2c/lib_baseui\|1.0.0\|src/main/ets/utils/ColorUtils.ts` | 1 | 2 | 600ms | +| 6 | `isArbitrageEnabled entry\|biz_trade\|1.0.0\|src/main/ets/trade/manager/TradeCustomTabManager.ts` | 1 | 4 | 1200ms | +| 7 | `sendMsg entry\|@b2c-f/futures_ohos_trade_sdk\|1.0.13\|src/main/ets/trade/api/rpc/RspSendMsgCenterImpl.ts` | 1 | 10 | 3000ms | +| 8 | `showOrderNameCell entry\|biz_trade\|1.0.0\|src/main/ets/views/tab/ContractNameCellView.ts` | 1 | 4 | 1200ms | +| 9 | `anonymous entry\|biz_futures_chart\|1.0.0\|src/main/ets/view/View.ts` | 1 | 6 | 1800ms | +| 10 | `onForeground entry\|biz_trade\|1.0.0\|src/main/ets/views/tab/TradeGuaDanListView.ts` | 1 | 10 | 3000ms | + +## 当前判断 + +- `hq_table` 表格数据合并是优先级最高的新增候选:`TableDataSource.notifyDataSetChanged()` 仍在主线程逐行 merge、创建默认行并触发可见行更新,TOP3 达到 10 次调用、3000ms。 +- `lib_communication` 队列管理仍有结构性优化空间:`QueueManagement` 通过遍历 `Map>` 完成注册、删除、映射和分发查找,可改为维护 `instanceId` 索引减少线性扫描。 +- `biz_trade` 套利开关可做缓存:`isArbitrageEnabled()` 会进入 `TradeFunctionSwitch.isSupportArbitrageTrade()` 和 `GrayTestManager.isFeatureEnable()`,灰度命中时包含日志和 `JSON.stringify(grayConfig)`,不适合在列表构建中高频调用。 +- `ContractNameCellView.showOrderNameCell()`、`ColorUtils.transFormServerColor()`、`StringUtil.isNumber()` 和 `BaseView` 绘制/布局闭包本身都较短,当前不应只针对短函数微调,应先定位其上层列表刷新、图表刷新或交易请求链路。 + +## 建议修复顺序 + +1. 在 `hq_table` 源码侧优化 `TableDataSource.notifyDataSetChanged()`:减少全量行合并、只更新可见区、批量合并 `notifyChange()`,必要时将可 Sendable 的数据预处理继续前移到子线程。 +2. 在通信 SDK 源码侧重构 `QueueManagement` 索引:新增 `instanceId -> clients` 和 companion 映射,保留原公开接口,避免响应分发时扫描全部客户端。 +3. 在 `TradeCustomTabManager` 或 `TradeFunctionSwitch` 增加账号维度套利灰度缓存,并在灰度配置变更、账号切换、设置变更时失效。 +4. 对 `TradeGuaDanListView.onForeground()` 的挂单刷新做去重/节流,避免前台恢复、手动刷新和推送触发重复查询。 + +## 验证口径 + +每个候选项修复后,需要用新包重新采集线上或真机冻屏采样,确认对应函数不再进入 TOP 耗时列表。仅凭本次 TOP10 采样不能判定短函数为唯一根因。 diff --git a/harmony-ths-futures-freeze/doc/change-notes/11-top10-followup-fixes.md b/harmony-ths-futures-freeze/doc/change-notes/11-top10-followup-fixes.md new file mode 100644 index 0000000..46eb028 --- /dev/null +++ b/harmony-ths-futures-freeze/doc/change-notes/11-top10-followup-fixes.md @@ -0,0 +1,90 @@ +# TOP10 线上耗时函数后续修复 +## 修复提交 + +| 仓库 | 分支 | Commit ID | 说明 | +| --- | --- | --- | --- | +| `harmony-ths-futures` | `work-20260818-clz-FUHM-1979-apm-appfreeze` | `0d10cf71a01c8c5d875ca4c950d6d2fd71d3fd42` | 挂单请求合并、套利开关缓存、通信队列索引、行情表格延迟合并 | +| `ohos_mobile_lib_communication` | `work-freeze-lib-communication-1.1.1` | `031d4cf221b4da6d9aaf2db443ba293559a0d4a5` | `QueueManagement` 队列索引(instanceId→client / companion) | + +## 问题来源 + +基于 2026-08-21 `TOP10耗时函数列表.md` 的线上采样,继续处理仍有明确优化空间的主线程热点: + +- `@b2b/hq_table` `TableDataSource`:1 次故障、10 次调用、总耗时 3000ms。 +- `@kernel/lib_communication` `QueueManagement`:1 次故障、2 次调用、总耗时 600ms。 +- `biz_trade` `TradeCustomTabManager.isArbitrageEnabled()`:1 次故障、4 次调用、总耗时 1200ms。 +- `biz_trade` `TradeGuaDanListView.onForeground()`:1 次故障、10 次调用、总耗时 3000ms。 + +## TOP20 线上数据补充 + +同日 `/Users/clz/Downloads/TOP20耗时函数列表.md` 汇总数据如下:故障出现次数 `1134`,调用次数 `11340`,记录总耗时 `3402000ms`。所有记录的单个故障最大耗时均为 `3000ms`。这些数值是采样记录的分层汇总,不代表一次冻屏实际耗时,不能直接相加计算收益。 + +TOP20 主要归并为以下调用链: + +| 调用链 | TOP | 故障/调用 | 总耗时 | 当前判断 | +| --- | ---: | ---: | ---: | --- | +| 重登录复用 IP 与 Native 锁 | 1–4、19–20 | 75/750;44/440 | 900000ms;132000ms/项 | 锁优化 SO 已接入,应用侧仍有同步查询边界,待新包复采 | +| 交易请求拦截与发送 | 5–6、11–18 | 47–67/470–670 | 141000–201000ms/项 | 已移除拦截器同步状态检查,通用请求仍需观察并发放大 | +| 通信响应回调转发 | 7–10 | 58–59/580–590 | 174000–177000ms/项 | 属同一管线分层采样,Worker/索引优化已接入,待新包复采 | + +TOP20 显示的交易 SDK 与通信库版本分别为旧版 `1.0.11/1.1.1` 和 `1.1.1-beta.1`;当前工程依赖已更新为交易 SDK `1.0.13/1.2.0` 及 Worker 优化通信 HAR。因此本表用于记录线上问题来源和修复映射,不能作为当前版本仍全部复现的证据。 + +## 修复内容 + +1. `GuaDanViewModel.sendRequest()` 增加进行中请求合并:挂单查询未返回时,后续前台/刷新触发只记录一次尾随刷新,避免短时间重复进入 SDK 查询链路。 +2. `TradeCustomTabManager.isArbitrageGrayOpened()` 增加当前账号维度缓存;`TradeSettingEvents.SETTING_UPDATE` 触发时失效,避免列表构建高频进入 `GrayTestManager.isFeatureEnable()` 的日志和配置序列化路径。 +3. 通信 SDK `QueueManagement` 增加 `instanceId -> client` 和 companion 映射索引,`getNetworkClient()` 从全量扫描改为按索引取主请求和 companion 请求。 +4. `hq_table_20260616.har` 内 `TableDataSource` 对非可见区 `SendableRow` 做延迟合并:当前刷新只合并可见行,非可见行缓存到 `pendingSendableRows`,滚动取数时再落到 `RowData`。 +5. 重新构建通信 HAR,并替换当前工程 `libs/lib_communication20250903-worker-opt.har`;`hq_table` 无相邻源码仓库,本次直接以最小差异更新现有 HAR。 + +## 影响范围 + +- 挂单列表前台恢复、手动刷新和交易推送叠加时的请求频率。 +- 独立交易页期权/套利 Tab 显示判断。 +- 通信响应分发中的请求匹配。 +- 行情表格 Sendable 数据进入主线程后的行合并时机。 + +业务协议、接口签名、表格展示数据结构和交易请求返回处理不变。挂单请求合并保留尾随刷新,避免丢掉进行中期间发生的最后一次刷新诉求。 + +## 验证与压测 + +- `node claude_tool/subthread-import-guard/check-subthread-imports.mjs`:PASS。 +- 通信 SDK:`devecocli build --modules lib_communication@default`:BUILD SUCCESSFUL。 +- 主工程:`./claude_tool/claude_compile.sh`:BUILD_SUCCESS。 +- 模拟器:Pura 90,`127.0.0.1:5555`,HarmonyOS 6.1.1(24)。 + - `devecocli run --module entry --device 127.0.0.1:5555 --product default --build-mode debug --skip-build`:安装并启动成功。 + - 应用进程 `com.hexin.plat.hmn.futures` 存活,前台任务 `EntryAbility` 存在。 + - 前后台压力:Home/重新拉起 20 轮完成,无 crash、无 `APPFREEZE` 日志。 + - UI 基础场景:行情 Tab 切换、行情页上下滑动 8 轮、交易 Tab 切换、交易入口前后台 5 轮完成,无 crash、无 `APPFREEZE` 日志。 + - 备注:未登录交易账号,挂单真实请求返回链路未覆盖;本轮覆盖交易入口页加载和前台恢复基础路径。 +- 临时 stdin 压测脚本未落盘,覆盖 100000 个通信实例索引查询、100000 行表格 Sendable 延迟合并、1000 次挂单刷新触发合并: + - `queueIndexedMs`: 4ms + - `tableDeferredMs`: 4ms + - `requestRealSendAfter1000Triggers`: 2 + +### 新模拟器真实账户验证 + +- 设备:Mate X7,`127.0.0.1:5557`,HarmonyOS 6.1.1(24);通过 `devecocli ui` 完成控件树检查和交互。 +- 真实账户登录成功,交易首页权益、可用资金和行情盘口加载完成。 +- 进入当日委托/挂单页面,连续 10 轮切换挂单、委托、成交并触发刷新;页面保持响应,无 `APPFREEZE`,无应用 crash。 +- 该轮未执行下单、撤单或改价,避免产生真实资金业务副作用。 +- 设备网络不可用时出现 DNS/监控上传错误;同时观察到已有 SQLite 空表名查询错误,均未导致应用退出或冻屏,不纳入本次 TOP 修复结论。 +- 最后一次误触打开交易时间提醒 WebView,页面仍正常展示;该路径不属于本次交易 SDK 压测范围。 + +### Wukong 滑动压力验证 + +- 设备:Mate X7,`127.0.0.1:5557`;仅允许 `com.hexin.plat.hmn.futures`,固定种子 `20260821`。 +- Wukong 随机测试报告:`task status=success`、`task time=149s`、`task count=100`,事件为 100 次滑动;点击、键盘、旋转和应用切换比例均为 0。 +- Wukong 异常统计为空;设备 `APPFREEZE` 查询为空,应用 crash 查询为空。 +- 报告目录:`/data/local/tmp/wukong/report/20260821_152309/`。该轮未执行交易点击,真实账户无资金业务副作用。 + +### 按修改点专项复验 + +- `hq_table`:国内行情大表执行 Wukong 50 次纯滑动,报告 `task status=success`、`task count=50`、滑动事件 50 次、异常为空;页面仍可正常进入合约详情。 +- Worker 解码:合约详情连续 8 轮切换分时、日 K、1/5/15 分钟,图表和行情数据正常,无 `APPFREEZE` 或 crash。 +- `GuaDanViewModel` 与 `QueueManagement`:真实账户交易页在设置更新后重新进入当日委托,连续 5 轮切换挂单/委托并刷新;页面正常,无 `APPFREEZE` 或 crash。 +- `TradeCustomTabManager`:当前真实账户和灰度环境未展示期权/套利独立下单 Tab,仅触发交易设置更新事件并返回交易页验证回退/重建路径;期权/套利 Tab 的直接专项验证待灰度开放后补测。 + +## 后续验证 + +需要用新包重新采集线上或真机冻屏采样,确认 `TableDataSource`、`QueueManagement`、`isArbitrageEnabled`、`TradeGuaDanListView.onForeground` 不再进入 TOP 耗时函数。短函数如 `ColorUtils.transFormServerColor()`、`StringUtil.isNumber()` 和 `BaseView` 绘制闭包仍应结合完整栈判断,不单独做微调。 diff --git a/需求开发文档.md b/需求开发文档.md new file mode 100644 index 0000000..cd0cd75 --- /dev/null +++ b/需求开发文档.md @@ -0,0 +1,80 @@ +# 期货 HarmonyOS 客户端稳定性治理需求开发文档 + +## 1. 文档目的 + +本文将现有冻结(App Freeze)与内存溢出(OOM)整改记录整理为可研发落地、可测试验收的需求。目标是在不改变行情、交易、画线业务协议及用户可见规则的前提下,降低主线程阻塞、请求重复与对象滞留风险。 + +本文记录的是已验证改造的需求基线,不代表所有项均已随正式版本发布;版本接入与线上复采必须以各上游仓库和发布包为准。 + +## 2. 范围与代码归属 + +| 域 | 主要归属 | 本次目标 | +| ------ | ------------------------------------------------------------------- | --------------------- | +| 应用业务 | `harmony-ths-futures` | 交易、行情与 WebView 生命周期治理 | +| 通信 | `ohos_mobile_lib_communication` | 解码、曲线解析、请求队列与缓冲释放 | +| 交易 SDK | `harmony_futures_trade_sdk` `futures_trade_sdk`(原生 `lib-weituosdk`) | Native 锁规避、成交推送、市价缓存 | +| 画线 SDK | `hmdrawlinebasicsdk` | 绘制候选集去重 | + +本仓库仅保存变更说明,不能直接构建或产出 HAP/HAR。 + +## 3. 功能需求 + +### FR-01 通信与解析异步化 + +1. XML 响应的字节解码和属性解析必须在 TaskPool 中完成;主线程仅消费解析结果。解析入口只允许携带可序列化数据,禁用 DOCTYPE,并保持登录会话、验证码、手机号查询的原有回调与失败语义。 +2. `HXZip` 解压及曲线的 `points × fields` 解析必须在通信 HAR 的持久 Worker 中执行。响应须按请求顺序串行回调;Worker 异常须拒绝当前等待任务,但不得阻断后续消息。 +3. `QueueManagement` 必须维护 `instanceId → client/companion` 索引,避免响应分发遍历全部客户端;跨 `frameId` 切换时,必须释放旧请求缓冲、主/伴随 client 映射。 + +### FR-02 行情与绘制主线程削峰 + +1. 表格实时通知在同一事件循环内仅保留最新一份 `TableData`,已有定时器时不得继续创建闭包;切页或释放时必须取消定时器并清空待通知数据。 +2. 画线候选集去重必须保持“同对象、本地 ID 相同或远端 ID 相同即重复”的既有语义与插入顺序,复杂度由 O(n²) 降为 O(n)。 + +### FR-03 交易链路防重复与锁规避 + +1. 前台交易页的在线判断必须读取 `currentAccount.isOnline` 缓存,不得同步调用 Native 在线查询;断网、重登、切账号后的条件单补查行为保持不变。 +2. 同一账号重登必须复用进行中的 Promise;无实际拦截效果的交易发送前 SDK 状态检查必须移除。 +3. 挂单查询进行中再次触发时,只保留一次尾随刷新;列表构建中的套利灰度判断按账号缓存,并在设置更新、账号或灰度配置变化时失效。 +4. 成交推送必须按唯一标识去重。首次加载可全量排序,后续记录必须以稳定二分插入,且不改变成交量、盈亏、账号过滤与展示顺序。 +5. 市价缓存须按“合约 + 价格类型”更新同一节点;消费后必须用可修改数组操作删除节点,禁止重复累积。 + +### FR-04 页面与请求资源释放 + +1. `TableRequestClient` 切页、取消订阅或释放时,必须幂等地移除旧队列映射、实时订阅、请求缓冲与待通知数据。 +2. WebView 总览页销毁时,必须停止三个行情请求并清空 client 与 JS bridge 的双向引用。 + +## 4. 影响面 + +| 需求域 | 用户/业务入口 | 受影响组件 | 保持不变 | 重点回归 | +| --- | --- | --- | --- | --- | +| 通信与解析 | 登录、验证码、行情包与 K 线加载 | XML Parser、通信 Worker、`QueueManagement` | 协议、回调结果、消息顺序 | 损坏 XML、大包、周期连续切换、跨 Frame 请求 | +| 行情与画线 | 行情列表刷新、合约详情、K 线绘制 | `TableRequestClient`、画线 SDK | 行情数据、线条样式、命中与层级 | 高频推送、分组切换、滚动、跨周期线合并 | +| 交易 | 前后台、账号切换、挂单/委托/成交页 | `TradePage`、`TradeApiManager`、交易 SDK | 下单/撤单、账户过滤、成交汇总与排序规则 | 断网重登、重复刷新、重复推送、真实账户切换 | +| 页面生命周期 | WebView 行情总览页退出 | `OverViewEvent`、请求 client、JS bridge | 总览请求参数、前后台恢复行为 | 反复进入/退出总览页、页面销毁后回调 | + +## 5. 非功能要求与边界 + +- 所有异步任务仅传递基础类型、`Uint8Array` 或其他可序列化数据;单个 Worker 消息受 16 MB 限制,接近上限时需分块设计。 +- 不修改网络协议、公开接口、交易下单/撤单规则、行情数据模型或画线样式。 +- 不将 SDK 对象迁移至 Worker;Native 锁的根治依赖交易 SDK 缩小锁粒度,应用侧仅减少进入锁域的次数。 +- 临时 HAR 路径、压测代码、计数日志和 API SDK 替换不得进入正式提交。 + +## 6. 验收标准 + +1. 在上游项目执行 `devecocli build --modules @default` 或对应 HAP 构建成功,且 `devecocli check lint` 无新增问题。 +2. 覆盖登录/断网重登、前后台、账号切换、挂单/委托/成交刷新、行情分组切换、K 线周期切换及 WebView 总览销毁;无 JS Crash、CppCrash、OOM 或 `APPFREEZE`。 +3. 成交去重、稳定排序、缓存删除和请求解绑须具备单元或白盒压力测试;测试后不存在旧订阅、旧缓冲、待通知数据或缓存节点残留。 +4. 新包须在真机或模拟器重新采集冻屏采样。`QueueManagement`、`isArbitrageEnabled` 与 `TradeGuaDanListView.onForeground` 不应继续作为可归因的 TOP 耗时热点;分层采样不得重复相加作为性能收益。 + +## 7. 交付与追溯 + +每个交付项需提供:所属仓库/分支/commit、HAR 或 HAP 版本与校验值、修改文件清单、构建日志、测试设备与场景、前后指标、未覆盖项及回滚包。详细历史证据见 `harmony-ths-futures-freeze/doc/change-notes/` 和 `harmony-ths-futures-apm-oom/doc/change-notes/`。 + +## 8. Change-notes 追溯 + +| 需求域 | 改动仓库 | 原始功能改动记录(括号内为改动仓库) | +| ------ | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 通信与解析 | `harmony-ths-futures`
`ohos_mobile_lib_communication` |

  • [02-xml-taskpool-refactor.md](harmony-ths-futures-freeze/doc/change-notes/02-xml-taskpool-refactor.md)(`harmony-ths-futures`)
  • [03-communication-taskpool-decompression.md](harmony-ths-futures-freeze/doc/change-notes/03-communication-taskpool-decompression.md)(`ohos_mobile_lib_communication`、`harmony-ths-futures`)
  • [04-curve-parsing-taskpool.md](harmony-ths-futures-freeze/doc/change-notes/04-curve-parsing-taskpool.md)(`ohos_mobile_lib_communication`、`harmony-ths-futures`)
  • [14-communication-request-buffer-frame-cleanup.md](harmony-ths-futures-apm-oom/doc/change-notes/14-communication-request-buffer-frame-cleanup.md)(`ohos_mobile_lib_communication`、`harmony-ths-futures`)
| +| 行情与画线 | `harmony-ths-futures`
`hmdrawlinebasicsdk` |
  • [09-drawline-dedup-optimization.md](harmony-ths-futures-freeze/doc/change-notes/09-drawline-dedup-optimization.md)(`hmdrawlinebasicsdk`、`harmony-ths-futures`)
  • [10-table-request-client-subscription-cleanup.md](harmony-ths-futures-apm-oom/doc/change-notes/10-table-request-client-subscription-cleanup.md)(`harmony-ths-futures`)
  • [11-table-request-client-notification-coalescing.md](harmony-ths-futures-apm-oom/doc/change-notes/11-table-request-client-notification-coalescing.md)(`harmony-ths-futures`)
| +| 交易 | `harmony-ths-futures`
`futures_trade_sdk`(原生 `lib-weituosdk`)
`harmony_futures_trade_sdk` |
  • [05-trade-sdk-native-lock-app-mitigation.md](harmony-ths-futures-freeze/doc/change-notes/05-trade-sdk-native-lock-app-mitigation.md)(`harmony-ths-futures`、`futures_trade_sdk` 的原生 `lib-weituosdk`)
  • [07-trade-sdk-match-push-optimization.md](harmony-ths-futures-freeze/doc/change-notes/07-trade-sdk-match-push-optimization.md)(`harmony-ths-futures`、`harmony_futures_trade_sdk`)
  • [08-trade-page-online-status-cache.md](harmony-ths-futures-freeze/doc/change-notes/08-trade-page-online-status-cache.md)(`harmony-ths-futures`)
  • [11-top10-followup-fixes.md](harmony-ths-futures-freeze/doc/change-notes/11-top10-followup-fixes.md)(`harmony-ths-futures`)
  • [12-trade-sdk-market-price-cache-cleanup.md](harmony-ths-futures-apm-oom/doc/change-notes/12-trade-sdk-market-price-cache-cleanup.md)(`harmony_futures_trade_sdk`)
| +| 页面生命周期 | `harmony-ths-futures` |
  • [13-webview-overview-request-cleanup.md](harmony-ths-futures-apm-oom/doc/change-notes/13-webview-overview-request-cleanup.md)(`harmony-ths-futures`)
|