“后台服务就该永远默默无闻”?——为什么不把 Ability 模型、ServiceAbility 实现、FA/PA 交互和 RPC 一次性拿下!
本文为鸿蒙开发者提供ServiceAbility与ServiceExtensionAbility的完整开发指南,系统梳理了FA模型与Stage模型的差异。重点讲解了startAbility(启动服务)和connectAbility(连接RPC)的使用场景与实现方法,详细对比了两代模型在清单注册、生命周期管理上的区别。通过音乐播放服务示例,展示了Stage模型下服务端Stub实现与客户端调用的完整流
我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~
前言
老实说,每次看到“某某功能需要常驻后台”这句需求,我脑门都会“咔”地一紧:到底是应该“启动服务就完事”(startAbility),还是要“连接服务走 RPC”(connectAbility),抑或两者兼用? 再加上 Harmony 的 FA/PA 模型与 Stage 模型并存,名词一上来就“外焦里嫩”。
别怵,这篇就按你的大纲一路捋顺:Ability 模型 → ServiceAbility 实现 → FA/PA 交互,把 startAbility / connectAbility / RPC 的选型与代码姿势讲清楚;顺带把 ServiceExtensionAbility(Stage 模型) 的差异也标出来,方便你做“新老并轨”。文末再给一套“工程化”清单,帮你避坑。参考点我都贴了官方文档,想深挖直接跳。(华为开发者官方网站)
一、先把地图摊开:Ability 模型长啥样?
1)两代模型要对齐记忆
-
FA 模型(早期)
- FA(Feature Ability):多为可见页面(PageAbility)
- PA(Particle Ability):多为无界面服务(ServiceAbility / DataAbility / FormAbility)
- 交互套路:
startAbility()(启动/驱动)+connectAbility()(绑定后 RPC) - 官方“连接 ServiceAbility”一文把流程讲得很直白:连接需要回调;onConnect() 要返回 IRemoteObject;通信靠 RPC。(华为开发者官方网站)
-
Stage 模型(API 9+)
- UIAbility ≈ FA 的 PageAbility
- ServiceExtensionAbility ≈ PA 的 ServiceAbility 的“Stage 版”
- 交互套路:
context.startAbility(...) / startServiceExtensionAbility(...)与context.connectServiceExtensionAbility(...)/disconnect...;上下文类型是ServiceExtensionContext。(GitCode)
经验口诀:“启动干活用 startAbility,交互沟通用 connectAbility + RPC”。启动负责“让服务活起来”,连接负责“建立全双工通道”。
二、ServiceAbility/ServiceExtensionAbility:怎么“注册 + 实现 + 启停 + 连接”?
2.1 清单注册(FA vs Stage)
-
FA(旧):在
config.json/module/abilities里注册,type: "service",可见性、图标/描述等按需配置(不同版本字段略有差异,注意与 SDK 对齐)。很多实战总结也强调:用 startAbility 仅启动即可,不必实现 onConnect;若要 RPC 就必须支持 connectAbility/onConnect。(ost.51cto.com) -
Stage(新):在
module.json5的abilities里声明一个type: "service"的扩展能力(即 ServiceExtensionAbility),启动/连接 API 均从上下文拿(ServiceExtensionContext),文档列出了start/stop/connect/disconnect的全家桶。(GitCode)
2.2 生命周期要点
- FA/ServiceAbility:
onStart(初始化)→onCommand(每次 startAbility 调用都会到)→onConnect(绑定时返回IRemoteObject)→onDisconnect→onStop。(ost.51cto.com) - Stage/ServiceExtensionAbility:方法名略有不同,但语义一致:创建/请求/连接/断开/销毁;连接时同样需要返回远端对象用于 RPC。(GitCode)
口诀 2:“start 负责唤醒,command 接任务;connect 负责建通道,disconnect 收尾”。
三、两种启动姿势:startAbility vs connectAbility
| 场景 | 方式 | 生命周期触发 | 是否拿到远端对象 | 典型用途 |
|---|---|---|---|---|
| 只需让服务开始工作(下载、巡检、预热…) | startAbility() / startServiceExtensionAbility() |
onStart → onCommand |
否 | 纯“启动”,不交互 |
| 需要与服务交互(播放控制、查询状态…) | connectAbility() / connectServiceExtensionAbility() |
onConnect(首次) |
是(IRemoteObject) |
建立 RPC 会话 |
- FA 文档强调:连接 Service 需实现 IAbilityConnection 回调,成功后拿到
IRemoteObject;服务端的onConnect()必须 return 一个 IRemoteObject(通常继承自rpc.RemoteObject)。(华为开发者官方网站) - Stage 文档给出同等 API 在
ServiceExtensionContext上的对应方法。(GitCode)
四、RPC 怎么写:RemoteObject/Proxy/Stub 三件套
**核心对象(ArkTS/JS 层)**在 @kit.IPCKit 的 rpc 模块中:
IRemoteObject:对端对象的抽象RemoteObject:服务端“桩”(Stub)的默认实现基类(重写onRemoteRequest)MessageSequence:消息载体(序列化/反序列化)- 典型流程见 “IPC/RPC 开发指导”与
js-apis-rpc参考。(GitCode)
口诀 3:“服务端写 Stub(RemoteObject),客户端包 Proxy,双方约定 code + 序列化协议”。
五、完整示例(ArkTS/Stage 版):音乐播放服务(播放/暂停/查询状态)
说明:示例以 Stage 模型 为主(
ServiceExtensionAbility),FA 的 ServiceAbility 写法在架构和 RPC 思路上一致,只是入口/上下文 API 命名不同;文档映射见上文。以下代码为示意,不同 API 版本的命名可能略有差异,落地时以已安装 SDK 文档为准。(GitCode)
5.1 module.json5 注册(Stage)
{
"module": {
"name": "entry",
"type": "entry",
"abilities": [
{
"name": "EntryAbility",
"type": "page",
"srcEntry": "./ets/entryability/EntryAbility.ets"
},
{
"name": "PlayerService",
"type": "service", // ServiceExtensionAbility
"srcEntry": "./ets/service/PlayerService.ets",
"exported": true // 允许跨应用连接(按需)
}
],
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" }
]
}
}
5.2 服务端:PlayerService.ets(Stub + Service)
// ets/service/PlayerService.ets
import { ServiceExtensionAbility, common } from '@kit.AbilityKit'
import { rpc } from '@kit.IPCKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
// 定义消息码
const TRANS = {
PLAY: 1,
PAUSE: 2,
GET_STATUS: 3,
} as const
// 服务端 Stub:继承 RemoteObject,重写 onRemoteRequest
class PlayerStub extends rpc.RemoteObject {
private playing = false
constructor(descriptor = 'com.example.player.IPC') {
super(descriptor)
}
// 处理来自客户端的请求
onRemoteRequest(code: number, data: rpc.MessageSequence, reply: rpc.MessageSequence, option: rpc.MessageOption): boolean {
try {
switch (code) {
case TRANS.PLAY: {
const url = data.readString() // 假设客户端写入了一个字符串 URL
hilog.info(0x0, 'Player', `PLAY ${url}`)
this.playing = true
reply.writeInt(0) // 0 表示 OK
return true
}
case TRANS.PAUSE: {
hilog.info(0x0, 'Player', `PAUSE`)
this.playing = false
reply.writeInt(0)
return true
}
case TRANS.GET_STATUS: {
reply.writeBool(this.playing)
return true
}
default:
return super.onRemoteRequest(code, data, reply, option)
}
} catch (e) {
hilog.error(0x0, 'Player', `onRemoteRequest error: ${JSON.stringify(e)}`)
return false
}
}
}
export default class PlayerService extends ServiceExtensionAbility {
private stub = new PlayerStub()
// startAbility / startServiceExtensionAbility 触发
onCreate(want: common.Want): void {
hilog.info(0x0, 'Player', 'Service onCreate')
}
// connectAbility / connectServiceExtensionAbility 触发,必须返回 IRemoteObject
onConnect(want: common.Want): rpc.IRemoteObject {
hilog.info(0x0, 'Player', 'Service onConnect')
return this.stub as unknown as rpc.IRemoteObject
}
onDisconnect(want: common.Want): void {
hilog.info(0x0, 'Player', 'Service onDisconnect')
}
onDestroy(): void {
hilog.info(0x0, 'Player', 'Service onDestroy')
}
}
要点回看:onConnect 必须返回
IRemoteObject,文档也明确了这一点;你通常继承rpc.RemoteObject来实现。(GitCode)
5.3 客户端(UIAbility 内):启动与连接
// ets/pages/Home.ets(或 UIAbility 中的某处)
import { common } from '@kit.AbilityKit'
import { rpc } from '@kit.IPCKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
const TRANS = { PLAY:1, PAUSE:2, GET_STATUS:3 } as const
// 简易 Proxy,把 IRemoteObject 包一层,屏蔽 MessageSequence 细节
class PlayerProxy {
constructor(private remote: rpc.IRemoteObject) {}
async play(url: string): Promise<number> {
const data = rpc.MessageSequence.create()
const reply = rpc.MessageSequence.create()
const opt = new rpc.MessageOption()
data.writeString(url)
const ok = this.remote.sendRequest(TRANS.PLAY, data, reply, opt)
const code = ok ? reply.readInt() : -1
data.reclaim(); reply.reclaim()
return code
}
async pause(): Promise<number> {
const data = rpc.MessageSequence.create()
const reply = rpc.MessageSequence.create()
const opt = new rpc.MessageOption()
const ok = this.remote.sendRequest(TRANS.PAUSE, data, reply, opt)
const code = ok ? reply.readInt() : -1
data.reclaim(); reply.reclaim()
return code
}
async getStatus(): Promise<boolean> {
const data = rpc.MessageSequence.create()
const reply = rpc.MessageSequence.create()
const opt = new rpc.MessageOption()
const ok = this.remote.sendRequest(TRANS.GET_STATUS, data, reply, opt)
const playing = ok ? reply.readBool() : false
data.reclaim(); reply.reclaim()
return playing
}
}
@Entry
@Component
struct Home {
private connection?: number
private proxy?: PlayerProxy
build() {
Column({ space: 12 }) {
Button('Start Service (fire & forget)').onClick(async () => {
// 仅启动(不建立连接)
// Stage:可用 this.getContext() as UIAbilityContext,再调用 startServiceExtensionAbility
const want: common.Want = {
bundleName: 'com.example.app',
abilityName: 'PlayerService'
}
// @ts-ignore 伪示例:以你项目的 context API 为准
await this.getUIContext().startServiceExtensionAbility(want)
})
Button('Connect Service (RPC)').onClick(async () => {
const want: common.Want = {
bundleName: 'com.example.app',
abilityName: 'PlayerService'
}
// 连接:拿到 token(连接 id)与 IRemoteObject
// @ts-ignore 伪示例:实际需传入回调/IAbilityConnection,按 SDK 写
const { token, remote } = await this.getUIContext().connectServiceExtensionAbility(want)
this.connection = token
this.proxy = new PlayerProxy(remote)
}).backgroundColor('#238636').fontColor('#fff')
Button('PLAY').onClick(async () => {
const code = await this.proxy?.play('https://example.com/song.mp3')
hilog.info(0x0, 'Home', `PLAY -> ${code}`)
})
Button('PAUSE').onClick(async () => {
const code = await this.proxy?.pause()
hilog.info(0x0, 'Home', `PAUSE -> ${code}`)
})
Button('Query status').onClick(async () => {
const playing = await this.proxy?.getStatus()
hilog.info(0x0, 'Home', `playing=${playing}`)
})
Button('Disconnect').onClick(async () => {
if (this.connection != null) {
// @ts-ignore
await this.getUIContext().disconnectServiceExtensionAbility(this.connection)
this.connection = undefined
this.proxy = undefined
}
}).backgroundColor('#C9510C').fontColor('#fff')
}.padding(16)
}
}
对照文档:连接 API、上下文类型以及 MessageSequence 的读写方法都在官方能力包与 IPC 参考里列得很细,实际编码时以你工程的 API 版本为准。(GitCode)
六、FA/PA 交互怎么落地(老项目怎么办)?
-
FA→PA(PageAbility 调 ServiceAbility)
- “只启动”就用
featureAbility.startAbility(want)(触发onStart/onCommand); - “要交互”用
featureAbility.connectAbility(want, connection);成功后在onAbilityConnectDone(...)里拿到IRemoteObject,包一层 Proxy,后续就和上面的PlayerProxy同理。官方在“连接 ServiceAbility”的指南里强调 onConnect 返回 IRemoteObject 与 连接回调,照抄即可。(华为开发者官方网站)
- “只启动”就用
-
迁移到 Stage
- PageAbility → UIAbility;ServiceAbility → ServiceExtensionAbility;
featureAbility.startAbility/connectAbility→ 上下文上的 start/connectServiceExtensionAbility;IRemoteObject/RemoteObject/MessageSequence的用法不变(只是导入来源模块不同)。(GitCode)
七、跨设备与跨应用:就差一个 Want
- 跨应用:
want.bundleName指向对方包名,exported: true或相应可见性要打开。 - 跨设备:在 Want(或 Operation)里带上
deviceId(不同层语言名字略有差异),connectAbility 就会连到远端服务;这在华为开发者站“连接 Service”示例里有说明(Java 版 OperationBuilder 例子,但思路一致)。(华为开发者官方网站)
友情提示:跨设备 = IPC 变 RPC,链路更长,超时/重试/断线重连务必做全。
八、工程化清单(别到线上才想起来)
-
线程与耗时:服务回调在主线程,绝对不要在
onRemoteRequest里做重活;把重活丢到线程池/任务队列,再把结果写回。 -
协议与版本:RPC 的
code/参数结构 要有版本号;两端不同版本并存时,向下兼容。 -
连接管理:
- 首次连接才会触发
onConnect生成IRemoteObject,同进程多连接可能共享同一对象(文档有说明)(Geek大学); - 记得 disconnect;服务端适时回收资源。
- 首次连接才会触发
-
异常与错误码:
js-apis-rpc列出了错误码与MessageSequence生命周期管理(reclaim());异常要兜住。(GitCode) -
权限与可见性:最小权限、
exported慎开;系统/三方能力边界以当期 SDK 政策为准(系统文档会注明限制)。(GitCode) -
日志与监控:启动/连接/断连/远端死亡(
onAbilityDisconnectDone)都要打点;跨设备 RPC 要记录 RTT。(华为开发者官方网站)
九、常见“坑位”我先替你踩
- 只 start 不 connect 却想拿接口:start 只是“让它动起来”,拿不到远端对象;要交互必须 connect。(华为开发者官方网站)
- onConnect 不返回 IRemoteObject:那客户端连上也没法 RPC;记得继承
rpc.RemoteObject并return。(GitCode) - 在 onRemoteRequest 里做大 IO:主线程卡死 + ANR 警告,线上稳崩。
- MessageSequence 不
reclaim():长连场景内存涨跌肉眼可见(参考js-apis-rpc的资源回收说明)。(GitCode) - 跨设备忽略掉断连:弱网/休眠/切后台会频繁掉线,重连机制一定要做。
- 混用 FA/Stage API:团队要先定“当前应用跑哪套模型”,迁移分支要明确。
十、收个尾:把“后台服务”从概念落到秩序
看完这套链路,你就会发现:ServiceAbility/ServiceExtensionAbility 并不玄学。
- 能力模型:FA/PA 与 Stage 对齐心里有数;
- 实现路径:服务注册 + 生命周期 + onConnect 返回 IRemoteObject;
- 交互策略:start 负责“开机”,connect + RPC 负责“对话”;
- 工程护栏:线程、权限、错误码、连接管理一个都别落。
下一步?把你项目里那个“常驻下载/播放/同步”的服务挑出来,按上面的 Stub/Proxy 模版改造一次。一回生二回熟,第三回你就能写成团队基建。
参考(可直接对照 SDK)
- 华为开发者:连接 ServiceAbility(FA)——连接回调、onConnect 返回 IRemoteObject、跨设备/跨应用要点。(华为开发者官方网站)
- OpenHarmony 文档:连接 ServiceAbility(通用指南)——onConnect 返回值与
rpc.RemoteObject默认实现。(GitCode) - OpenHarmony 文档:ServiceExtensionContext(Stage)——
start/stop/connect/disconnect全部接口。(GitCode) - OpenHarmony 文档:IPC/RPC 开发指导 & js-apis-rpc——
MessageSequence读写、错误码、对象模型。(GitCode)
…
(未完待续)
更多推荐





所有评论(0)