添加了后台同步功能,应用置于后台,仍然可以进行同步。
This commit is contained in:
@@ -8,6 +8,30 @@ export class AppSettings {
|
||||
private static readonly STORE: string = 'sync_settings';
|
||||
private static readonly KEY_SHOW_SYSTEM: string = 'show_system_calendar';
|
||||
private static readonly KEY_SYNC_INTERVAL: string = 'sync_interval_minutes';
|
||||
private static readonly KEY_BACKGROUND_SYNC: string = 'background_sync';
|
||||
|
||||
/** 是否开启后台持续同步(长时任务,默认关) */
|
||||
static async getBackgroundSync(context: common.Context): Promise<boolean> {
|
||||
try {
|
||||
const store: preferences.Preferences =
|
||||
await preferences.getPreferences(context, AppSettings.STORE);
|
||||
return await store.get(AppSettings.KEY_BACKGROUND_SYNC, false) as boolean;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static async setBackgroundSync(context: common.Context, value: boolean): Promise<void> {
|
||||
try {
|
||||
const store: preferences.Preferences =
|
||||
await preferences.getPreferences(context, AppSettings.STORE);
|
||||
await store.put(AppSettings.KEY_BACKGROUND_SYNC, value);
|
||||
await store.flush();
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
console.error(`保存后台同步设置失败: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否混合显示系统日历日程(默认开) */
|
||||
static async getShowSystemCalendar(context: common.Context): Promise<boolean> {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// entry/src/main/ets/common/BackgroundSyncService.ets
|
||||
// 后台同步管理:
|
||||
// 1) 长时任务(DATA_TRANSFER):进程不被挂起,前台定时器在后台继续跑(需用户在设置中开启)
|
||||
// 2) 延迟任务(workScheduler):App 被杀/重启后由系统按条件拉起兜底同步(最小周期 20 分钟)
|
||||
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
|
||||
import { wantAgent, common } from '@kit.AbilityKit';
|
||||
import { workScheduler } from '@kit.BackgroundTasksKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { AppSettings } from './AppSettings';
|
||||
import { LogUtil } from './LogUtil';
|
||||
|
||||
const WORK_ID: number = 1001;
|
||||
|
||||
export class BackgroundSyncService {
|
||||
/** 申请长时任务(DATA_TRANSFER):申请成功后进程在后台不被挂起 */
|
||||
static async startContinuousTask(context: common.UIAbilityContext): Promise<void> {
|
||||
try {
|
||||
const info: wantAgent.WantAgentInfo = {
|
||||
wants: [{
|
||||
bundleName: context.abilityInfo.bundleName,
|
||||
abilityName: 'EntryAbility'
|
||||
}],
|
||||
operationType: wantAgent.OperationType.START_ABILITY,
|
||||
requestCode: 0
|
||||
};
|
||||
const agent = await wantAgent.getWantAgent(info);
|
||||
await backgroundTaskManager.startBackgroundRunning(
|
||||
context, backgroundTaskManager.BackgroundMode.DATA_TRANSFER, agent);
|
||||
LogUtil.write('长时任务已申请:后台持续同步开启');
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
LogUtil.write(`长时任务申请失败: ${e.code} - ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 停止长时任务 */
|
||||
static async stopContinuousTask(context: common.UIAbilityContext): Promise<void> {
|
||||
try {
|
||||
await backgroundTaskManager.stopBackgroundRunning(context);
|
||||
LogUtil.write('长时任务已停止');
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
LogUtil.write(`长时任务停止失败: ${e.code} - ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册延迟任务兜底同步:无论长时任务开关与否都注册。
|
||||
* 条件:有任意网络即执行;周期重复;重启后自动恢复。
|
||||
* 注:repeatCycleTime 各版本下限不同(20 分钟 ~ 2 小时),先用 20 分钟,
|
||||
* 被系统拒绝(9700003 条件无效)时降级为 2 小时再试。
|
||||
*/
|
||||
static scheduleDeferredSync(context: common.UIAbilityContext): void {
|
||||
const build = (cycleMs: number): workScheduler.WorkInfo => {
|
||||
const info: workScheduler.WorkInfo = {
|
||||
workId: WORK_ID,
|
||||
bundleName: context.abilityInfo.bundleName,
|
||||
abilityName: 'SyncWorkAbility',
|
||||
networkType: workScheduler.NetworkType.NETWORK_TYPE_ANY,
|
||||
isRepeat: true,
|
||||
isPersisted: true,
|
||||
repeatCycleTime: cycleMs
|
||||
};
|
||||
return info;
|
||||
};
|
||||
try {
|
||||
workScheduler.startWork(build(20 * 60 * 1000));
|
||||
LogUtil.write('延迟任务已注册:20 分钟兜底同步');
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
if (e.code === 9700003) {
|
||||
try {
|
||||
workScheduler.startWork(build(2 * 60 * 60 * 1000));
|
||||
LogUtil.write('延迟任务降级注册:2 小时兜底同步');
|
||||
return;
|
||||
} catch (err2) {
|
||||
const e2 = err2 as BusinessError;
|
||||
LogUtil.write(`延迟任务注册失败(2h): ${e2.code} - ${e2.message}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
LogUtil.write(`延迟任务注册失败: ${e.code} - ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** App 启动时统一入口:注册延迟任务 + 按设置恢复长时任务 */
|
||||
static async initOnLaunch(context: common.UIAbilityContext): Promise<void> {
|
||||
BackgroundSyncService.scheduleDeferredSync(context);
|
||||
const enabled: boolean = await AppSettings.getBackgroundSync(context);
|
||||
if (enabled) {
|
||||
await BackgroundSyncService.startContinuousTask(context);
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置页开关切换 */
|
||||
static async setBackgroundSync(context: common.UIAbilityContext, enabled: boolean): Promise<void> {
|
||||
await AppSettings.setBackgroundSync(context, enabled);
|
||||
if (enabled) {
|
||||
await BackgroundSyncService.startContinuousTask(context);
|
||||
} else {
|
||||
await BackgroundSyncService.stopContinuousTask(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export class SyncEngine {
|
||||
* 同步一个 CalDAV 账号(先推该账号的本地修改,再拉远端变更),
|
||||
* 返回远端事件总数(拉取侧)。带 120 秒超时保护。
|
||||
*/
|
||||
static async syncAccount(context: common.UIAbilityContext, acc: DavAccount): Promise<number> {
|
||||
static async syncAccount(context: common.Context, acc: DavAccount): Promise<number> {
|
||||
const t0: number = Date.now();
|
||||
LogUtil.write(`========== 同步账号「${acc.name}」开始:服务器 ${acc.serverUrl},共 ${acc.calendarHrefs.length} 个日历本 ==========`);
|
||||
try {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { window } from '@kit.ArkUI';
|
||||
import { BackgroundSyncService } from '../common/BackgroundSyncService';
|
||||
|
||||
const DOMAIN = 0x0000;
|
||||
|
||||
export default class EntryAbility extends UIAbility {
|
||||
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
||||
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
|
||||
// 后台同步:注册延迟任务兜底 + 按设置恢复长时任务
|
||||
BackgroundSyncService.initOnLaunch(this.context);
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { router } from '@kit.ArkUI';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { AppSettings } from '../common/AppSettings';
|
||||
import { BackgroundSyncService } from '../common/BackgroundSyncService';
|
||||
import { LogUtil } from '../common/LogUtil';
|
||||
|
||||
const INTERVAL_OPTIONS: number[] = [1, 5, 15, 30, 60];
|
||||
@@ -12,6 +13,7 @@ const INTERVAL_OPTIONS: number[] = [1, 5, 15, 30, 60];
|
||||
struct SettingsPage {
|
||||
@State showSystem: boolean = true;
|
||||
@State intervalMinutes: number = 1;
|
||||
@State backgroundSync: boolean = false;
|
||||
private context?: common.Context;
|
||||
|
||||
aboutToAppear(): void {
|
||||
@@ -27,6 +29,9 @@ struct SettingsPage {
|
||||
AppSettings.getSyncIntervalMinutes(ctx).then((v: number): void => {
|
||||
this.intervalMinutes = v;
|
||||
});
|
||||
AppSettings.getBackgroundSync(ctx).then((v: boolean): void => {
|
||||
this.backgroundSync = v;
|
||||
});
|
||||
}
|
||||
|
||||
private async saveShowSystem(value: boolean): Promise<void> {
|
||||
@@ -48,6 +53,24 @@ struct SettingsPage {
|
||||
.showToast({ message: `自动同步间隔已设为 ${minutes} 分钟` });
|
||||
}
|
||||
|
||||
private async saveBackgroundSync(value: boolean): Promise<void> {
|
||||
if (this.context === undefined) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await BackgroundSyncService.setBackgroundSync(
|
||||
this.context as common.UIAbilityContext, value);
|
||||
this.getUIContext().getPromptAction().showToast({
|
||||
message: value
|
||||
? '后台持续同步已开启,通知栏将显示常驻通知'
|
||||
: '后台持续同步已关闭'
|
||||
});
|
||||
} catch (err) {
|
||||
this.backgroundSync = !value; // 失败回滚 UI
|
||||
this.getUIContext().getPromptAction().showToast({ message: '设置失败,请重试' });
|
||||
}
|
||||
}
|
||||
|
||||
private intervalLabel(minutes: number): string {
|
||||
return minutes >= 60 ? `${minutes / 60} 小时` : `${minutes} 分钟`;
|
||||
}
|
||||
@@ -134,6 +157,34 @@ struct SettingsPage {
|
||||
.borderRadius(12)
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.border({ width: 1, color: $r('app.color.shadow_color') })
|
||||
|
||||
// 后台持续同步(长时任务)
|
||||
Row({ space: 10 }) {
|
||||
Column({ space: 2 }) {
|
||||
Text('后台持续同步')
|
||||
.fontSize(15)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor($r('app.color.text_primary'))
|
||||
Text('开启后申请后台长时任务,App 退到后台也按设定间隔继续同步(通知栏会显示常驻通知)')
|
||||
.fontSize(12)
|
||||
.fontColor($r('app.color.text_secondary'))
|
||||
}
|
||||
.alignItems(HorizontalAlign.Start)
|
||||
.layoutWeight(1)
|
||||
Toggle({ type: ToggleType.Switch, isOn: this.backgroundSync })
|
||||
.selectedColor($r('app.color.brand'))
|
||||
.onChange((isOn: boolean) => {
|
||||
if (isOn !== this.backgroundSync) {
|
||||
this.backgroundSync = isOn;
|
||||
this.saveBackgroundSync(isOn);
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
.padding(14)
|
||||
.borderRadius(12)
|
||||
.backgroundColor($r('app.color.card_bg'))
|
||||
.border({ width: 1, color: $r('app.color.shadow_color') })
|
||||
}
|
||||
.width('100%')
|
||||
.padding({ left: 20, right: 20, top: 8, bottom: 20 })
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// entry/src/main/ets/syncwork/SyncWorkAbility.ets
|
||||
// 延迟任务兜底同步:App 进程被杀/设备重启后,系统在满足条件(有网络)时拉起本扩展执行同步
|
||||
// 注意:必须幂等——同步本身基于 ETag 全量比对,天然幂等;执行完必须 stopWork,否则系统按超时处理
|
||||
import { WorkSchedulerExtensionAbility, workScheduler } from '@kit.BackgroundTasksKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { DavAccount, AccountStore, TYPE_CALDAV } from '../common/AccountStore';
|
||||
import { SyncEngine } from '../common/SyncEngine';
|
||||
import { CardDataService } from '../common/CardDataService';
|
||||
import { ReminderService } from '../common/ReminderService';
|
||||
import { LogUtil } from '../common/LogUtil';
|
||||
|
||||
export default class SyncWorkAbility extends WorkSchedulerExtensionAbility {
|
||||
onWorkStart(workInfo: workScheduler.WorkInfo): void {
|
||||
LogUtil.init(this.context);
|
||||
LogUtil.write(`延迟任务触发:workId=${workInfo.workId}`);
|
||||
this.doSync(workInfo);
|
||||
}
|
||||
|
||||
onWorkStop(workInfo: workScheduler.WorkInfo): void {
|
||||
LogUtil.write(`延迟任务结束:workId=${workInfo.workId}`);
|
||||
}
|
||||
|
||||
private async doSync(workInfo: workScheduler.WorkInfo): Promise<void> {
|
||||
try {
|
||||
const context = this.context;
|
||||
const accounts: DavAccount[] = await AccountStore.loadAll(context);
|
||||
let ok: number = 0;
|
||||
for (const acc of accounts) {
|
||||
if (acc.type !== TYPE_CALDAV) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await SyncEngine.withTimeout(
|
||||
SyncEngine.syncAccount(context, acc), 100000);
|
||||
ok++;
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
LogUtil.write(`延迟任务同步「${acc.name}」失败: ${e.message}`);
|
||||
}
|
||||
}
|
||||
await SyncEngine.settleLocalEvents(context);
|
||||
// 同步后刷新卡片 + 重建提醒,保证后台同步的成果直接可见
|
||||
await CardDataService.pushToAllForms(context);
|
||||
await ReminderService.refreshReminders(context);
|
||||
LogUtil.write(`延迟任务同步完成:${ok} 个账号`);
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
LogUtil.write(`延迟任务同步失败: ${e.message}`);
|
||||
} finally {
|
||||
// 必须主动结束,否则系统按 120 秒超时处理
|
||||
workScheduler.stopWork(workInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,9 @@
|
||||
"startWindowIcon": "$media:startIcon",
|
||||
"startWindowBackground": "$color:start_window_background",
|
||||
"exported": true,
|
||||
"backgroundModes": [
|
||||
"dataTransfer"
|
||||
],
|
||||
"skills": [
|
||||
{
|
||||
"entities": [
|
||||
@@ -95,6 +98,13 @@
|
||||
"resource": "$profile:form_config"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SyncWorkAbility",
|
||||
"srcEntry": "./ets/syncwork/SyncWorkAbility.ets",
|
||||
"description": "$string:sync_work_desc",
|
||||
"type": "workScheduler",
|
||||
"exported": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
{
|
||||
"name": "card_6x4_name",
|
||||
"value": "日程大全"
|
||||
},
|
||||
{
|
||||
"name": "sync_work_desc",
|
||||
"value": "日历后台兜底同步任务"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user