背景:用户反馈"后台常驻其实没实现,过一段时间常驻通知就没了"。调研确认这是 系统强制管控、不是 bug:DATA_TRANSFER 长时任务有一致性校验,**超 10 分钟未更新 传输进度即被强制取消**。日历同步是"间歇跑、平时无进度",不符合该语义 → 必被取消, 且频繁触发校验失败可能被列入管控名单。另:workScheduler 请求 20 分钟低于系统 "应用活跃分组"下限(活跃≥2h),被静默压制 → 名义 20 分钟实际从未生效。 改为分层调度(目标:用户要看时数据一定最新,而非追求进程常驻): 1. **移除 dataTransfer 长时任务**:BackgroundSyncService 不再申请; module.json5 去掉 backgroundModes 与 ohos.permission.KEEP_BACKGROUND_RUNNING 2. **延迟任务周期 20 分钟 → 2 小时**(对齐系统下限,避免被压制);保留 isRepeat/isPersisted 3. **新增 BackgroundSyncRunner**:后台同步唯一执行体(抽取自 SyncWorkAbility), 供延迟任务/网络恢复/回到前台三处共用,避免逻辑漂移 4. **新增 NetworkSyncWatcher**:net.connection 监听,**从无网恢复到有网时立即补同步** (带 20s 去抖;仅在 netLost→netAvailable 时触发,单纯切网不重复同步) 5. **回到前台补同步**:UIAbility.onForeground 立即同步 + 取消短时任务归还配额 6. **退后台短时任务**:onBackground 用 requestSuspendDelay 兜住退后台那次同步; 回到前台 cancelSuspendDelay(配额仅 10 分钟/日,必须归还) 7. **设置页开关语义调整**:"后台持续同步(常驻通知)" → "后台自动同步", 文案如实说明"系统最短约 2 小时一次 + 回前台/网络恢复立即补同步"; 开关现在真正控制延迟任务的注册/取消 8. **官网文档同步**:sf/uh 改写后台同步说明;upa **移除 KEEP_BACKGROUND_RUNNING 权限行** (权限表必须与实际声明一致);已重建 404 并部署线上 注:applyEfficiencyResources 在当前 SDK 不可用,未采用。
80 lines
3.2 KiB
Plaintext
80 lines
3.2 KiB
Plaintext
// entry/src/main/ets/common/NetworkSyncWatcher.ets
|
|
// ⭐ 网络恢复补同步:从"无网"变为"有网"时立刻同步一次。
|
|
//
|
|
// 为什么这条最有价值(《定时同步方案调研.md》第 ④ 层):
|
|
// 延迟任务最短 2 小时、还会随应用活跃度降级,长时间在外(飞行模式/地铁/弱网)期间
|
|
// 基本不会跑。用户一旦回到有网环境,**立刻对齐一次**,才能做到
|
|
// "我要看的时候数据是最新的"。这比追求"进程常驻"实际得多。
|
|
//
|
|
// 实现:net.connection 的 netAvailable / netLost 事件 + 去抖,避免网络抖动时连打多次。
|
|
import { connection } from '@kit.NetworkKit';
|
|
import { common } from '@kit.AbilityKit';
|
|
import { BackgroundSyncRunner } from './BackgroundSyncRunner';
|
|
import { LogUtil } from './LogUtil';
|
|
|
|
/** 网络恢复后的去抖间隔:避免弱网下反复"断开→恢复"导致连续同步 */
|
|
const DEBOUNCE_MS: number = 20 * 1000;
|
|
|
|
export class NetworkSyncWatcher {
|
|
private static netConn: connection.NetConnection | null = null;
|
|
/** 上一次触发同步的时间,用于去抖 */
|
|
private static lastTriggerAt: number = 0;
|
|
/** 当前是否处于"无网"状态(只有从无网恢复才触发,避免频繁切换网络时误触发) */
|
|
private static offline: boolean = false;
|
|
|
|
/** 开始监听网络变化(幂等:重复调用不会重复注册) */
|
|
static start(context: common.UIAbilityContext): void {
|
|
if (NetworkSyncWatcher.netConn !== null) {
|
|
return;
|
|
}
|
|
try {
|
|
const conn: connection.NetConnection = connection.createNetConnection();
|
|
conn.on('netLost', (): void => {
|
|
NetworkSyncWatcher.offline = true;
|
|
LogUtil.write('网络断开:标记为无网状态');
|
|
});
|
|
conn.on('netAvailable', (): void => {
|
|
// ⭐ 只有"从无网恢复"才补同步:单纯切换 Wi-Fi/蜂窝时不重复触发
|
|
if (!NetworkSyncWatcher.offline) {
|
|
return;
|
|
}
|
|
NetworkSyncWatcher.offline = false;
|
|
NetworkSyncWatcher.triggerDebounced(context, 'netRecover');
|
|
});
|
|
conn.register((): void => {
|
|
LogUtil.write('网络监听已注册(网络恢复补同步)');
|
|
});
|
|
NetworkSyncWatcher.netConn = conn;
|
|
} catch (err) {
|
|
LogUtil.write(`网络监听注册失败:${(err as Error).message}`);
|
|
}
|
|
}
|
|
|
|
/** 停止监听(一般不需要,App 存活期间持续有效) */
|
|
static stop(): void {
|
|
if (NetworkSyncWatcher.netConn === null) {
|
|
return;
|
|
}
|
|
try {
|
|
NetworkSyncWatcher.netConn.unregister((err: object): void => {
|
|
// 忽略取消失败
|
|
});
|
|
} catch (err) {
|
|
// 忽略
|
|
}
|
|
NetworkSyncWatcher.netConn = null;
|
|
}
|
|
|
|
/** 去抖后触发一次同步 */
|
|
private static triggerDebounced(context: common.UIAbilityContext, tag: string): void {
|
|
const now: number = Date.now();
|
|
if (now - NetworkSyncWatcher.lastTriggerAt < DEBOUNCE_MS) {
|
|
LogUtil.write(`网络恢复补同步:距上次过快(<20s),跳过`);
|
|
return;
|
|
}
|
|
NetworkSyncWatcher.lastTriggerAt = now;
|
|
LogUtil.write('网络已恢复 → 触发补同步');
|
|
// 不 await:事件回调里不能阻塞;BackgroundSyncRunner 内部全 try-catch,不会抛出
|
|
BackgroundSyncRunner.runOnce(context, tag);
|
|
}
|
|
} |