Administrator
发布于 2026-09-03 / 7,714 阅读
0
0

鸿蒙系统服务框架:SAMgr、SA 注册与跨设备调用

鸿蒙百篇系列第 69 篇 · 专家

第 56 篇讲过 IPC 跨进程通信,那是从"应用间数据共享"的角度。这一篇我们钻进鸿蒙的"系统服务"层(SAMgr,System Ability Manager),看看鸿蒙内部那些全局服务(如 DistributedSchedule、SoftBus、AbilityManager)是怎么注册、怎么被发现的,以及怎么写一个"自己的系统服务"。

一、什么是系统服务(System Ability)

普通应用是"有 UI、能被用户启动的程序"。系统服务是"没有 UI、长期在后台、给别的进程提供能力"的服务。鸿蒙里几乎所有的核心子系统都是 SA:

系统服务

saId

用途

AbilityManagerService

401

管理 UIAbility / Service / Ability 生命周期

BundleManagerService

402

包管理

DistributedSchedService

1401

分布式任务调度

SoftBusServer

1000

分布式软总线

StorageManagerService

500

存储

这些服务跑在不同的系统进程里。普通应用通过"代理 + 远程调用"间接跟它们交互。

二、SAMgr 的工作原理

SAMgr 是所有 SA 的总后台。它的核心职责:

  1. 注册:每个服务进程启动时把自己的 SA 注册到 SAMgr

  2. 查询:其他进程通过 SA ID 查找服务句柄

  3. 生命周期管理:跟踪在线/离线、动态拉起需要的服务进程

  4. 分布式:跨设备查询 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)

常见演进策略:

  1. 加新方法。老客户端调 DoSomething(string),新方法 DoSomethingEx(string, bool)

  2. 参数包扩展。把单一 string 改成 struct/enum

  3. 返回值版本号。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 超时但没等结果

九、几个常见错误

错误

原因

处理

GetSystemAbility 返回 nullptr

服务进程没启动或没权限

查 saId → 检查启动 log

SendRequest 卡死

远端死锁或 sandbox 不匹配

加 MessageOption 超时

IPC 反序列化失败

struct 字段未对齐版本

rollback 到稳定版本

多线程并发访问 RemoteObject

RemoteObject 不天然线程安全

加 mutex

十、和前几篇的衔接

  • 第 56 篇 IPC 与 DataShareExtension 跨进程数据共享

  • 第 9 篇分布式软总线,本篇的跨设备 SA 落在 SoftBus 之上

  • 第 38 篇埋点与监控,错误事件里有 “sa load failed” 一类

十一、小结

System Ability Framework 是鸿蒙"操作系统级"的可重用基础设施。理解 SAMgr 的注册、发现、远程调用模型,写自定义 SA 比想象中简单,但调试跨设备 SA 时问题会变多。把 SA 设计看作"长时间运行的服务 + 明确的接口契约 + 演进的版本协议",就抓住了主线。

下一篇是深度专题阶段回顾,把 61-70 篇串成一张知识图谱,并预告后 30 篇进入"专家架构 + 生态 + 收官"的方向。


评论