为什么“一个能力不够用就再借一个”?——鸿蒙 Ability Framework 真的能把应用拆成“部队集结”吗!

举报
喵手 发表于 2025/10/31 17:18:01 2025/10/31
【摘要】 开篇语哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,...

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区: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 模块(路由 + 延迟安装思路)

不同版本的安装/拉起接口会有差异,下面给出通用策略伪代码,把关键控制面讲清楚。

策略

  1. 路由前置:业务跳转时先看模块是否已安装;
  2. 已安装:直接构造 Want 跳转;
  3. 未安装:触发 按需安装 → 监听进度 → 成功后再次路由;
  4. 弱网/失败:给出降级方案或引导在 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|工程化清单(性能 × 安全 × 可维护)

性能

  1. 连接复用:FA 与 PA 的连接复用(同一页内多次调用不必反复 connect/disconnect)。
  2. 冷启动加速:把大对象懒加载,把大模块按需安装
  3. UI/IO 分离:UI(FA)无阻塞;重活交给 PA/Extension。
  4. 批量/流式:RPC 合并小请求,传大对象走流式/分块

安全

  1. 权限最小化:PA 侧按接口粒度校验调用者(bundle/签名/会话),不要“全放行”
  2. 数据边界:DataShare 只暴露必要表/列;敏感字段脱敏/分级
  3. 可信日志:连接、调用、失败、权限拒绝都要打点(traceId 贯穿)。

健壮性

  1. 幂等:对可能重试的接口引入 idempotencyKey 与去重表。
  2. 超时/熔断:RPC 设置 deadline,高失败率熔断并退避。
  3. 降级:远端不可用 → 本地实现或排队;按需安装失败 → 备用轻量页
  4. 观测面rpc_latency_ms{code}datasahre_query_qpsfeature_install_fail_total 等指标。
  5. 版本矩阵:FA/PA/Extension 的接口版本能力位图要协商;路由器按版本做降级映射。

结语:把复杂折叠起来,用户只看到“顺滑”

FA/PA 不是为了“名词好看”,而是为了让界面与后台职责清爽分离RPC / DataShare / 事件也不是“越多越强”,而是各司其职模块化与按需安装不是“炫技”,是让应用又轻又灵。当你用这些“工程秩序”把能力编排起来,用户只感到一件事:顺滑。
  现在就挑一段上面的示例改成你项目的命名,连上真实的能力名和能力声明,跑一遍——**你会发现,拆分与编排,其实很有成就感。**😉

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。