From 0a372f1339ece05e7ce0fc0783b3d49b0a1871d0 Mon Sep 17 00:00:00 2001 From: lanterngamescn Date: Fri, 26 Jun 2026 07:11:48 +0800 Subject: [PATCH] =?UTF-8?q?M5(T-M5-03):=20=E9=AB=98=E9=A2=91=E5=87=BA?= =?UTF-8?q?=E7=AB=99=E8=8A=82=E6=B5=81=E2=80=94=E2=80=94=E9=80=9A=E7=94=A8?= =?UTF-8?q?=20Debouncer/Throttler=20+=20=E7=BD=91=E7=BB=9C=E5=8F=98?= =?UTF-8?q?=E5=8C=96=E9=98=B2=E6=8A=96=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - common 新增 Debouncer/Throttler 工具(不在桥核心按 handler 名节流,避免桥核心 感知能力,违反零感知约束;由 Provider 自治应用) - NetworkProvider:netAvailable/netLost/netCapabilitiesChange 三事件统一经 300ms 防抖,切网瞬间多次突发合并为一次出站;onBackground/onDestroy 取消待发 - 说明:连续定位已受系统 timeInterval:5(5s) 约束、Device 为被动查询,无需额外节流; lzyzsd 协议 _handleMessageFromNative 单条语义,不做跨条合并(保 H5 零改动) Co-Authored-By: Claude Opus 4.8 (1M context) --- common/Index.ets | 1 + common/src/main/ets/util/Throttle.ets | 76 +++++++++++++++++++ .../main/ets/providers/NetworkProvider.ets | 16 +++- 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 common/src/main/ets/util/Throttle.ets diff --git a/common/Index.ets b/common/Index.ets index a893158..b970b3b 100644 --- a/common/Index.ets +++ b/common/Index.ets @@ -9,3 +9,4 @@ export { NavEvents, OpenGenericWebPayload, SwitchGamePayload, BackGamePayload } export { DIContainer, Factory } from './src/main/ets/di/DIContainer'; export { ErrorCenter, ErrorStrategy, ErrorSink } from './src/main/ets/error/ErrorCenter'; export { AppEnv } from './src/main/ets/env/AppEnv'; +export { Debouncer, Throttler } from './src/main/ets/util/Throttle'; diff --git a/common/src/main/ets/util/Throttle.ets b/common/src/main/ets/util/Throttle.ets new file mode 100644 index 0000000..0189c7d --- /dev/null +++ b/common/src/main/ets/util/Throttle.ets @@ -0,0 +1,76 @@ +/** + * 节流 / 防抖工具(T-M5-03)。供高频出站源(如网络变化广播)合并突发、降低跨引擎调用次数。 + * + * 不在桥核心做按 handler 名的节流(那会让桥核心感知具体能力,违反「桥核心零感知」约束); + * 由各 Provider 自治决定对哪些可合并的状态广播应用本工具。 + */ + +/** 防抖:连续触发只在最后一次后 delayMs 执行一次(合并突发,取最终状态)。 */ +export class Debouncer { + private readonly delayMs: number; + private timer: number = -1; + + constructor(delayMs: number) { + this.delayMs = delayMs; + } + + /** 触发:取消上一次待执行,重新计时;窗口静默 delayMs 后执行 action。 */ + run(action: () => void): void { + this.cancel(); + this.timer = setTimeout(() => { + this.timer = -1; + action(); + }, this.delayMs); + } + + /** 取消未执行的回调(Provider 去激活/销毁时调用,防泄漏与打已弃桥)。 */ + cancel(): void { + if (this.timer !== -1) { + clearTimeout(this.timer); + this.timer = -1; + } + } +} + +/** 限流:窗口 windowMs 内最多执行一次(leading),窗口内最后一次在窗口末补执行(trailing)。 */ +export class Throttler { + private readonly windowMs: number; + private last: number = 0; + private timer: number = -1; + private pending: (() => void) | undefined = undefined; + + constructor(windowMs: number) { + this.windowMs = windowMs; + } + + /** 触发:距上次执行 ≥ windowMs 立即执行;否则记为待执行,窗口末执行最后一次。 */ + run(action: () => void): void { + const now: number = Date.now(); + const elapsed: number = now - this.last; + if (elapsed >= this.windowMs) { + this.last = now; + action(); + return; + } + this.pending = action; + if (this.timer === -1) { + this.timer = setTimeout(() => { + this.timer = -1; + this.last = Date.now(); + const p = this.pending; + this.pending = undefined; + if (p !== undefined) { + p(); + } + }, this.windowMs - elapsed); + } + } + + cancel(): void { + if (this.timer !== -1) { + clearTimeout(this.timer); + this.timer = -1; + } + this.pending = undefined; + } +} diff --git a/feature_capabilities/src/main/ets/providers/NetworkProvider.ets b/feature_capabilities/src/main/ets/providers/NetworkProvider.ets index 7e42f1b..4e9b3c5 100644 --- a/feature_capabilities/src/main/ets/providers/NetworkProvider.ets +++ b/feature_capabilities/src/main/ets/providers/NetworkProvider.ets @@ -8,7 +8,7 @@ import { connection } from '@kit.NetworkKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { BridgeController } from 'feature_bridge'; import { InboundHandlers, OutboundHandlers } from 'contracts'; -import { Logger } from 'common'; +import { Logger, Debouncer } from 'common'; import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider'; /** 网络码(契约:1 无网 / 2 WiFi / 3 移动)。 */ @@ -16,11 +16,16 @@ const NET_NONE: string = '1'; const NET_WIFI: string = '2'; const NET_MOBILE: string = '3'; +/** 网络变化防抖窗口:切网瞬间 netAvailable/netCapabilitiesChange 常连发多次,合并为一次出站。 */ +const NET_DEBOUNCE_MS: number = 300; + export class NetworkProvider implements CapabilityProvider { readonly name: string = 'network'; private readonly log: Logger = Logger.tag('NetworkProvider'); private bridge: BridgeController | undefined = undefined; private conn: connection.NetConnection | undefined = undefined; + /** T-M5-03:合并短时间内的多次网络变化,取最终状态后只出站一次。 */ + private readonly debouncer: Debouncer = new Debouncer(NET_DEBOUNCE_MS); register(bridge: BridgeController, _ctx: CapabilityContext): void { this.bridge = bridge; @@ -40,10 +45,12 @@ export class NetworkProvider implements CapabilityProvider { /** 槽去激活:注销网络变化订阅(非激活端不监听)。 */ onBackground(): void { + this.debouncer.cancel(); this.unsubscribe(); } onDestroy(): void { + this.debouncer.cancel(); this.unsubscribe(); } @@ -78,9 +85,10 @@ export class NetworkProvider implements CapabilityProvider { try { const conn: connection.NetConnection = connection.createNetConnection(); this.conn = conn; - conn.on('netAvailable', () => this.broadcast()); - conn.on('netLost', () => this.bridge?.callHandler(OutboundHandlers.GetNetwork, NET_NONE)); - conn.on('netCapabilitiesChange', () => this.broadcast()); + // 三类事件统一经防抖 → broadcast 计算最终网络码后出站一次(netLost 时 hasDefaultNet=false→NET_NONE) + conn.on('netAvailable', () => this.debouncer.run(() => this.broadcast())); + conn.on('netLost', () => this.debouncer.run(() => this.broadcast())); + conn.on('netCapabilitiesChange', () => this.debouncer.run(() => this.broadcast())); conn.register((e: BusinessError) => { if (e) { this.log.w(`net register failed: ${e.code} ${e.message}`);