HarmonyOS开发:系统广播与守护——系统级事件监听

HarmonyOS开发:系统广播与守护------系统级事件监听

📌 核心要点:系统级CommonEvent是鸿蒙进程间通信的"大喇叭",掌握开机启动、守护进程和事件优先级,才能让你的系统服务稳稳当当跑起来。

背景与动机

你的系统应用需要在开机后自动启动,怎么办?你的后台服务被系统杀了,怎么让它重新活过来?设备插入充电器时你要做特殊处理,怎么监听这个事件?

这些场景都离不开系统级事件------也就是CommonEvent。

普通应用也能收发CommonEvent,但系统级CommonEvent有它的特殊性:有些事件只有系统签名应用才能收到,有些事件需要特定的权限,有些事件的时序非常关键------比如开机启动事件,你错过了就没了。

更麻烦的是守护进程。你的系统服务必须7×24小时运行,但系统内存紧张时会杀后台进程。怎么保证你的服务被杀后能自动重启?怎么防止被系统回收?

这篇就来讲清楚系统级事件监听、开机启动、守护进程和系统服务保活的完整方案。

核心原理

CommonEvent的分类

鸿蒙的CommonEvent分两大类:

类型 发送方式 接收限制 典型事件
标准事件 publish 无限制,所有订阅者都能收到 充电状态变化、网络变化
有序事件 publish with isOrdered 按优先级依次传递,可拦截 短信接收、来电

系统级事件特指那些需要系统签名或特定权限才能订阅的事件:

事件 权限要求 说明
COMMON_EVENT_BOOT_COMPLETED 无(系统签名应用才能订阅) 开机完成
COMMON_EVENT_SHUTDOWN 关机
COMMON_EVENT_PACKAGE_ADDED 应用安装
COMMON_EVENT_PACKAGE_REMOVED 应用卸载
COMMON_EVENT_BATTERY_CHANGED 电量变化
COMMON_EVENT_WIFI_CONNECTION_STATE WiFi连接状态
COMMON_EVENT_USER_SWITCHED ohos.permission.MANAGE_LOCAL_ACCOUNTS 用户切换

开机启动的流程

#mermaid-svg-XEXIpHqU23KKreh2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XEXIpHqU23KKreh2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XEXIpHqU23KKreh2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XEXIpHqU23KKreh2 .error-icon{fill:#552222;}#mermaid-svg-XEXIpHqU23KKreh2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XEXIpHqU23KKreh2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XEXIpHqU23KKreh2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XEXIpHqU23KKreh2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XEXIpHqU23KKreh2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XEXIpHqU23KKreh2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XEXIpHqU23KKreh2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XEXIpHqU23KKreh2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XEXIpHqU23KKreh2 .marker.cross{stroke:#333333;}#mermaid-svg-XEXIpHqU23KKreh2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XEXIpHqU23KKreh2 p{margin:0;}#mermaid-svg-XEXIpHqU23KKreh2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XEXIpHqU23KKreh2 .cluster-label text{fill:#333;}#mermaid-svg-XEXIpHqU23KKreh2 .cluster-label span{color:#333;}#mermaid-svg-XEXIpHqU23KKreh2 .cluster-label span p{background-color:transparent;}#mermaid-svg-XEXIpHqU23KKreh2 .label text,#mermaid-svg-XEXIpHqU23KKreh2 span{fill:#333;color:#333;}#mermaid-svg-XEXIpHqU23KKreh2 .node rect,#mermaid-svg-XEXIpHqU23KKreh2 .node circle,#mermaid-svg-XEXIpHqU23KKreh2 .node ellipse,#mermaid-svg-XEXIpHqU23KKreh2 .node polygon,#mermaid-svg-XEXIpHqU23KKreh2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XEXIpHqU23KKreh2 .rough-node .label text,#mermaid-svg-XEXIpHqU23KKreh2 .node .label text,#mermaid-svg-XEXIpHqU23KKreh2 .image-shape .label,#mermaid-svg-XEXIpHqU23KKreh2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-XEXIpHqU23KKreh2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XEXIpHqU23KKreh2 .rough-node .label,#mermaid-svg-XEXIpHqU23KKreh2 .node .label,#mermaid-svg-XEXIpHqU23KKreh2 .image-shape .label,#mermaid-svg-XEXIpHqU23KKreh2 .icon-shape .label{text-align:center;}#mermaid-svg-XEXIpHqU23KKreh2 .node.clickable{cursor:pointer;}#mermaid-svg-XEXIpHqU23KKreh2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XEXIpHqU23KKreh2 .arrowheadPath{fill:#333333;}#mermaid-svg-XEXIpHqU23KKreh2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XEXIpHqU23KKreh2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XEXIpHqU23KKreh2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XEXIpHqU23KKreh2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XEXIpHqU23KKreh2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XEXIpHqU23KKreh2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XEXIpHqU23KKreh2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XEXIpHqU23KKreh2 .cluster text{fill:#333;}#mermaid-svg-XEXIpHqU23KKreh2 .cluster span{color:#333;}#mermaid-svg-XEXIpHqU23KKreh2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XEXIpHqU23KKreh2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XEXIpHqU23KKreh2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-XEXIpHqU23KKreh2 .icon-shape,#mermaid-svg-XEXIpHqU23KKreh2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XEXIpHqU23KKreh2 .icon-shape p,#mermaid-svg-XEXIpHqU23KKreh2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XEXIpHqU23KKreh2 .icon-shape .label rect,#mermaid-svg-XEXIpHqU23KKreh2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XEXIpHqU23KKreh2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XEXIpHqU23KKreh2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XEXIpHqU23KKreh2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-XEXIpHqU23KKreh2 .boot>*{fill:#fce4ec!important;stroke:#f44336!important;color:#b71c1c!important;}#mermaid-svg-XEXIpHqU23KKreh2 .boot span{fill:#fce4ec!important;stroke:#f44336!important;color:#b71c1c!important;}#mermaid-svg-XEXIpHqU23KKreh2 .boot tspan{fill:#b71c1c!important;}#mermaid-svg-XEXIpHqU23KKreh2 .system>*{fill:#fff3e0!important;stroke:#ff9800!important;color:#e65100!important;}#mermaid-svg-XEXIpHqU23KKreh2 .system span{fill:#fff3e0!important;stroke:#ff9800!important;color:#e65100!important;}#mermaid-svg-XEXIpHqU23KKreh2 .system tspan{fill:#e65100!important;}#mermaid-svg-XEXIpHqU23KKreh2 .app>*{fill:#e8f5e9!important;stroke:#4caf50!important;color:#1b5e20!important;}#mermaid-svg-XEXIpHqU23KKreh2 .app span{fill:#e8f5e9!important;stroke:#4caf50!important;color:#1b5e20!important;}#mermaid-svg-XEXIpHqU23KKreh2 .app tspan{fill:#1b5e20!important;}#mermaid-svg-XEXIpHqU23KKreh2 .event>*{fill:#e3f2fd!important;stroke:#2196f3!important;color:#0d47a1!important;}#mermaid-svg-XEXIpHqU23KKreh2 .event span{fill:#e3f2fd!important;stroke:#2196f3!important;color:#0d47a1!important;}#mermaid-svg-XEXIpHqU23KKreh2 .event tspan{fill:#0d47a1!important;} 内核启动
init进程
samgr启动
SystemServer启动
核心SA启动
Launcher启动
SystemUI启动
发出BOOT_COMPLETED事件
系统签名应用收到事件
启动后台ServiceExtension
注册守护定时器

守护进程的策略

守护进程的核心思路就一个:被杀了就重启。具体实现有三种方式:

  1. SA自愈:samgr检测到SA进程异常退出后自动重启。这是最正规的方式。
  2. 定时心跳:后台定时器定期检查服务状态,发现服务不在就拉起。
  3. 事件触发:监听系统事件(如屏幕亮灭、网络变化),在事件回调中检查服务状态。

#mermaid-svg-1V1KEmNg8u1ShBjq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1V1KEmNg8u1ShBjq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1V1KEmNg8u1ShBjq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1V1KEmNg8u1ShBjq .error-icon{fill:#552222;}#mermaid-svg-1V1KEmNg8u1ShBjq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1V1KEmNg8u1ShBjq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1V1KEmNg8u1ShBjq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1V1KEmNg8u1ShBjq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1V1KEmNg8u1ShBjq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1V1KEmNg8u1ShBjq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1V1KEmNg8u1ShBjq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1V1KEmNg8u1ShBjq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1V1KEmNg8u1ShBjq .marker.cross{stroke:#333333;}#mermaid-svg-1V1KEmNg8u1ShBjq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1V1KEmNg8u1ShBjq p{margin:0;}#mermaid-svg-1V1KEmNg8u1ShBjq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1V1KEmNg8u1ShBjq .cluster-label text{fill:#333;}#mermaid-svg-1V1KEmNg8u1ShBjq .cluster-label span{color:#333;}#mermaid-svg-1V1KEmNg8u1ShBjq .cluster-label span p{background-color:transparent;}#mermaid-svg-1V1KEmNg8u1ShBjq .label text,#mermaid-svg-1V1KEmNg8u1ShBjq span{fill:#333;color:#333;}#mermaid-svg-1V1KEmNg8u1ShBjq .node rect,#mermaid-svg-1V1KEmNg8u1ShBjq .node circle,#mermaid-svg-1V1KEmNg8u1ShBjq .node ellipse,#mermaid-svg-1V1KEmNg8u1ShBjq .node polygon,#mermaid-svg-1V1KEmNg8u1ShBjq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1V1KEmNg8u1ShBjq .rough-node .label text,#mermaid-svg-1V1KEmNg8u1ShBjq .node .label text,#mermaid-svg-1V1KEmNg8u1ShBjq .image-shape .label,#mermaid-svg-1V1KEmNg8u1ShBjq .icon-shape .label{text-anchor:middle;}#mermaid-svg-1V1KEmNg8u1ShBjq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1V1KEmNg8u1ShBjq .rough-node .label,#mermaid-svg-1V1KEmNg8u1ShBjq .node .label,#mermaid-svg-1V1KEmNg8u1ShBjq .image-shape .label,#mermaid-svg-1V1KEmNg8u1ShBjq .icon-shape .label{text-align:center;}#mermaid-svg-1V1KEmNg8u1ShBjq .node.clickable{cursor:pointer;}#mermaid-svg-1V1KEmNg8u1ShBjq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1V1KEmNg8u1ShBjq .arrowheadPath{fill:#333333;}#mermaid-svg-1V1KEmNg8u1ShBjq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1V1KEmNg8u1ShBjq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1V1KEmNg8u1ShBjq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1V1KEmNg8u1ShBjq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1V1KEmNg8u1ShBjq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1V1KEmNg8u1ShBjq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1V1KEmNg8u1ShBjq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1V1KEmNg8u1ShBjq .cluster text{fill:#333;}#mermaid-svg-1V1KEmNg8u1ShBjq .cluster span{color:#333;}#mermaid-svg-1V1KEmNg8u1ShBjq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1V1KEmNg8u1ShBjq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1V1KEmNg8u1ShBjq rect.text{fill:none;stroke-width:0;}#mermaid-svg-1V1KEmNg8u1ShBjq .icon-shape,#mermaid-svg-1V1KEmNg8u1ShBjq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1V1KEmNg8u1ShBjq .icon-shape p,#mermaid-svg-1V1KEmNg8u1ShBjq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1V1KEmNg8u1ShBjq .icon-shape .label rect,#mermaid-svg-1V1KEmNg8u1ShBjq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1V1KEmNg8u1ShBjq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1V1KEmNg8u1ShBjq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1V1KEmNg8u1ShBjq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-1V1KEmNg8u1ShBjq .problem>*{fill:#fce4ec!important;stroke:#f44336!important;color:#b71c1c!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .problem span{fill:#fce4ec!important;stroke:#f44336!important;color:#b71c1c!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .problem tspan{fill:#b71c1c!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .strategy>*{fill:#e3f2fd!important;stroke:#2196f3!important;color:#0d47a1!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .strategy span{fill:#e3f2fd!important;stroke:#2196f3!important;color:#0d47a1!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .strategy tspan{fill:#0d47a1!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .check>*{fill:#fff3e0!important;stroke:#ff9800!important;color:#e65100!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .check span{fill:#fff3e0!important;stroke:#ff9800!important;color:#e65100!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .check tspan{fill:#e65100!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .solution>*{fill:#e8f5e9!important;stroke:#4caf50!important;color:#1b5e20!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .solution span{fill:#e8f5e9!important;stroke:#4caf50!important;color:#1b5e20!important;}#mermaid-svg-1V1KEmNg8u1ShBjq .solution tspan{fill:#1b5e20!important;} SA自愈
定时心跳


事件触发


服务被杀
守护策略
samgr检测进程退出
自动重启SA进程
SA OnStart重新执行
后台定时器触发
服务是否存活?
拉起服务
继续监控
系统事件到达
服务是否存活?
在事件回调中拉起
正常处理事件

代码实战

基础用法:系统级CommonEvent订阅

typescript 复制代码
// common/SystemEventReceiver.ets - 系统事件接收器
import CommonEvent from '@ohos.commonEventManager';
import { BusinessError } from '@ohos.base';

// 支持订阅的系统事件
const SYSTEM_EVENTS = {
  BOOT_COMPLETED: 'usual.event.BOOT_COMPLETED',
  SHUTDOWN: 'usual.event.SHUTDOWN',
  PACKAGE_ADDED: 'usual.event.PACKAGE_ADDED',
  PACKAGE_REMOVED: 'usual.event.PACKAGE_REMOVED',
  BATTERY_CHANGED: 'usual.event.BATTERY_CHANGED',
  SCREEN_ON: 'usual.event.SCREEN_ON',
  SCREEN_OFF: 'usual.event.SCREEN_OFF',
  WIFI_CONNECT: 'usual.event.WIFI_CONN_STATE',
};

type EventCallback = (event: string, data: string) => void;

class SystemEventReceiver {
  private tag: string = 'SystemEventReceiver';
  private subscriber: CommonEvent.CommonEventSubscriber | null = null;
  private callbacks: Map<string, EventCallback[]> = new Map();

  // 创建订阅者
  async subscribe(): Promise<boolean> {
    try {
      const subscribeInfo: CommonEvent.CommonEventSubscribeInfo = {
        events: [
          SYSTEM_EVENTS.BOOT_COMPLETED,
          SYSTEM_EVENTS.PACKAGE_ADDED,
          SYSTEM_EVENTS.PACKAGE_REMOVED,
          SYSTEM_EVENTS.BATTERY_CHANGED,
          SYSTEM_EVENTS.SCREEN_ON,
          SYSTEM_EVENTS.SCREEN_OFF,
        ],
        // 关键:设置优先级(数值越大优先级越高)
        priority: 100,
      };

      this.subscriber = CommonEvent.createSubscriber(subscribeInfo);

      // 订阅事件
      CommonEvent.subscribe(this.subscriber, (err, data) => {
        if (err) {
          console.error(this.tag, `收到事件错误: ${JSON.stringify(err)}`);
          return;
        }
        this.handleEvent(data.event, data.data || '');
      });

      console.info(this.tag, '系统事件订阅成功');
      return true;
    } catch (err) {
      const error = err as BusinessError;
      console.error(this.tag, `订阅失败: ${error.message}`);
      return false;
    }
  }

  // 取消订阅
  async unsubscribe(): Promise<void> {
    if (this.subscriber) {
      try {
        CommonEvent.unsubscribe(this.subscriber);
        this.subscriber = null;
        console.info(this.tag, '取消订阅成功');
      } catch (err) {
        console.error(this.tag, `取消订阅失败: ${JSON.stringify(err)}`);
      }
    }
  }

  // 注册事件回调
  on(event: string, callback: EventCallback): void {
    const callbacks = this.callbacks.get(event) || [];
    callbacks.push(callback);
    this.callbacks.set(event, callbacks);
  }

  // 处理收到的事件
  private handleEvent(event: string, data: string): void {
    console.info(this.tag, `收到事件: ${event}, data: ${data}`);
    const callbacks = this.callbacks.get(event) || [];
    callbacks.forEach(cb => {
      try {
        cb(event, data);
      } catch (err) {
        console.error(this.tag, `事件回调执行失败: ${JSON.stringify(err)}`);
      }
    });
  }
}

export default new SystemEventReceiver();

进阶用法:开机启动与守护进程

typescript 复制代码
// common/DaemonManager.ets - 守护进程管理
import CommonEvent from '@ohos.commonEventManager';
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
import { BusinessError } from '@ohos.base';

const BOOT_COMPLETED_EVENT = 'usual.event.BOOT_COMPLETED';

class DaemonManager {
  private tag: string = 'DaemonManager';
  private isRunning: boolean = false;
  private heartbeatInterval: number = 30000; // 心跳间隔30秒
  private heartbeatTimer: number = -1;

  // 开机自启动入口
  async onBootCompleted(): Promise<void> {
    console.info(this.tag, '收到开机完成事件,启动守护服务');
    
    // 1. 启动核心服务
    await this.startCoreService();
    
    // 2. 申请长时任务保活
    await this.requestContinuousTask();
    
    // 3. 启动心跳监控
    this.startHeartbeat();
    
    this.isRunning = true;
  }

  // 启动核心服务
  private async startCoreService(): Promise<void> {
    try {
      // 这里启动你的核心ServiceExtension
      console.info(this.tag, '核心服务启动成功');
    } catch (err) {
      console.error(this.tag, `核心服务启动失败: ${JSON.stringify(err)}`);
    }
  }

  // 申请长时任务(后台保活)
  async requestContinuousTask(): Promise<boolean> {
    try {
      const request: backgroundTaskManager.ContinuousTaskParam = {
        bgMode: backgroundTaskManager.BackgroundMode.DATA_TRANSFER,
        wantAgent: undefined, // 实际使用时需要传入wantAgent
      };
      
      await backgroundTaskManager.startBackgroundRunning(
        globalThis.context,
        backgroundTaskManager.BackgroundMode.DATA_TRANSFER,
        '守护服务运行中'
      );
      
      console.info(this.tag, '长时任务申请成功');
      return true;
    } catch (err) {
      const error = err as BusinessError;
      console.error(this.tag, `长时任务申请失败: ${error.message}`);
      return false;
    }
  }

  // 停止长时任务
  async stopContinuousTask(): Promise<void> {
    try {
      await backgroundTaskManager.stopBackgroundRunning(globalThis.context);
      console.info(this.tag, '长时任务已停止');
    } catch (err) {
      console.error(this.tag, `停止长时任务失败: ${JSON.stringify(err)}`);
    }
  }

  // 启动心跳监控
  private startHeartbeat(): void {
    this.stopHeartbeat(); // 先停掉旧的
    
    this.heartbeatTimer = setInterval(() => {
      this.checkServiceHealth();
    }, this.heartbeatInterval) as unknown as number;
    
    console.info(this.tag, '心跳监控已启动');
  }

  // 停止心跳监控
  private stopHeartbeat(): void {
    if (this.heartbeatTimer !== -1) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = -1;
    }
  }

  // 检查服务健康状态
  private async checkServiceHealth(): Promise<void> {
    try {
      // 检查核心服务是否存活
      const isAlive = await this.pingCoreService();
      if (!isAlive) {
        console.warn(this.tag, '核心服务不存活,尝试重启');
        await this.startCoreService();
      }
    } catch (err) {
      console.error(this.tag, `健康检查失败: ${JSON.stringify(err)}`);
    }
  }

  // 检测核心服务是否存活
  private async pingCoreService(): Promise<boolean> {
    // 实际实现:通过IPC ping核心SA
    return this.isRunning;
  }

  // 停止守护
  async stopDaemon(): Promise<void> {
    this.stopHeartbeat();
    await this.stopContinuousTask();
    this.isRunning = false;
    console.info(this.tag, '守护服务已停止');
  }
}

export default new DaemonManager();

完整示例:系统事件监控面板

typescript 复制代码
// pages/SystemEventPage.ets - 系统事件监控面板
import SystemEventReceiver, { SYSTEM_EVENTS } from '../common/SystemEventReceiver';
import DaemonManager from '../common/DaemonManager';
import promptAction from '@ohos.promptAction';

interface EventLog {
  event: string;
  data: string;
  time: string;
}

@Entry
@Component
struct SystemEventPage {
  @State eventLogs: EventLog[] = [];
  @State isSubscribed: boolean = false;
  @State isDaemonRunning: boolean = false;
  @State eventCount: number = 0;

  aboutToAppear() {
    this.setupEventListeners();
  }

  aboutToDisappear() {
    if (this.isSubscribed) {
      SystemEventReceiver.unsubscribe();
    }
  }

  // 设置事件监听
  setupEventListeners() {
    // 监听所有系统事件
    Object.values(SYSTEM_EVENTS).forEach(event => {
      SystemEventReceiver.on(event, (eventName: string, data: string) => {
        this.addLog(eventName, data);
      });
    });
  }

  // 添加日志
  addLog(event: string, data: string) {
    const now = new Date();
    const time = `${this.padZero(now.getHours())}:${this.padZero(now.getMinutes())}:${this.padZero(now.getSeconds())}`;
    
    this.eventLogs.unshift({ event, data, time });
    this.eventCount++;
    
    // 最多保留100条日志
    if (this.eventLogs.length > 100) {
      this.eventLogs = this.eventLogs.slice(0, 100);
    }
  }

  padZero(num: number): string {
    return num < 10 ? `0${num}` : `${num}`;
  }

  // 获取事件名称的简短描述
  getEventShortName(event: string): string {
    const nameMap: Record<string, string> = {
      [SYSTEM_EVENTS.BOOT_COMPLETED]: '开机完成',
      [SYSTEM_EVENTS.SHUTDOWN]: '关机',
      [SYSTEM_EVENTS.PACKAGE_ADDED]: '应用安装',
      [SYSTEM_EVENTS.PACKAGE_REMOVED]: '应用卸载',
      [SYSTEM_EVENTS.BATTERY_CHANGED]: '电量变化',
      [SYSTEM_EVENTS.SCREEN_ON]: '屏幕亮起',
      [SYSTEM_EVENTS.SCREEN_OFF]: '屏幕熄灭',
      [SYSTEM_EVENTS.WIFI_CONNECT]: 'WiFi状态',
    };
    return nameMap[event] || event;
  }

  build() {
    Column() {
      // 控制面板
      Row() {
        Column() {
          Text('事件监控')
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
          Text(`已收到 ${this.eventCount} 个事件`)
            .fontSize(12)
            .fontColor('#999999')
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)

        // 订阅开关
        Button(this.isSubscribed ? '已订阅' : '订阅事件')
          .height(36)
          .fontSize(13)
          .backgroundColor(this.isSubscribed ? '#4caf50' : '#1976d2')
          .borderRadius(18)
          .onClick(async () => {
            if (this.isSubscribed) {
              await SystemEventReceiver.unsubscribe();
              this.isSubscribed = false;
            } else {
              const success = await SystemEventReceiver.subscribe();
              this.isSubscribed = success;
              if (!success) {
                promptAction.showToast({ message: '订阅失败,请检查权限' });
              }
            }
          })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .margin({ bottom: 12 })

      // 守护进程控制
      Row() {
        Text('守护服务')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)

        Toggle({ type: ToggleType.Switch, isOn: this.isDaemonRunning })
          .onChange(async (isOn: boolean) => {
            if (isOn) {
              await DaemonManager.onBootCompleted();
              this.isDaemonRunning = true;
              promptAction.showToast({ message: '守护服务已启动' });
            } else {
              await DaemonManager.stopDaemon();
              this.isDaemonRunning = false;
              promptAction.showToast({ message: '守护服务已停止' });
            }
          })
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .margin({ bottom: 12 })

      // 事件日志列表
      Column() {
        Row() {
          Text('事件日志')
            .fontSize(16)
            .fontWeight(FontWeight.Bold)
            .layoutWeight(1)
          Text('清除')
            .fontSize(13)
            .fontColor('#f44336')
            .onClick(() => {
              this.eventLogs = [];
              this.eventCount = 0;
            })
        }
        .width('100%')
        .margin({ bottom: 12 })

        if (this.eventLogs.length === 0) {
          Column() {
            Text('暂无事件日志')
              .fontSize(14)
              .fontColor('#999999')
            Text('请先订阅事件,然后触发系统操作')
              .fontSize(12)
              .fontColor('#cccccc')
              .margin({ top: 4 })
          }
          .width('100%')
          .height(200)
          .justifyContent(FlexAlign.Center)
        } else {
          List() {
            ForEach(this.eventLogs, (log: EventLog, index: number) => {
              ListItem() {
                Row() {
                  Text(log.time)
                    .fontSize(11)
                    .fontColor('#999999')
                    .width(60)
                  Text(this.getEventShortName(log.event))
                    .fontSize(13)
                    .fontColor('#333333')
                    .layoutWeight(1)
                  if (log.data) {
                    Text(log.data)
                      .fontSize(11)
                      .fontColor('#666666')
                      .maxLines(1)
                      .width(100)
                      .textAlign(TextAlign.End)
                  }
                }
                .width('100%')
                .padding({ top: 8, bottom: 8 })
              }
            }, (log: EventLog, index: number) => `${index}`)
          }
          .width('100%')
          .layoutWeight(1)
          .divider({ strokeWidth: 0.5, color: '#f0f0f0' })
        }
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#ffffff')
      .borderRadius(12)
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#f5f5f5')
  }
}

踩坑与注意事项

坑一:BOOT_COMPLETED事件收不到

这是最常见的问题。你订阅了BOOT_COMPLETED事件,但开机后就是收不到。

原因:BOOT_COMPLETED事件是一次性事件,系统只发一次。如果你的应用在事件发出时还没完成订阅,就永远收不到了。

解决办法:在module.json5中声明静态订阅,而不是在代码里动态订阅:

json 复制代码
// 静态订阅配置(module.json5)
{
  "extensionAbilities": [
    {
      "name": "BootCompletedReceiver",
      "type": "staticSubscriber",
      "label": "开机启动",
      "exported": true,
      "metadata": [
        {
          "name": "ohos.extension.staticSubscriber",
          "resource": "$profile:static_subscriber_config"
        }
      ]
    }
  ]
}
json 复制代码
// resources/base/profile/static_subscriber_config.json
{
  "events": [
    "usual.event.BOOT_COMPLETED"
  ]
}

静态订阅在应用安装时就注册了,不需要应用先启动。这样开机事件发出时,系统会自动拉起你的StaticSubscriberExtension

坑二:有序事件的优先级设置无效

你设置了priority: 100,但事件还是先被其他应用收到了。

原因:有序事件的优先级只在同一进程内生效。跨进程的事件分发顺序由系统决定,不保证按优先级来。

如果你需要确保事件处理顺序,用有序事件+abortCommonEvent拦截:

typescript 复制代码
// 发送有序事件
const options: CommonEvent.CommonEventPublishData = {
  isOrdered: true,
};

CommonEvent.publish('my.ordered.event', options, (err) => {
  // 发送完成
});

// 高优先级订阅者可以拦截事件
// 在回调中调用 abortCommonEvent 阻止事件继续传递

坑三:后台长时任务被系统回收

你申请了ContinuousTask,但系统还是把你的后台服务杀了。

原因:ContinuousTask不是免死金牌。系统在极端内存压力下仍然会回收后台任务。而且如果你的长时任务没有实际工作(比如只是空转),系统检测到后会主动回收。

正确做法:

  1. 长时任务必须有实际工作(数据传输、位置追踪、音乐播放等)
  2. 配合心跳监控,被杀后自动重启
  3. 使用WorkScheduler做定时任务,而不是一直占着后台

坑四:事件订阅泄漏

每次调用subscribe都会创建新的订阅者,如果你忘了unsubscribe,就会导致事件回调被多次执行。

解决办法:在组件的aboutToDisappear里取消订阅,或者用单例模式管理订阅者:

typescript 复制代码
// 单例模式确保只有一个订阅者
if (!this.subscriber) {
  this.subscriber = CommonEvent.createSubscriber(subscribeInfo);
  CommonEvent.subscribe(this.subscriber, callback);
}

坑五:守护进程的无限重启循环

服务被杀→守护拉起→服务又崩→守护又拉起→......无限循环,设备CPU飙到100%。

解决办法:加退避策略和重启次数限制:

typescript 复制代码
class DaemonManager {
  private restartCount: number = 0;
  private maxRestartCount: number = 5;
  private restartCooldown: number = 5000; // 5秒冷却

  async restartService(): Promise<void> {
    if (this.restartCount >= this.maxRestartCount) {
      console.error('重启次数超限,停止守护');
      this.stopDaemon();
      return;
    }

    this.restartCount++;
    
    // 指数退避:每次重启等待时间翻倍
    const delay = this.restartCooldown * Math.pow(2, this.restartCount - 1);
    await this.sleep(delay);

    try {
      await this.startCoreService();
      // 启动成功,重置计数器
      this.restartCount = 0;
    } catch (err) {
      console.error(`第${this.restartCount}次重启失败`);
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

HarmonyOS 6适配说明

HarmonyOS 6在系统广播和守护方面有几个变化:

  1. 静态订阅增强StaticSubscriberExtension新增onReceiveEvent回调,可以直接在回调中启动Ability或Service,不再需要通过wantAgent中转。

  2. 事件分类更细 :新增COMMON_EVENT_DEVICE_IDLE_MODE_CHANGED(省电模式变化)、COMMON_EVENT_CHARGE_TYPE_CHANGED(充电类型变化)等事件,覆盖更多设备状态。

  3. 后台任务管理更严格ContinuousTask的申请需要声明具体的bgMode,系统会校验实际行为是否匹配。空挂长时任务会被检测并回收。

  4. WorkScheduler增强 :新增WorkScheduler的约束条件,可以设置"仅在充电时执行"、"仅在WiFi下执行"等条件,减少不必要的唤醒。

适配建议:开机启动改用静态订阅,长时任务确保有实际工作,配合WorkScheduler做定时任务减少资源占用。

总结

系统级事件监听和守护进程是系统应用"活着"的基础。收不到开机事件就启动不了,没有守护进程就活不长。

核心要点回顾:

  • 开机启动用静态订阅,不要用动态订阅
  • 守护进程三种策略:SA自愈、定时心跳、事件触发
  • 长时任务必须有实际工作,空挂会被回收
  • 守护重启要加退避策略和次数限制,防止无限循环
  • 事件订阅要注意泄漏,组件销毁时取消订阅
维度 评价
学习难度 ⭐⭐⭐⭐ 事件机制不难,但守护和保活策略需要经验
使用频率 ⭐⭐⭐⭐ 系统应用几乎都需要开机启动和后台保活
重要程度 ⭐⭐⭐⭐⭐ 直接决定系统服务是否稳定运行