鸿蒙百篇系列第 69 篇 · 专家
第 56 篇讲过 IPC 跨进程通信,那是从"应用间数据共享"的角度。这一篇我们钻进鸿蒙的"系统服务"层(SAMgr,System Ability Manager),看看鸿蒙内部那些全局服务(如 DistributedSchedule、SoftBus、AbilityManager)是怎么注册、怎么被发现的,以及怎么写一个"自己的系统服务"。
一、什么是系统服务(System Ability)
普通应用是"有 UI、能被用户启动的程序"。系统服务是"没有 UI、长期在后台、给别的进程提供能力"的服务。鸿蒙里几乎所有的核心子系统都是 SA:
这些服务跑在不同的系统进程里。普通应用通过"代理 + 远程调用"间接跟它们交互。
二、SAMgr 的工作原理
SAMgr 是所有 SA 的总后台。它的核心职责:
注册:每个服务进程启动时把自己的 SA 注册到 SAMgr
查询:其他进程通过 SA ID 查找服务句柄
生命周期管理:跟踪在线/离线、动态拉起需要的服务进程
分布式:跨设备查询 SA 时,通过
networkId路由到对端
+--------------------+ +--------------------+
| Service Process A | | Service Process B |
| - SA 401 (AMS) | | - SA 1401 (DMS) |
+--------+-----------+ +---------+----------+
↑ ↑
register SA register SA
│ │
v v
+------------------------------------------+
| System Ability Mgr |
| (samgr 服务进程) |
| - 维护 SA ID → IRemoteObject 映射 |
| - 按需加载进程 |
+--------+---------------------------------+
↑
GetSystemAbility
│
+--------+--------+
| Application |
| (ArkTS client) |
+------------------+
应用进程查 SA 时拿到的不是具体实现对象,而是一个 IRemoteObject(Binder IPC stub)。这是"代理模式"的鸿蒙实现。
三、ArkTS 侧使用已注册 SA
绝大多数系统 SA 已经被包装好了 ArkTS 直接能用的 API。例如 featureAbility.startAbility()、geolocation.getCurrentLocation() 等。但有些场景你需要直接调底层 SA。
直接通过 RPC 包调 SA
import rpc from '@ohos.rpc';
import featureAbility from '@ohos.ability.featureAbility';
const SA_ID = 401; // AbilityManagerService 的 SA ID
async function callAbilityManager() {
const want = {
bundleName: 'com.example.app',
abilityName: 'EntryAbility'
};
// 1. 绑定到目标 Ability
const connect: rpc.RemoteObject = await new Promise((resolve, reject) => {
featureAbility.connectAbility({
deviceId: '',
bundleName: want.bundleName,
abilityName: want.abilityName
}, {
onConnect: (elementName, remote) => resolve(remote as rpc.RemoteObject),
onDisconnect: () => {},
onFailed: (code) => reject(new Error(`connect fail code=${code}`))
});
});
}
更常见的"用 SA"是直接 import kit 包。但了解这条路有好处:
知道哪个 SA 在背后
能理解延迟从哪来
四、写一个自定义 SA
这一节是这一篇的重点。开发者有时会写"业务级 SA",给同一进程内或跨进程的其他模块用。
步骤 1:SA 接口定义
// IMyService.h
class IMyService : public IRemoteBroker {
public:
virtual int DoSomething(const std::string &input) = 0;
DECLARE_INTERFACE_DESCRIPTOR(u"ohos.samples.IMyService");
};
IRemoteBroker 是所有 SA stub/proxy 的基类。DECLARE_INTERFACE_DESCRIPTOR 是给它分配唯一描述符,用于 IPC 反序列化时识别。
步骤 2:实现 stub(服务侧)
// MyServiceStub.h
class MyServiceStub : public IRemoteStub<IMyService> {
public:
virtual int OnRemoteRequest(uint32_t code, MessageParcel &data,
MessageParcel &reply, MessageOption &option) override {
switch (code) {
case 1: {
std::string input = data.ReadString();
int result = DoSomething(input);
reply.WriteInt32(result);
break;
}
}
return 0;
}
};
stub 在收到 IPC 请求后反序列化 → 调本地实现 → 序列化结果 → 回传。
步骤 3:实现 proxy(客户端代理)
// MyServiceProxy.h
class MyServiceProxy : public IRemoteProxy<IMyService> {
public:
explicit MyServiceProxy(const sptr<IRemoteObject> &remote)
: IRemoteProxy<IRemoteObject>(remote) {}
int DoSomething(const std::string &input) override {
MessageParcel data;
MessageParcel reply;
data.WriteString(input);
int err = remote_->SendRequest(1, data, reply, MessageOption());
if (err != 0) return err;
return reply.ReadInt32();
}
};
proxy 把调用序列化成 Parcel 通过 SendRequest 发到 stub。远程调用从 stub 端"恢复"出来调用真正的实现。
步骤 4:注册服务
每个 SA 都有一个唯一 ID。注册代码:
#include "system_ability_manager.h"
#include "system_ability_definition.h"
int main(int argc, char *argv[]) {
auto saMgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
sptr<MyServiceStub> stub = new (std::nothrow) MyServiceStub();
sptr<IMyService> service = iface_cast<IMyService>(stub);
// 注册
int32_t MY_SA_ID = 3999;
int ret = saMgr->AddSystemAbility(MY_SA_ID, stub);
if (ret != 0) {
std::cerr << "Failed to register SA " << MY_SA_ID << ", err: " << ret << std::endl;
return ret;
}
std::cout << "SA " << MY_SA_ID << " registered" << std::endl;
// 主线程进入事件循环
while (true) {
sleep(1);
}
}
加到 sa 配置里:
{
"services": [
{
"name": "my_service",
"path": ["/system/bin/my_service"],
"uid": "system",
"gid": ["system", "shell"],
"ondemand": true,
"saId": 3999,
"permission": "ohos.permission.ACCESS_MY_SERVICE"
}
]
}
ondemand: true 表示"按需加载"——如果没人调它就不启动进程。
步骤 5:客户端发现 SA
sptr<ISystemAbilityManager> saMgr =
SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
sptr<IRemoteObject> remote = saMgr->GetSystemAbility(3999);
sptr<IMyService> myService = iface_cast<IMyService>(remote);
int result = myService->DoSomething("hello");
注意 GetSystemAbility 是同步的:会重试 10 次共约 1 秒。如果服务进程未启动,会先启动它再返回。
步骤 6:动态加载 + 回调
对按需加载的 SA,要用异步接口:
class LoadCallback : public SystemAbilityLoadCallbackStub {
public:
void OnLoadSystemAbilitySuccess(int32_t saId, const sptr<IRemoteObject> &remote) override {
std::cout << "SA " << saId << " loaded" << std::endl;
// 调用服务
sptr<IMyService> myService = iface_cast<IMyService>(remote);
}
void OnLoadSystemAbilityFail(int32_t saId) override {
std::cerr << "SA " << saId << " load failed" << std::endl;
}
};
sptr<LoadCallback> callback = new LoadCallback();
saMgr->LoadSystemAbility(MY_SA_ID, callback);
五、跨设备 SA 调用
系统服务可以分布在不同设备。调用时需要 networkId:
// 获取对端设备的 networkId
std::string networkId = GetRemoteDeviceNetworkId();
sptr<IRemoteObject> remote = saMgr->GetSystemAbility(
MY_SA_ID,
networkId,
ISystemAbilityManager::IFACE
);
if (remote != nullptr) {
// 跨设备调用
sptr<IMyService> remoteProxy = iface_cast<IMyService>(remote);
int result = remoteProxy->DoSomething("cross_device");
}
跨设备 SA 走的是分布式软总线,会被网络延迟影响。要在客户端做超时(推荐用 SendRequest 时设置 WaitForTime)。
六、SA 接口演进
SA 的版本必须向后兼容,否则上线后旧客户端拉起新服务会失败:
SA v1 接口:DoSomething(string)
SA v2 接口:DoSomething(string, bool newFlag)
常见演进策略:
加新方法。老客户端调
DoSomething(string),新方法DoSomethingEx(string, bool)参数包扩展。把单一 string 改成 struct/enum
返回值版本号。reply 头部写 protocal_version,客户端对不上做兜底
七、一些系统服务 ID
// foundation/systemabilitymgr/samgr/interfaces/innerkits/samgr_proxy/include/system_ability_definition.h
namespace OHOS {
constexpr int32_t SYSTEMABILITY_ID_BASE = 10000;
constexpr int32_t SUBSYS_COMMON_SERVICE_ID_BEGIN = 10000;
// ...
constexpr int32_t APP_MGR_SERVICE_ID = 3501;
constexpr int32_t BUNDLE_MGR_SERVICE_ID = 3511;
constexpr int32_t DISTRIBUTED_SCHED_SA_ID = 1401;
constexpr int32_t SOFTBUS_SERVER_SA_ID = 1000;
// ...
}
普通开发者不需要查这些 ID,但出问题搜时会见到。
八、调试与诊断
调试 SA 调用时关键工具是 hilog、hdc shell 以及 DevEco 的 IPC 跟踪:
# 抓跨进程调用 logcat
hdc shell hilog | grep -i 'saMgr\|RemoteObject'
# 查设备所有 SA
hdc shell sa list
客户端 SendRequest 成功后看 logcat,如果没反应,多半是:
SA 没注册(saId 不存在或 ondemand 路径错误)
权限不够(
ohos.permission.MANAGE_SYSTEM_SA_SERVICE或ohos.permission.MANAGE_USER_SA_SERVICE)GetSystemAbility超时但没等结果
九、几个常见错误
十、和前几篇的衔接
第 56 篇 IPC 与 DataShareExtension 跨进程数据共享
第 9 篇分布式软总线,本篇的跨设备 SA 落在 SoftBus 之上
第 38 篇埋点与监控,错误事件里有 “sa load failed” 一类
十一、小结
System Ability Framework 是鸿蒙"操作系统级"的可重用基础设施。理解 SAMgr 的注册、发现、远程调用模型,写自定义 SA 比想象中简单,但调试跨设备 SA 时问题会变多。把 SA 设计看作"长时间运行的服务 + 明确的接口契约 + 演进的版本协议",就抓住了主线。
下一篇是深度专题阶段回顾,把 61-70 篇串成一张知识图谱,并预告后 30 篇进入"专家架构 + 生态 + 收官"的方向。