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
注册守护定时器
守护进程的策略
守护进程的核心思路就一个:被杀了就重启。具体实现有三种方式:
- SA自愈:samgr检测到SA进程异常退出后自动重启。这是最正规的方式。
- 定时心跳:后台定时器定期检查服务状态,发现服务不在就拉起。
- 事件触发:监听系统事件(如屏幕亮灭、网络变化),在事件回调中检查服务状态。
#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不是免死金牌。系统在极端内存压力下仍然会回收后台任务。而且如果你的长时任务没有实际工作(比如只是空转),系统检测到后会主动回收。
正确做法:
- 长时任务必须有实际工作(数据传输、位置追踪、音乐播放等)
- 配合心跳监控,被杀后自动重启
- 使用
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在系统广播和守护方面有几个变化:
-
静态订阅增强 :
StaticSubscriberExtension新增onReceiveEvent回调,可以直接在回调中启动Ability或Service,不再需要通过wantAgent中转。 -
事件分类更细 :新增
COMMON_EVENT_DEVICE_IDLE_MODE_CHANGED(省电模式变化)、COMMON_EVENT_CHARGE_TYPE_CHANGED(充电类型变化)等事件,覆盖更多设备状态。 -
后台任务管理更严格 :
ContinuousTask的申请需要声明具体的bgMode,系统会校验实际行为是否匹配。空挂长时任务会被检测并回收。 -
WorkScheduler增强 :新增
WorkScheduler的约束条件,可以设置"仅在充电时执行"、"仅在WiFi下执行"等条件,减少不必要的唤醒。
适配建议:开机启动改用静态订阅,长时任务确保有实际工作,配合WorkScheduler做定时任务减少资源占用。
总结
系统级事件监听和守护进程是系统应用"活着"的基础。收不到开机事件就启动不了,没有守护进程就活不长。
核心要点回顾:
- 开机启动用静态订阅,不要用动态订阅
- 守护进程三种策略:SA自愈、定时心跳、事件触发
- 长时任务必须有实际工作,空挂会被回收
- 守护重启要加退避策略和次数限制,防止无限循环
- 事件订阅要注意泄漏,组件销毁时取消订阅
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐ 事件机制不难,但守护和保活策略需要经验 |
| 使用频率 | ⭐⭐⭐⭐ 系统应用几乎都需要开机启动和后台保活 |
| 重要程度 | ⭐⭐⭐⭐⭐ 直接决定系统服务是否稳定运行 |