为什么“一个能力不够用就再借一个”?——鸿蒙 Ability Framework 真的能把应用拆成“部队集结”吗!
开篇语
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
前言
第一次接触鸿蒙的 Ability Framework,我内心的 OS 是:这不是传统的 Activity/Service 套壳,而是把应用按“能力(Ability)”切成可组合的小单元,然后再靠统一的 Want(意图路由)、IPC(进程间通信)、模块化(HAP) 和 分布式底座 把它们拧成一股绳。今天我们就把这套“部队建制”拆开聊:FA/PA 模型怎么分工、服务间通信怎么玩、动态加载与模块化如何落地,再塞进几段可直接抄走的 ArkTS/伪代码,务求不玄学、只讲工程。
前言:从“页面+服务”到“能力编排”
传统移动端多半是“界面页 + 后台服务”。鸿蒙把粒度再切细:UI 界面是能力、前台业务是能力、后台长驻也是能力。能力之间靠 Want 路由和 IPC 粘在一起,再叠上分布式能力,同一个 App 可以“向外借用别的设备的能力”。听上去酷,但工程关键是:边界清晰、契约稳定、调用可观测。
01|Ability Framework 鸟瞰
┌──────────────────────── App 进程(一个或多个 HAP 模块) ────────────────────────┐
│ UIAbility / FeatureAbility(界面能力) Service/ParticleAbility(无界面能力) │
│ ExtensionAbility(数据共享、文件、输入法等扩展) │
│ ↑ Want(意图) │ AbilityConnection(连接式 RPC) │
│ ├───────────────────┴──────────────────────────────┐ │
│ │ DataShare(URI 数据通道) │ │
│ └────────────── Common Event(发布/订阅) ─────────┘ │
│ HAP(entry/feature/extension) 模块化装配,按需安装 / 动态路由 │
└────────────────────────────────────────────────────────────────────┘
- Ability:颗粒化的功能单元,有 UI 的(FA/UIAbility) 与 无 UI 的(PA/ServiceAbility/Extension)。
- Want:带上
action / entities / uri / parameters的“意图包裹”,用于定位目标能力并传参。 - HAP:应用安装包的模块单位(
entry必有,feature/extension选配),支持按需安装。 - 模型演进:早期 FA/PA(Feature/Particle),后续 Stage 模型里有
UIAbility / ServiceExtensionAbility / DataShareExtensionAbility …。本文围绕FA/PA 概念讲透,并给出贴近 ArkTS/Stage 的示意代码,便于新旧心智统一。
02|FA vs. PA:分工与协作
2.1 Feature Ability(FA)
- 定位:有界面、面向交互与页面导航的能力。
- 典型职责:渲染 UI、收集输入、组合多个后台能力的结果。
- 最佳实践:轻逻辑,IO/长任务丢给 PA 或 Extension;UI 层只关心“状态”。
2.2 Particle Ability(PA)
- 定位:无界面、面向长驻/定时/重 IO/跨会话的能力。
- 典型职责:播放/下载、设备同步、分布式任务执行、消息推送接入等。
- 最佳实践:作为 “后端小中台”,提供清晰的 RPC 接口与幂等/权限治理。
2.3 协作边界(非常重要)
- FA → PA:通过 AbilityConnection(连接式 IPC) 发起调用(如“开始下载/查询状态/停止”)。
- 数据类交互:统一通过 DataShare(URI + 表式接口) 暴露结构化数据;天然支持观察者模式。
- 事件类交互:Common Event 用于系统广播/业务域内广播(前后台切换、网络变化等)。
03|服务间通信机制(选型心法)
3.1 连接式 RPC(FA ⇄ PA)
- 场景:双向、会话化、调用频繁、需要回调(进度/心跳)。
- 形态:
connectAbility(want, connection)→ 回调onConnect(remote)拿到 RemoteObject,之后 发送请求。 - 优点:像本地接口一样调用,低延迟、强约束。
- 注意:连接生命周期要跟页面/应用生命周期绑紧;断线要自动重连或降级。
3.2 DataShare(数据提供者)
- 场景:跨 Ability/跨进程结构化数据共享(列表、详情、增删改查),支持变更订阅。
- 形态:
datashare://bundle.authority/table+query/insert/update/delete。 - 优点:天然解耦、可缓存、可权限细分。
3.3 Common Event(发布/订阅)
- 场景:系统或业务广播(如“下载完成”、“登录状态变化”)。
- 形态:
subscribe({ events: [...] })/publish({ event, data })。 - 优点:多对多、松耦合;
- 注意:不要滥用,数据请走 DataShare,过程调用请走 RPC。
04|动态加载与模块化
4.1 HAP 模块化
- entry:应用入口(常驻),可含 UI 与基础能力。
- feature:可选装的功能模块(如编辑器、支付、AR)。
- extension:系统扩展类能力(文件、数据分享、输入法等)。
4.2 按需安装(On-Demand)
- 通过 模块标识(moduleName) 与 能力声明(skills/permissions),在需要时再拉起安装。
- 策略:冷路径分发(首次进入某功能再装)、弱网/低电量延后、Wi-Fi + 充电时预热。
4.3 路由与可发现性
- Want 中带
bundleName / moduleName / abilityName / action / uri等信息; - Bundle/Ability 元数据(
module.json5)声明了 skills(过滤条件)与权限需求; - 统一路由器 负责解析业务意图 → 选择能力/模块 → 按需安装 → 拉起。
05|实战 A:FA 连接 PA 并做 RPC(ArkTS/示意)
说明:接口名称随 SDK 版本会有差异,下面示例强调调用心智与资源管理;把它当作模板套入你当前版本的 API。
// FA 侧:连接后台 PA 并发起调用(如:开始下载 / 查询进度)
import featureAbility from '@ohos.app.ability.featureAbility'
import rpc from '@ohos.rpc'
let remote: rpc.IRemoteObject | null = null
const connection = {
onConnect(elementName, remoteObj) {
remote = remoteObj
console.info('[FA] connected to PA:', JSON.stringify(elementName))
},
onDisconnect(elementName) {
remote = null
console.warn('[FA] disconnected:', JSON.stringify(elementName))
},
onFailed(code) {
console.error('[FA] connect failed:', code)
}
}
export async function startDownload(taskId: string, url: string) {
const want = {
bundleName: 'com.demo.app',
abilityName: 'com.demo.app.DownloadPA', // 你的 PA Ability 全名
action: 'action.download'
}
if (!remote) {
await featureAbility.connectAbility(want, connection) // 拉起连接
}
if (!remote) throw new Error('PA not connected')
// 构造 RPC 请求(示意)
const data = rpc.MessageParcel.create()
const reply = rpc.MessageParcel.create()
const option = new rpc.MessageOption()
data.writeString('start')
data.writeString(taskId)
data.writeString(url)
try {
await remote.sendRequest(1001 /* START_DOWNLOAD */, data, reply, option)
const ok = reply.readInt() === 0
if (!ok) throw new Error('remote error')
} finally {
data.reclaim(); reply.reclaim()
}
}
export async function queryProgress(taskId: string): Promise<number> {
if (!remote) throw new Error('not connected')
const data = rpc.MessageParcel.create()
const reply = rpc.MessageParcel.create()
const option = new rpc.MessageOption()
data.writeString('progress'); data.writeString(taskId)
try {
await remote.sendRequest(1002 /* QUERY_PROGRESS */, data, reply, option)
return reply.readInt() // 0..100
} finally { data.reclaim(); reply.reclaim() }
}
// 生命周期管理(很关键)
export function teardown() {
if (remote) {
featureAbility.disconnectAbility(connection)
remote = null
}
}
PA 侧骨架(示意):实现一个 RemoteObject,在 onConnect 时把对象暴露给对端。
// DownloadPA(粒子能力)——伪代码,演示 onRemoteRequest 处理
import rpc from '@ohos.rpc'
class DownloadStub extends rpc.RemoteObject {
constructor(descriptor) { super(descriptor) }
async onRemoteRequest(code: number, data: rpc.MessageParcel, reply: rpc.MessageParcel, option) {
const cmd = data.readString()
switch (code) {
case 1001: // START_DOWNLOAD
const taskId = data.readString()
const url = data.readString()
await startTask(taskId, url) // 你的业务
reply.writeInt(0); return true
case 1002: // QUERY_PROGRESS
const id = data.readString()
reply.writeInt(getProgress(id)); return true
default:
return false
}
}
}
// 在 PA 生命周期里把 DownloadStub 实例返回给对端(示意)
// e.g. onConnect(want) { return new DownloadStub('com.demo.Download') }
要点复盘
- 连接式 RPC:适合高频、低延迟、需要回调的调用。
- 资源管理:连接绑定到 页面/应用生命周期,离开页面断开,避免泄漏。
- 错误兜底:失败→降级(本地实现/排队重试)。
06|实战 B:DataShare 跨服务数据访问(查询 + 监听)
// 查询下载任务表 & 监听变化(示意)
import dataShare from '@ohos.data.dataShare'
const URI = 'datashare://com.demo.app.DownloadProvider/tasks'
export async function queryTasks() {
const helper = await dataShare.createDataShareHelper(URI)
const predicates = new dataShare.DataSharePredicates()
.equalTo('owner', currentUserId)
.orderByDesc('updated_at')
const columns = ['task_id', 'status', 'progress', 'updated_at']
const resultSet = await helper.query(URI, predicates, columns)
const rows: any[] = []
while (resultSet.goToNextRow()) {
rows.push({
id: resultSet.getString(resultSet.getColumnIndex('task_id')),
status: resultSet.getString(resultSet.getColumnIndex('status')),
progress: resultSet.getInt(resultSet.getColumnIndex('progress')),
})
}
resultSet.close()
return rows
}
export async function watchTasks(onChange: () => void) {
const helper = await dataShare.createDataShareHelper(URI)
const observer = {
onChange(uris) { onChange() } // 列表变化回调
}
await helper.on('dataChange', observer) // 订阅
return () => helper.off('dataChange', observer) // 取消订阅
}
选型理由
- 结构化数据 用 DataShare 更自然,支持权限细分与变更通知;
- UI 层走查询 + 监听双轨,避免轮询与脏读。
07|实战 C:按需加载 Feature 模块(路由 + 延迟安装思路)
不同版本的安装/拉起接口会有差异,下面给出通用策略与伪代码,把关键控制面讲清楚。
策略
- 路由前置:业务跳转时先看模块是否已安装;
- 已安装:直接构造 Want 跳转;
- 未安装:触发 按需安装 → 监听进度 → 成功后再次路由;
- 弱网/失败:给出降级方案或引导在 Wi-Fi + 充电时预装。
伪代码:
type Route = { module: string; ability: string; want: any }
async function ensureFeatureInstalled(moduleName: string): Promise<boolean> {
// 1. 询问安装状态(伪接口)
if (await Bundle.isModuleInstalled('com.demo.app', moduleName)) return true
// 2. 触发安装并监听(伪接口)
const task = await Bundle.installModule('com.demo.app', moduleName)
task.on('progress', p => console.info('install progress', p))
const ok = await task.waitUntilDone({ timeoutMs: 60_000 })
return ok
}
export async function navigateTo(route: Route) {
const ok = await ensureFeatureInstalled(route.module)
if (!ok) { showToast('网络不给力,稍后再试或在 Wi-Fi 环境下重试'); return }
// 构造 Want 跳入 feature ability(示意)
const want = {
bundleName: 'com.demo.app',
moduleName: route.module,
abilityName: route.ability,
action: route.want?.action,
parameters: route.want?.params || {}
}
await featureAbility.startAbility(want)
}
工程注意
- 能力声明:在模块
module.json5中声明 skills/permissions,路由依赖它匹配。 - 资源与样式复用:跨模块统一
resources/键名,避免“换皮撕裂”。 - 灰度与回滚:按模块灰度发布;安装失败/崩溃→回滚到上一个稳定版本。
08|工程化清单(性能 × 安全 × 可维护)
性能
- 连接复用:FA 与 PA 的连接复用(同一页内多次调用不必反复 connect/disconnect)。
- 冷启动加速:把大对象懒加载,把大模块按需安装。
- UI/IO 分离:UI(FA)无阻塞;重活交给 PA/Extension。
- 批量/流式:RPC 合并小请求,传大对象走流式/分块。
安全
- 权限最小化:PA 侧按接口粒度校验调用者(bundle/签名/会话),不要“全放行”。
- 数据边界:DataShare 只暴露必要表/列;敏感字段脱敏/分级。
- 可信日志:连接、调用、失败、权限拒绝都要打点(traceId 贯穿)。
健壮性
- 幂等:对可能重试的接口引入 idempotencyKey 与去重表。
- 超时/熔断:RPC 设置 deadline,高失败率熔断并退避。
- 降级:远端不可用 → 本地实现或排队;按需安装失败 → 备用轻量页。
- 观测面:
rpc_latency_ms{code}、datasahre_query_qps、feature_install_fail_total等指标。 - 版本矩阵:FA/PA/Extension 的接口版本与能力位图要协商;路由器按版本做降级映射。
结语:把复杂折叠起来,用户只看到“顺滑”
FA/PA 不是为了“名词好看”,而是为了让界面与后台职责清爽分离;RPC / DataShare / 事件也不是“越多越强”,而是各司其职;模块化与按需安装不是“炫技”,是让应用又轻又灵。当你用这些“工程秩序”把能力编排起来,用户只感到一件事:顺滑。
现在就挑一段上面的示例改成你项目的命名,连上真实的能力名和能力声明,跑一遍——**你会发现,拆分与编排,其实很有成就感。**😉
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!
- 点赞
- 收藏
- 关注作者
评论(0)