Administrator
发布于 2026-09-01 / 26 阅读
0
0

鸿蒙开发系列(第13篇):推送服务与后台任务——Push Kit与Work Scheduler实战

鸿蒙开发系列 · 中级篇

前言

你的天气元服务已经能显示当前天气了,但有个明显的问题:用户每次想看天气都得主动打开应用。如果早上出门前台面卡片显示的还是昨天的数据,体验就大打折扣。反过来,如果用户关注的球队刚刚进球,他希望在锁屏上立刻看到推送——而不是等到下次打开App才知道。

推送和后台任务是现代移动应用的标配能力,但HarmonyOS的实现方式与Android/iOS有显著差异。Android的FCM、厂商推送各自为政,后台服务常常被系统杀死;iOS的APNs相对统一,但后台执行能力受限。HarmonyOS NEXT的做法是:统一推送通道 + 严格后台管控 + 智能任务调度。

本文将带你打通这两条链路:从服务端推送一条消息到设备,以及让应用在后台可靠地完成定时任务。


一、Push Kit 统一推送服务

1.1 Push Kit 架构与优势

HarmonyOS NEXT的推送服务名为 Push Kit,它是一个系统级、全设备统一的推送通道。

┌──────────────┐      HTTPS      ┌──────────────┐      系统级通道      ┌──────────────┐
│   开发者服务器  │ ─────────────→ │  Push Kit    │ ─────────────────→ │  用户设备     │
│  (你的后端)    │   (REST API)   │  推送平台     │   (不依赖App存活)   │ (通知中心/横幅)│
└──────────────┘                └──────────────┘                    └──────────────┘

与Android推送相比,Push Kit的核心优势:

维度

Push Kit(HarmonyOS)

Android FCM/厂商推送

通道层级

系统级,不依赖应用存活

依赖应用进程或厂商服务

设备覆盖

手机、平板、车机、手表统一

各厂商通道不互通

送达率

系统级保障,接近100%

受厂商策略影响,差异大

功耗控制

系统统一调度,单通道保活

多通道并存,功耗叠加

消息类型

通知消息 + 透传消息

各平台定义不一致

1.2 消息类型:通知消息 vs 透传消息

Push Kit支持两种消息:

类型

特点

适用场景

通知消息

由系统渲染展示,应用无感知

新闻、促销、提醒类推送

透传消息

静默投递到应用,应用自行处理

数据同步、指令下发

通知消息的优势是即使应用被杀死也能正常展示;透传消息的优势是可以携带自定义数据,由应用决定如何响应。

1.3 客户端接入实战

步骤1:配置module.json5

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.RECEIVE_NOTIFICATION",
        "reason": "$string:perm_push_reason"
      }
    ]
  }
}

步骤2:获取Push Token

Push Token是设备的唯一标识,服务端需要用它来指定推送目标。

// entry/src/main/ets/utils/PushManager.ets
import { push } from '@kit.PushKit';
import { promptAction } from '@kit.ArkUI';

export class PushManager {
  // 获取Push Token
  static async getToken(): Promise<string> {
    try {
      const token = await push.getToken();
      console.info(`Push Token: ${token}`);
      return token;
    } catch (err) {
      console.error('获取Push Token失败', err);
      throw err;
    }
  }

  // 删除Push Token(如用户退出登录)
  static async deleteToken(): Promise<void> {
    try {
      await push.deleteToken();
      console.info('Push Token已删除');
    } catch (err) {
      console.error('删除Push Token失败', err);
    }
  }

  // 注册推送回调(透传消息)
  static registerPushCallback(onMessage: (data: string) => void): void {
    push.on('message', (message) => {
      console.info('收到透传消息:', JSON.stringify(message));
      onMessage(message.data);
    });
  }

  // 注销推送回调
  static unregisterPushCallback(): void {
    push.off('message');
  }
}

步骤3:在EntryAbility中初始化

// entry/src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { PushManager } from '../utils/PushManager';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 应用启动时获取Token并上报服务端
    PushManager.getToken().then(token => {
      this.reportTokenToServer(token);
    });

    // 注册透传消息监听
    PushManager.registerPushCallback((data) => {
      this.handlePushData(data);
    });
  }

  private async reportTokenToServer(token: string) {
    // 将token上报到你的服务器
    // 服务器后续用此token向该设备推送消息
    console.info('上报Token到服务器:', token);
  }

  private handlePushData(data: string) {
    try {
      const payload = JSON.parse(data);
      // 根据payload.type处理不同类型消息
      switch (payload.type) {
        case 'weather_update':
          this.refreshWeatherWidget(payload.data);
          break;
        case 'news_alert':
          this.showNewsNotification(payload.data);
          break;
      }
    } catch (e) {
      console.error('解析推送数据失败', e);
    }
  }

  private refreshWeatherWidget(data: unknown) {
    // 刷新天气卡片
    console.info('刷新天气数据:', data);
  }

  private showNewsNotification(data: unknown) {
    // 展示新闻通知
    console.info('展示新闻:', data);
  }

  onDestroy(): void {
    PushManager.unregisterPushCallback();
  }
}

1.4 服务端推送示例

服务端通过HTTPS调用Push Kit REST API发送消息:

# 获取访问令牌(OAuth 2.0)
curl -X POST "https://oauth-login.cloud.huawei.com/oauth2/v3/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

# 发送推送消息
curl -X POST "https://push-api.cloud.huawei.com/v1/YOUR_APP_ID/messages:send" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "token": "DEVICE_PUSH_TOKEN",
      "notification": {
        "title": "天气提醒",
        "body": "北京下午有雨,记得带伞"
      },
      "android": {
        "notification": {
          "click_action": {
            "type": 1,
            "intent": "#Intent;action=ohos.want.action.view;component=your.bundle.name/EntryAbility;end"
          }
        }
      }
    }
  }'

实际项目中,建议使用华为提供的服务端SDK简化调用。


二、后台任务管理:Work Scheduler

2.1 后台执行的三种方式

HarmonyOS NEXT严格限制了应用的后台行为,提供了三种标准化的后台执行方式:

方式

能力

触发条件

执行时长限制

Work Scheduler

延迟任务、定时任务

网络状态、充电状态、时间条件

单次最长2分钟

Background Task

短时后台任务

应用退到后台时申请

最长3分钟

Alarm

精确闹钟

指定时间点触发

即时执行

对于大多数场景,Work Scheduler是首选。它由系统统一调度,在满足条件时批量执行多个应用的任务,降低整体功耗。

2.2 Work Scheduler 实战

场景:每天早上8点自动刷新天气数据,但只在Wi-Fi环境下执行以节省流量。

// entry/src/main/ets/utils/WorkSchedulerManager.ets
import { workScheduler } from '@kit.BackgroundTasksKit';

export class WorkSchedulerManager {
  // 注册定时天气刷新任务
  static registerWeatherRefreshTask(): void {
    const workInfo: workScheduler.WorkInfo = {
      workId: 1001, // 任务唯一ID
      bundleName: 'com.example.weatherapp',
      abilityName: 'WorkAbility',
      networkType: workScheduler.NetworkType.NETWORK_TYPE_WIFI, // 仅在Wi-Fi下执行
      chargerType: workScheduler.ChargingType.CHARGING_PLUGGED_ANY, // 充电时也可执行
      batteryLevel: 20, // 电量不低于20%
      storageRequest: workScheduler.StorageRequest.STORAGE_LEVEL_OKAY, // 存储空间充足
      repeatCycleTime: 24 * 60 * 60 * 1000, // 每24小时执行一次
      repeatCount: workScheduler.REPEAT_INFINITE // 无限重复
    };

    try {
      workScheduler.startWork(workInfo);
      console.info('天气刷新任务已注册');
    } catch (err) {
      console.error('注册任务失败', err);
    }
  }

  // 取消任务
  static cancelWeatherRefreshTask(): void {
    const workInfo: workScheduler.WorkInfo = {
      workId: 1001,
      bundleName: 'com.example.weatherapp',
      abilityName: 'WorkAbility'
    };

    try {
      workScheduler.stopWork(workInfo);
      console.info('天气刷新任务已取消');
    } catch (err) {
      console.error('取消任务失败', err);
    }
  }

  // 查询所有已注册任务
  static async getAllWorks(): Promise<workScheduler.WorkInfo[]> {
    try {
      return await workScheduler.getAllWorks();
    } catch (err) {
      console.error('查询任务失败', err);
      return [];
    }
  }
}

WorkAbility 实现:

// entry/src/main/ets/workability/WorkAbility.ets
import { WorkSchedulerExtensionAbility } from '@kit.BackgroundTasksKit';
import { http } from '@kit.NetworkKit';

export default class WorkAbility extends WorkSchedulerExtensionAbility {
  // 任务触发时执行
  async onWorkStart(workInfo: workScheduler.WorkInfo): Promise<void> {
    console.info(`任务 ${workInfo.workId} 开始执行`);

    try {
      // 调用天气API获取最新数据
      const weatherData = await this.fetchWeatherData();

      // 更新本地缓存
      await this.updateLocalCache(weatherData);

      // 更新桌面卡片
      await this.updateWidget(weatherData);

      console.info('天气数据刷新完成');
    } catch (err) {
      console.error('天气刷新失败', err);
    }
  }

  private async fetchWeatherData(): Promise<unknown> {
    // 实际项目中调用真实天气API
    return {
      city: '北京',
      temp: 24,
      condition: '晴',
      updateTime: new Date().toISOString()
    };
  }

  private async updateLocalCache(data: unknown): Promise<void> {
    // 写入Preferences或RDB
    console.info('更新本地缓存:', data);
  }

  private async updateWidget(data: unknown): Promise<void> {
    // 调用FormBindingData更新卡片
    console.info('更新桌面卡片:', data);
  }

  onWorkStop(workInfo: workScheduler.WorkInfo): void {
    console.info(`任务 ${workInfo.workId} 已停止`);
  }
}

2.3 Work Scheduler 的约束与最佳实践

约束项

限制

应对策略

单次执行时长

最长2分钟

任务设计为幂等、可中断,超时自动终止

最短间隔

20分钟

需要更频繁更新的场景考虑前台服务或Push触发

同时注册数

最多10个

合并同类任务,避免过度拆分

设备休眠

可能延迟执行

对时效性要求高的任务使用Alarm

最佳实践:

  1. 任务幂等设计:Work Scheduler不保证任务只执行一次,网络波动可能导致重复触发

    // 每次执行前检查时间戳,避免重复刷新
    async onWorkStart(workInfo: workScheduler.WorkInfo): Promise<void> {
      const lastUpdate = await this.getLastUpdateTime();
      if (Date.now() - lastUpdate < 23 * 60 * 60 * 1000) {
        console.info('距离上次更新不足23小时,跳过');
        return;
      }
      // 执行刷新...
    }
    
  2. 失败重试策略:Work Scheduler本身不提供重试,需要自行实现

    private async fetchWithRetry(url: string, maxRetries: number = 3): Promise<Response> {
      for (let i = 0; i < maxRetries; i++) {
        try {
          return await http.createHttp().request(url);
        } catch (err) {
          if (i === maxRetries - 1) throw err;
          await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))); // 指数退避
        }
      }
      throw new Error('请求失败');
    }
    
  3. 批量操作:在2分钟窗口内尽量完成更多工作

    async onWorkStart(): Promise<void> {
      // 同时执行多个独立的刷新任务
      await Promise.all([
        this.refreshWeather(),
        this.refreshNews(),
        this.cleanOldCache()
      ]);
    }
    

三、推送 + 后台任务的组合拳

3.1 场景设计:智能天气服务

将Push Kit和Work Scheduler组合,可以构建一个体验优秀的天气服务:

场景1:定时刷新(Work Scheduler)
├── 每天早上8点,Wi-Fi环境下自动刷新天气
├── 更新本地缓存 + 桌面卡片
└── 无需用户干预,卡片始终展示最新数据

场景2:恶劣天气预警(Push Kit)
├── 服务端监测到暴雨/大风/高温预警
├── 立即推送通知消息到设备
├── 用户点击通知进入详情页
└── 同时携带透传数据,静默更新卡片

场景3:用户手动刷新(前台交互)
├── 用户打开应用,下拉刷新
├── 前台请求最新数据
├── 更新UI + 缓存 + 卡片
└── 重置Work Scheduler的定时器

3.2 代码整合

// entry/src/main/ets/utils/WeatherService.ets
import { PushManager } from './PushManager';
import { WorkSchedulerManager } from './WorkSchedulerManager';

export class WeatherService {
  // 初始化所有天气相关服务
  static async initialize(): Promise<void> {
    // 1. 获取并上报Push Token
    const token = await PushManager.getToken();
    await this.reportToken(token);

    // 2. 注册透传消息监听
    PushManager.registerPushCallback((data) => {
      this.handleServerPush(data);
    });

    // 3. 注册定时刷新任务
    WorkSchedulerManager.registerWeatherRefreshTask();

    console.info('天气服务初始化完成');
  }

  // 用户手动刷新
  static async manualRefresh(): Promise<void> {
    const data = await this.fetchWeatherFromServer();
    await this.updateAll(data);
  }

  // 服务端推送处理
  private static async handleServerPush(data: string): Promise<void> {
    const payload = JSON.parse(data);
    if (payload.type === 'weather_alert') {
      await this.updateAll(payload.data);
    }
  }

  // 定时任务触发刷新
  static async scheduledRefresh(): Promise<void> {
    const data = await this.fetchWeatherFromServer();
    await this.updateAll(data);
  }

  private static async updateAll(data: unknown): Promise<void> {
    await Promise.all([
      this.updateLocalCache(data),
      this.updateWidget(data)
    ]);
  }

  private static async reportToken(token: string): Promise<void> {
    // 上报服务端
  }

  private static async fetchWeatherFromServer(): Promise<unknown> {
    // 网络请求
    return {};
  }

  private static async updateLocalCache(data: unknown): Promise<void> {
    // 更新缓存
  }

  private static async updateWidget(data: unknown): Promise<void> {
    // 更新卡片
  }
}

四、常见问题排查

问题

排查方向

解法

推送收不到

Token是否有效

检查Token是否过期,重新获取并上报

服务端API调用

确认access_token有效,请求参数正确

网络问题

设备是否能访问Push Kit服务器

权限

是否申请了RECEIVE_NOTIFICATION权限

Work Scheduler不触发

条件不满足

检查网络、电量、充电状态是否符合

任务冲突

同一workId只能存在一个

系统限制

系统可能因功耗策略延迟执行

后台任务被杀

超时

Work Scheduler单次最长2分钟

内存紧张

系统可能优先回收后台进程


五、小结与下篇预告

本文打通了两条关键链路:

  1. Push Kit推送:系统级统一推送通道,支持通知消息和透传消息,覆盖手机、平板、车机等全设备

  2. Work Scheduler后台任务:延迟/定时任务的标准化调度,在系统约束下实现可靠的后台执行

  3. 组合策略:推送负责即时触达,后台任务负责定时维护,两者结合覆盖绝大多数场景

HarmonyOS对后台的严格管控,本质上是一种"优雅约束"——它迫使开发者放弃"后台常驻"的坏习惯,转而用系统提供的标准化机制解决问题。结果是功耗更低、体验更一致。

接下来,我们将进入更具体的领域能力开发。音视频是现代应用的高频场景,但相机预览、录音、播放、编解码的复杂度也很高。第14篇将深入鸿蒙音视频开发实战,从AVPlayer音频播放到Camera Kit相机采集,再到媒体会话管理和后台播放,帮你构建完整的音视频技术栈。


参考阅读


评论