HarmonyOS 窗口资源降载:WindowStage前后台切换与任务收放

请添加图片描述

应用进入后台后仍保持地图定位刷新、动画定时器、相机预览、WebSocket心跳和大图预取,不仅增加功耗,也可能与系统后台规则冲突。另一种极端是窗口刚失去焦点就停止所有任务,用户打开弹窗、分屏操作或临时切换焦点时,页面频繁断流重建,体验同样糟糕。

WindowStage事件提供了比页面显示更高一层的窗口状态。正确做法不是在每个回调里随手启动或停止资源,而是把事件翻译成“活跃、降频、冻结”三档策略,由单一资源协调器幂等执行。本文给出完整实现,并说明INACTIVEHIDDEN不能等价处理。

1. 四个基础事件表达不同事实

SHOWN表示窗口处于前台,ACTIVE表示获得焦点,INACTIVE表示失去焦点,HIDDEN表示进入后台。API 11还提供RESUMEDPAUSED表示前台是否可交互。

事件 窗口事实 建议资源档位
SHOWN 已在前台显示 恢复必要数据,先用温和频率
ACTIVE / RESUMED 前台且可交互 恢复交互所需实时任务
INACTIVE / PAUSED 前台但暂不可交互 降频,不立刻销毁全部资源
HIDDEN 已进入后台 停止前台专属任务,保存检查点

如果一收到INACTIVE就断开长连接,系统弹窗或多窗口焦点变化会造成频繁重连。真正适合重降载的边界通常是HIDDEN

2. 先列出每类任务的收放策略

不同资源不能统一调用一个stopAll()。动画可暂停并保留进度,图片预取可直接取消,长连接可降低心跳或按业务断开,编辑草稿需要先保存。

type ResourceLevel = 'active' | 'reduced' | 'frozen';

interface ManagedTask {
  readonly name: string;
  apply(level: ResourceLevel): Promise<void>;
}

interface ResourceDecision {
  level: ResourceLevel;
  reason: string;
  changedAt: number;
}

把策略放在任务实现内,协调器只决定档位。这样新增播放器或传感器任务时,不需要修改窗口事件分支。

3. WindowStage事件需要保存同一个回调引用

注册与解除必须使用同一个函数对象。把回调定义为类字段,避免off时重新创建匿名函数导致监听残留。

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  private windowStage?: window.WindowStage;

  private readonly onStageEvent = (event: window.WindowStageEventType): void => {
    StageResourceHub.instance.onWindowEvent(event);
  };

  onWindowStageCreate(stage: window.WindowStage): void {
    this.windowStage = stage;
    stage.on('windowStageEvent', this.onStageEvent);
    stage.loadContent('pages/Index');
  }

  onWindowStageDestroy(): void {
    this.windowStage?.off('windowStageEvent', this.onStageEvent);
    this.windowStage = undefined;
    StageResourceHub.instance.shutdown();
  }
}

Ability销毁时不仅解除事件,还要关闭协调器拥有的任务。只做off会留下网络、定时器或设备句柄。

4. 窗口降载闭环允许再次恢复

请添加图片描述

窗口显示后先准备资源,获得焦点进入活跃档;失去焦点只降频;进入后台冻结前台任务;再次回到前台时按依赖顺序恢复。恢复必须读取上次检查点,而不是假设旧句柄仍然有效。

5. 用纯函数把事件映射为档位

事件到策略的映射最好独立成纯函数,便于单元测试覆盖每个枚举值。未知事件保持当前档位或使用保守策略,不能默认进入活跃状态。

function levelForEvent(event: window.WindowStageEventType): ResourceLevel {
  switch (event) {
    case window.WindowStageEventType.ACTIVE:
    case window.WindowStageEventType.RESUMED:
      return 'active';
    case window.WindowStageEventType.SHOWN:
    case window.WindowStageEventType.INACTIVE:
    case window.WindowStageEventType.PAUSED:
      return 'reduced';
    case window.WindowStageEventType.HIDDEN:
      return 'frozen';
    default:
      return 'reduced';
  }
}

实际产品可以让SHOWN直接进入active,但仍要等待页面与账号状态准备完成。事件只说明窗口状态,不保证所有业务依赖已经就绪。

6. 协调器必须幂等并串行执行

系统可能连续发送相同或相邻事件。协调器遇到相同档位直接返回,不重复启动任务;档位变化时串行应用,防止activefrozen并发操作同一连接。

class StageResourceHub {
  static readonly instance: StageResourceHub = new StageResourceHub();
  private level: ResourceLevel = 'frozen';
  private tasks: ManagedTask[] = [];
  private transition: Promise<void> = Promise.resolve();

  register(task: ManagedTask): void {
    this.tasks.push(task);
  }

  onWindowEvent(event: window.WindowStageEventType): void {
    const target = levelForEvent(event);
    this.transition = this.transition.then(async () => {
      if (target === this.level) return;
      for (const task of this.tasks) {
        await task.apply(target);
      }
      this.level = target;
    }).catch((error: Object) => {
      console.error(`resource transition failed: ${JSON.stringify(error)}`);
    });
  }

  shutdown(): void {
    this.tasks = [];
    this.level = 'frozen';
  }
}

如果任务之间有依赖,应明确恢复顺序与冻结的逆序。例如先恢复认证再恢复业务连接,冻结时先停业务请求再停认证刷新。

7. 定时刷新任务按档位改变频率

下面的任务在active每15秒刷新,在reduced每60秒刷新,frozen彻底清除定时器。它不会重复创建多个定时器。

class RefreshTask implements ManagedTask {
  readonly name: string = 'dashboard_refresh';
  private timerId: number = -1;

  async apply(level: ResourceLevel): Promise<void> {
    this.stopTimer();
    if (level === 'frozen') return;
    const interval = level === 'active' ? 15_000 : 60_000;
    this.timerId = setInterval(() => this.refresh(), interval);
    if (level === 'active') {
      await this.refresh();
    }
  }

  private stopTimer(): void {
    if (this.timerId !== -1) {
      clearInterval(this.timerId);
      this.timerId = -1;
    }
  }

  private async refresh(): Promise<void> {
    // 从仓储层获取轻量增量数据
  }
}

降频间隔应根据业务时效性决定。对没有后台价值的推荐刷新,reduced也可以停止;对用户正在看的计时信息,可保留本地计算而停止网络。

8. 窗口资源边界统一进入协调器

请添加图片描述

WindowStage和页面会话提供状态信号,资源协调器决定档位,后台策略约束任务可以做什么。页面不直接持有全局长连接,WindowStage也不理解具体业务任务,边界因此保持清晰。

class PageSessionGate {
  private visiblePageCount: number = 0;

  enter(): void {
    this.visiblePageCount += 1;
  }

  leave(): void {
    this.visiblePageCount = Math.max(0, this.visiblePageCount - 1);
  }

  get hasVisiblePage(): boolean {
    return this.visiblePageCount > 0;
  }
}

多窗口或多个页面共享任务时,仅靠单个页面aboutToDisappear无法决定全局资源是否可停。窗口档位与可见页面计数应共同参与决策。

9. 前台恢复先检查句柄是否仍有效

相机、传感器、音频和WebSocket在后台可能被系统或远端关闭。恢复时先核对状态,再决定复用还是重建;不要直接调用旧实例的start()

interface RecoverableConnection {
  isUsable(): boolean;
  connect(): Promise<void>;
  reduce(): Promise<void>;
  disconnect(): Promise<void>;
}

async function applyConnectionLevel(
  connection: RecoverableConnection,
  level: ResourceLevel
): Promise<void> {
  if (level === 'active' && !connection.isUsable()) {
    await connection.connect();
  } else if (level === 'reduced') {
    await connection.reduce();
  } else if (level === 'frozen') {
    await connection.disconnect();
  }
}

10. HIDDEN前保存检查点而不是等待销毁

窗口进入后台时,保存编辑位置、下载断点和任务代次。不要把关键保存只放到Ability销毁,因为进程终止前未必给业务足够时间执行复杂操作。

interface SessionCheckpoint {
  route: string;
  scrollOffset: number;
  draftVersion: number;
  savedAt: number;
}

async function saveCheckpoint(checkpoint: SessionCheckpoint): Promise<void> {
  // 写入Preferences或RDB,保持操作短小、可重试
}

保存操作也要防抖,避免INACTIVE与HIDDEN连续到来时重复写同一内容。

11. 多窗口不能用单个布尔值表示前后台

一个应用可能存在多个WindowStage。某个窗口HIDDEN时,另一个仍ACTIVE。应用级资源需要维护每个窗口的状态,再计算全局最高档位。

function mergeLevels(levels: ResourceLevel[]): ResourceLevel {
  if (levels.includes('active')) return 'active';
  if (levels.includes('reduced')) return 'reduced';
  return 'frozen';
}

窗口ID到档位的映射在窗口销毁时必须移除,否则全局状态可能永远停留在旧档位。

12. 前后台切换验收矩阵

[ ] ACTIVE与INACTIVE之间切换不会反复重建重资源
[ ] HIDDEN后前台专属定时器与预取任务停止
[ ] SHOWN恢复时先核对旧句柄可用性
[ ] 重复事件不会创建重复定时器或连接
[ ] 任务应用档位的过程串行执行
[ ] 关键检查点在进入后台时保存
[ ] 多窗口状态按窗口分别维护
[ ] WindowStage销毁时回调与任务都已清理

13. WindowStage资料索引

  • WindowStage.on/offWindowStageEventType:以本机HarmonyOS SDK API 23类型声明为准。
  • Stage模型开发概览

窗口事件只是事实,资源档位才是策略。把INACTIVE理解为暂时失焦,把HIDDEN理解为真正后台,再由幂等协调器统一收放任务,应用既不会在短暂焦点变化中频繁重建,也不会进入后台后继续无意义地消耗资源。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐