HarmonyOS开发:系统服务开发——SA服务扩展
HarmonyOS开发:系统服务开发——SA服务扩展
📌 核心要点:System Ability(SA)是鸿蒙系统服务的基石,掌握SA的开发、注册、发布和跨进程调用,才能把你的服务真正融入系统。
背景与动机
你有没有想过,鸿蒙系统里那些"天生就在"的服务是怎么来的?WiFi管理、蓝牙控制、电源管理、包管理——它们不是凭空出现的,都是以System Ability(简称SA)的形式运行在系统进程里。
那你自己开发的服务能不能也像它们一样?开机自动启动、其他应用随时调用、系统帮你管理生命周期?当然可以,但前提是你得学会写SA。
普通应用的服务是ServiceExtension,跑在你自己的进程里,系统不关心你什么时候启动什么时候销毁。SA不一样,它跑在系统进程里,由samgr(System Ability Manager)统一管理,有严格的生命周期和注册机制。
做设备定制、做系统中间件、做平台级SDK,你绕不开SA开发。这篇就把SA从开发到注册到调用的全流程讲清楚。
核心原理
SA是什么
System Ability本质上就是一个跑在系统进程里的服务组件。它有几个关键特征:
- 系统级生命周期:由samgr管理,开机按依赖关系启动
- 统一注册发现:所有SA都注册在samgr里,通过SA ID查找
- 跨进程调用:基于IPC/Binder机制,任何进程都能调用
- 按需启动:可以配置为懒加载,有调用时才启动
SA的架构
flowchart TD
A[应用进程] -->|IPC调用| B[samgr 进程]
B -->|查找SA| C[SA 注册表]
C -->|返回SA代理| A
B -->|启动/管理| D[SystemServer 进程]
D --> D1[SA: WiFi服务]
D --> D2[SA: 蓝牙服务]
D --> D3[SA: 自定义SA]
B -->|启动/管理| E[独立SA进程]
E --> E1[SA: 包管理服务]
E --> E2[SA: 电源管理服务]
classDef app fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
classDef samgr fill:#e3f2fd,stroke:#2196f3,color:#0d47a1
classDef registry fill:#fff3e0,stroke:#ff9800,color:#e65100
classDef sysproc fill:#fce4ec,stroke:#f44336,color:#b71c1c
classDef sa fill:#f3e5f5,stroke:#9c27b0,color:#4a148c
class A app
class B,C registry
class D,E sysproc
class D1,D2,D3,E1,E2 sa
SA开发的核心步骤
开发一个SA需要做四件事:
- 定义SA ID:每个SA有唯一标识,在系统头文件中定义
- 实现SA类:继承
SystemAbility,实现生命周期回调 - 注册SA:在
sa_profile中声明SA的ID、名称、进程等信息 - 发布SA:在
OnStart中调用Publish将SA注册到samgr
SA的生命周期
flowchart LR
A[注册到samgr] --> B[OnStart]
B --> C[Publish]
C --> D[运行中]
D -->|系统关机/按需回收| E[OnStop]
E --> F[已销毁]
D -->|OnDump| G[调试信息输出]
classDef lifecycle fill:#e3f2fd,stroke:#2196f3,color:#0d47a1
classDef running fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
classDef debug fill:#fff3e0,stroke:#ff9800,color:#e65100
class A,B,E,F lifecycle
class C,D running
class G debug
代码实战
基础用法:定义和实现一个SA
先看最基础的SA实现。假设你要开发一个"设备信息查询服务"。
第一步:定义SA ID
// system_ability_id.h - SA ID定义
// 系统SA ID范围:0x0001 - 0x8FFF
// 厂商自定义SA ID范围:0x9000 - 0xFFFF
#ifndef SYSTEM_ABILITY_ID_H
#define SYSTEM_ABILITY_ID_H
// 自定义SA ID
static const int32_t DEVICE_INFO_SA_ID = 0x9001;
#endif // SYSTEM_ABILITY_ID_H
第二步:实现SA类
// DeviceInfoAbility.ets - 设备信息SA实现
import { SystemAbility, SystemAbilityStatus } from '@ohos.app.ability.SystemAbility';
import { rpc } from '@ohos.rpc';
import deviceInfo from '@ohos.deviceInfo';
const SA_ID = 0x9001;
const SA_NAME = 'DeviceInfoService';
class DeviceInfoAbility extends SystemAbility {
private tag: string = 'DeviceInfoAbility';
constructor() {
// 传入SA ID和是否独立运行
// 第二个参数true表示在独立进程中运行
super(SA_ID, true);
}
// SA启动回调
OnStart(): void {
console.info(this.tag, 'DeviceInfoSA OnStart');
// 发布SA到samgr
this.Publish(this);
console.info(this.tag, 'DeviceInfoSA Publish成功');
}
// SA停止回调
OnStop(): void {
console.info(this.tag, 'DeviceInfoSA OnStop');
}
// 调试信息回调
OnDump(fd: number, args: Array<string>): void {
console.info(this.tag, 'DeviceInfoSA OnDump');
}
// 处理IPC请求
OnRemoteRequest(code: number, data: rpc.MessageSequence, reply: rpc.MessageSequence,
option: rpc.MessageOption): boolean {
console.info(this.tag, `收到请求, code: ${code}`);
switch (code) {
case 1: // 获取设备型号
reply.writeString(deviceInfo.productModel);
return true;
case 2: // 获取设备类型
reply.writeString(deviceInfo.deviceType);
return true;
case 3: // 获取SDK版本
reply.writeString(deviceInfo.sdkApiVersion.toString());
return true;
default:
console.error(this.tag, `未知请求码: ${code}`);
return false;
}
}
}
export default DeviceInfoAbility;
进阶用法:SA注册与跨进程调用
SA实现好了,还得注册到samgr里,其他进程才能找到它。
SA注册配置文件
// sa_profile/device_info_sa.json - SA注册配置
{
"process": "device_info_service",
"systemability": [
{
"name": 0x9001,
"libpath": "libdevice_info_sa.z.so",
"run-on-create": false,
"distributed": false,
"dump-level": 1,
"start-on-demand": {
"condition": [
{
"name": "device_info_query",
"value": "1"
}
]
},
"stop-on-demand": {
"condition": [
{
"name": "device_info_query",
"value": "0"
}
]
}
}
]
}
配置说明:
process:SA运行的进程名run-on-create:是否开机自动启动,false表示按需启动start-on-demand:按需启动的条件distributed:是否支持分布式部署
跨进程调用SA
// common/SAClient.ets - SA客户端调用工具
import { rpc } from '@ohos.rpc';
import { systemAbilityManager } from '@ohos.systemAbilityManager';
const DEVICE_INFO_SA_ID = 0x9001;
class SAClient {
private tag: string = 'SAClient';
private proxy: rpc.IRemoteObject | null = null;
// 获取SA代理对象
async getSAProxy(): Promise<rpc.IRemoteObject | null> {
if (this.proxy) {
return this.proxy;
}
try {
// 通过SA ID从samgr获取代理
this.proxy = await systemAbilityManager.getSystemAbility(DEVICE_INFO_SA_ID);
console.info(this.tag, '获取SA代理成功');
return this.proxy;
} catch (err) {
console.error(this.tag, `获取SA代理失败: ${JSON.stringify(err)}`);
return null;
}
}
// 查询设备型号
async getDeviceModel(): Promise<string> {
const proxy = await this.getSAProxy();
if (!proxy) {
return '获取失败';
}
try {
const option = new rpc.MessageOption();
const data = new rpc.MessageSequence();
const reply = new rpc.MessageSequence();
// 发送IPC请求,code=1表示获取设备型号
await proxy.sendMessageRequest(1, data, reply, option);
const model = reply.readString();
return model;
} catch (err) {
console.error(this.tag, `查询设备型号失败: ${JSON.stringify(err)}`);
return '查询失败';
}
}
// 查询设备类型
async getDeviceType(): Promise<string> {
const proxy = await this.getSAProxy();
if (!proxy) {
return '获取失败';
}
try {
const option = new rpc.MessageOption();
const data = new rpc.MessageSequence();
const reply = new rpc.MessageSequence();
await proxy.sendMessageRequest(2, data, reply, option);
const type = reply.readString();
return type;
} catch (err) {
console.error(this.tag, `查询设备类型失败: ${JSON.stringify(err)}`);
return '查询失败';
}
}
// 清理代理缓存
clearProxy(): void {
this.proxy = null;
}
}
export default new SAClient();
完整示例:SA服务管理页面
把SA的调用封装成页面,方便调试和验证:
// pages/SAManagerPage.ets - SA服务管理页面
import SAClient from '../common/SAClient';
import { systemAbilityManager } from '@ohos.systemAbilityManager';
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct SAManagerPage {
@State deviceModel: string = '未查询';
@State deviceType: string = '未查询';
@State sdkVersion: string = '未查询';
@State saStatus: string = '未知';
@State isQuerying: boolean = false;
// 检查SA状态
async checkSAStatus() {
try {
const proxy = await systemAbilityManager.getSystemAbility(0x9001);
this.saStatus = proxy ? '运行中' : '未启动';
} catch (err) {
this.saStatus = '未启动';
}
}
aboutToAppear() {
this.checkSAStatus();
}
// 查询所有设备信息
async queryAllInfo() {
this.isQuerying = true;
try {
const model = await SAClient.getDeviceModel();
const type = await SAClient.getDeviceType();
this.deviceModel = model;
this.deviceType = type;
this.saStatus = '运行中';
promptAction.showToast({ message: '查询成功' });
} catch (err) {
promptAction.showToast({ message: '查询失败' });
} finally {
this.isQuerying = false;
}
}
build() {
Scroll() {
Column() {
// SA状态指示
Row() {
Text('SA服务状态')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Badge({
count: 0,
position: BadgePosition.Right,
style: { badgeSize: 8, badgeColor: this.saStatus === '运行中' ? '#4caf50' : '#f44336' }
}) {
Text(this.saStatus)
.fontSize(14)
.fontColor(this.saStatus === '运行中' ? '#4caf50' : '#f44336')
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.margin({ bottom: 24 })
// 设备信息卡片
Column() {
Text('设备信息')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
this.InfoItem('设备型号', this.deviceModel)
this.InfoItem('设备类型', this.deviceType)
this.InfoItem('SDK版本', this.sdkVersion)
}
.width('100%')
.padding(20)
.backgroundColor('#ffffff')
.borderRadius(12)
.margin({ bottom: 16 })
// 操作按钮
Button('查询设备信息')
.width('100%')
.height(48)
.backgroundColor('#1976d2')
.borderRadius(8)
.enabled(!this.isQuerying)
.onClick(() => this.queryAllInfo())
Button('刷新SA状态')
.width('100%')
.height(48)
.backgroundColor('#616161')
.borderRadius(8)
.margin({ top: 12 })
.onClick(() => this.checkSAStatus())
Button('清除代理缓存')
.width('100%')
.height(48)
.backgroundColor('#795548')
.borderRadius(8)
.margin({ top: 12 })
.onClick(() => {
SAClient.clearProxy();
promptAction.showToast({ message: '缓存已清除' });
})
}
.padding(24)
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
@Builder
InfoItem(label: string, value: string) {
Row() {
Text(label)
.fontSize(14)
.fontColor('#999999')
.width(80)
Text(value)
.fontSize(14)
.fontColor('#333333')
.layoutWeight(1)
}
.width('100%')
.margin({ bottom: 12 })
}
}
踩坑与注意事项
坑一:SA ID冲突
SA ID是全局唯一的,如果你自定义的ID跟系统已有的ID冲突了,注册的时候不会报错,但调用的时候会拿到错误的SA代理。
系统SA ID范围是0x0001 - 0x8FFF,厂商自定义范围是0x9000 - 0xFFFF。务必使用厂商范围的ID,不要碰系统范围。
坑二:OnStart里忘记Publish
这是新手最常犯的错误。SA启动了,但没调用Publish,其他进程通过getSystemAbility拿到的就是null。
Publish必须在OnStart里调用,而且要在SA初始化完成之后。如果你的SA需要异步加载配置,等加载完了再Publish:
// 错误:还没初始化完就Publish了
OnStart(): void {
this.Publish(this); // 其他进程拿到代理后调用可能失败
this.loadConfig(); // 配置还没加载完
}
// 正确:初始化完再Publish
async OnStart(): void {
await this.loadConfig();
this.Publish(this); // 确保SA完全就绪
}
坑三:IPC调用超时
跨进程调用不是同步的,有超时机制。默认超时时间是5秒,如果你的SA处理耗时操作,客户端会收到超时错误。
解决办法:
- 用
MessageOption.TF_ASYNC发送异步请求 - 把耗时操作放到工作线程,IPC方法只做分发
- 增加超时时间(不推荐,治标不治本)
坑四:按需启动的SA首次调用慢
配置了run-on-create: false的SA是按需启动的。第一次调用时,samgr要先启动SA进程、初始化SA、Publish,然后才能返回代理。这个过程可能需要几秒。
如果你的场景对响应时间敏感,要么改成开机自启动,要么在应用启动时提前触发一次SA调用,把SA"预热"起来。
坑五:SA进程崩溃后代理失效
SA进程崩溃后,你缓存的代理对象就失效了。再通过这个代理发IPC请求会直接报错。
解决办法:调用失败时清除代理缓存,下次调用重新获取:
async callSA(code: number): Promise<string> {
try {
const proxy = await this.getSAProxy();
if (!proxy) return '获取代理失败';
const option = new rpc.MessageOption();
const data = new rpc.MessageSequence();
const reply = new rpc.MessageSequence();
await proxy.sendMessageRequest(code, data, reply, option);
return reply.readString();
} catch (err) {
// 代理可能失效,清除缓存重试
this.clearProxy();
console.error('SA调用失败,已清除代理缓存');
return '调用失败';
}
}
HarmonyOS 6适配说明
HarmonyOS 6在SA开发方面有几个重要更新:
-
SA动态注册:新增
systemAbilityManager.registerSystemAbility接口,支持运行时动态注册SA,不再需要修改配置文件重新编译。这对插件化架构非常友好。 -
SA依赖声明:SA注册配置新增
depend-on字段,可以声明SA之间的启动依赖关系。samgr会按依赖顺序启动SA,不再需要手动控制启动顺序。
// HarmonyOS 6 SA依赖声明
{
"systemability": [{
"name": 0x9001,
"depend-on": [0x9000], // 依赖0x9000先启动
"run-on-create": true
}]
}
-
SA健康检查:新增
OnHealthCheck回调,samgr会定期检查SA的健康状态。如果SA不健康,samgr会自动重启SA进程。 -
IPC性能优化:Binder驱动优化了跨进程调用的序列化/反序列化性能,大数据量传输场景下性能提升约30%。
适配建议:如果你的SA依赖其他SA,务必使用新的depend-on字段声明依赖,避免启动顺序问题。
总结
SA开发是鸿蒙系统级开发的核心能力。理解SA的生命周期、注册机制、IPC调用模型,你才能把服务真正融入系统。
核心要点回顾:
- SA ID要在厂商范围内定义,避免跟系统冲突
OnStart里必须调用Publish,且在初始化完成之后- 跨进程调用有超时,耗时操作不要放在IPC方法里
- 按需启动的SA首次调用慢,对响应敏感的场景用开机自启
- SA进程崩溃后代理失效,调用失败要清除缓存重试
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐⭐ 涉及IPC、进程管理、生命周期,概念多且抽象 |
| 使用频率 | ⭐⭐⭐ 做系统级服务必用,普通应用开发用不到 |
| 重要程度 | ⭐⭐⭐⭐⭐ 系统服务开发的基石,不会SA就做不了系统中间件 |
- 点赞
- 收藏
- 关注作者
评论(0)