HarmonyOS 5.0.0 后台恢复状态乱了怎么办:请求取消、页面快照和旧结果拦截怎么拆

HarmonyOS 应用从后台回到前台时,页面看起来只是重新显示了一下,但代码里可能同时发生几件事:旧请求还没结束,新请求又开始了;页面快照要恢复,接口数据也要刷新;窗口尺寸可能变了,列表状态还要重新算。
如果这些边界没拆清楚,就会出现很难解释的现象:
| 现象 | 表面看起来 | 实际可能是 |
|---|---|---|
| 切回来列表变成旧数据 | 缓存没刷新 | 旧请求比新请求晚返回 |
| 页面先显示空白再闪一下 | 渲染慢 | 快照恢复和接口刷新互相覆盖 |
| 筛选条件丢了 | 状态管理写错 | 后台前后的状态版本没校验 |
| 偶尔才复现 | 网络问题 | 生命周期和异步任务时序撞在一起 |
我会把后台恢复拆成三层:请求取消、页面快照、旧结果拦截。只做其中一个,短期看能好一点,但线上换个网络或设备形态又会出问题。
先看一个常见写法。页面进来就请求数据,切后台回来再请求一次。
type PageData = {
keyword: string;
rows: string[];
updatedAt: number;
};
async function loadPageData(keyword: string): Promise<PageData> {
await new Promise<void>((resolve) => setTimeout(resolve, 120));
return {
keyword,
rows: ['item-a', 'item-b', 'item-c'],
updatedAt: Date.now()
};
}
let pageData: PageData | undefined;
async function refresh(keyword: string): Promise<void> {
pageData = await loadPageData(keyword);
}
这段代码的问题不在请求本身,而在“谁回来都能写”。用户先搜索 arkui,马上切后台,再回来搜索 rdb。如果 arkui 那次请求最后才返回,它就会把 rdb 的新结果覆盖掉。
我更稳的做法是给每次刷新加一个版本号,旧版本结果直接丢弃。
class RequestVersion {
private value = 0;
next(scene: string): number {
this.value += 1;
console.info('[request-version] scene=' + scene + ', value=' + this.value);
return this.value;
}
isCurrent(version: number): boolean {
return version === this.value;
}
}
const requestVersion = new RequestVersion();
async function refreshSafely(keyword: string, scene: string): Promise<void> {
const version = requestVersion.next(scene);
const result = await loadPageData(keyword);
if (!requestVersion.isCurrent(version)) {
console.warn('[request-version] ignore stale result, keyword=' + keyword);
return;
}
pageData = result;
}
这一步很关键。它不是为了让请求变快,而是为了保证只有最新场景能写页面。后台恢复、搜索切换、分页加载、本地缓存回填,都可以用同一套判断。
第二个问题更像线上偶发。为了让用户切回来时不空白,我们通常会保存一份页面快照。但如果快照恢复和接口刷新都能写同一个状态,就可能互相打架。
我会把状态来源分清楚:快照只负责“先恢复可看内容”,接口刷新负责“确认最新内容”。快照不能覆盖刷新后的数据。
type PageSnapshot = {
keyword: string;
scrollY: number;
rows: string[];
savedAt: number;
};
class SnapshotStore {
private snapshot: PageSnapshot | undefined;
save(data: PageData, scrollY: number): void {
this.snapshot = {
keyword: data.keyword,
scrollY,
rows: data.rows,
savedAt: Date.now()
};
}
restore(maxAgeMs: number): PageSnapshot | undefined {
if (!this.snapshot) {
return undefined;
}
if (Date.now() - this.snapshot.savedAt > maxAgeMs) {
return undefined;
}
return this.snapshot;
}
}
后台恢复时,先恢复快照,再启动一次受版本控制的刷新。
const snapshotStore = new SnapshotStore();
async function onPageHide(scrollY: number): Promise<void> {
if (pageData) {
snapshotStore.save(pageData, scrollY);
}
}
async function onPageShow(): Promise<void> {
const snapshot = snapshotStore.restore(5 * 60 * 1000);
if (snapshot) {
pageData = {
keyword: snapshot.keyword,
rows: snapshot.rows,
updatedAt: snapshot.savedAt
};
console.info('[snapshot] restored keyword=' + snapshot.keyword);
}
await refreshSafely(pageData?.keyword ?? '', 'page-show-refresh');
}
这里的重点是顺序:快照只在回来时先顶一下,后续刷新仍然走版本校验。旧请求不能写,新请求才能写。
版本号能挡住旧结果,但旧请求本身还在跑。如果请求很重、会占资源,最好再加一层取消。
class CancelBox {
private cancelled = false;
cancel(): void {
this.cancelled = true;
}
get isCancelled(): boolean {
return this.cancelled;
}
}
let currentCancelBox: CancelBox | undefined;
async function refreshWithCancel(keyword: string): Promise<void> {
currentCancelBox?.cancel();
const cancelBox = new CancelBox();
currentCancelBox = cancelBox;
const result = await loadPageData(keyword);
if (cancelBox.isCancelled) {
console.warn('[request-cancel] cancelled keyword=' + keyword);
return;
}
pageData = result;
}
如果项目里请求层支持真正的 abort,那就用真实取消;如果暂时没有,至少也要做结果拦截。不能因为取消能力不完整,就让旧结果随便写页面。
| 方案 | 能解决什么 | 不足 |
|---|---|---|
只在 onPageShow 重新请求 | 数据可能更新 | 旧请求仍然可能覆盖新状态 |
| 只保存页面快照 | 返回时不空白 | 快照可能盖掉新结果 |
| 快照恢复 + 请求版本 + 旧结果拦截 | 可看、可刷新、可防旧写入 | 代码多一点,但回归最稳 |
我会选第三种。后台恢复的问题本质是“时序不确定”,不是某一个 API 调一次就完事。只要时序不确定,就要有版本、来源和写入规则。
type ResumeProbe = {
scene: string;
version: number;
keyword: string;
source: 'snapshot' | 'network' | 'ignored';
costMs: number;
};
function reportResumeProbe(record: ResumeProbe): void {
console.info('[resume-probe] ' + JSON.stringify(record));
}
回归动作我会这样设计:
| 动作 | 预期 |
|---|---|
| 搜索 A 后马上搜索 B | A 的旧结果不会覆盖 B |
| 搜索后切后台再回来 | 先恢复快照,再刷新最新数据 |
| 弱网下连续切前后台 | 日志能看到旧请求被忽略 |
| 快照超过有效期 | 不恢复过期快照,直接刷新 |
| 重复三轮 | 页面关键状态不会反复跳回旧值 |
后台恢复写稳以后,用户感知会很明显:页面回来不空白,数据不会莫名倒退,列表位置也更容易保持。后续扩到多窗口、折叠屏、平板和鸿蒙电脑时,这套“状态来源 + 版本校验 + 旧结果拦截”的结构还能继续复用。
更多推荐



所有评论(0)