我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

前言

先从一个略带吐槽的场景开场:你有个记事/账本/收藏类应用,用户说:“我还有一款工具 App,也想直接读你的‘条目列表’,别让我再导入导出了,行不?”这时候,如果你还想着“我给你丢个 JSON、或者开个本地 HTTP 端口”,那大概率会陷入权限难校验、协议难迭代、兼容性难维持的三难困境。
  鸿蒙体系里,这活儿其实该交给 DataAbility:它是系统化的应用间数据共享机制,天然支持URI 标识、权限校验、增删改查(insert/query/update/delete),客户端只需要一个 DataAbilityHelper 就能像用数据库一样“取数/写数”。官方还给了完整架构与 API,上手并不难(等会儿就用代码把它跑起来)。(华为开发者官网)

友情提示:如果你的项目是 Stage 模型优先、而且目标是“新版本” HarmonyOS,官方同时提供了 DataShare(DataShareExtensionAbility + DataShare 接口族)作为跨应用数据共享的“Stage 路线”。本文主轴仍按你给的主题讲 DataAbility,但会在合适位置标注 Stage 侧的“等价思路”,方便你迁移或二选一。(华为开发者官网)

一、DataAbility 架构:谁提供、谁访问、怎么跑

1) 两个主角:提供方访问方

  • 提供方(Provider):实现一个 DataAbility 组件,内部可接 RDB(关系数据库)、文件、内存表等,把对外能力封装为 增删改查接口
  • 访问方(Consumer):通过 DataAbilityHelper,携带 URI,调用 insert/query/update/delete 等操作。(华为开发者官网)

一个“串得起来的脑图”——

┌──────────────┐       URI(dataability://…)
│ 访问方 App A │  ──────────────────────────┐
│ DataAbility  │  ──(DataAbilityHelper)──▶  │  ┌──────────────────────┐
│   Helper     │                            └▶ │ 提供方 App B          │
└──────────────┘                                 │  DataAbility          │
        ▲                                        │  (RDB/File/内存…)     │
        │ (结果/游标/计数)                        │  insert/query/update  │
        └────────────────────────────────────────┴──────────────────────┘

要点

  • 全流程以 URI 为“寻址核心”,调用前不需要绑定服务,这让“跨应用取数”具有低耦合/强约束的特性。
  • 提供方是否可被访问,由 组件配置权限校验共同决定(后文专讲)。(华为开发者官网)

2) 关于 FA / Stage 的“路线图”

  • FA 模型里就是 DataAbility + DataAbilityHelper 这对组合;
  • Stage 模型里推荐 DataShareExtensionAbility + DataShare / DataShareHelper 完成同类能力(命名不同、思路一致)。如果新项目直接走 Stage,可优先考虑 DataShare。(华为开发者官网)

二、URI 机制:一串字符串,决定你访问的是谁的什么

1) URI 规范与示例

官方对 DataAbility 的 URI 定义非常清晰:

  • scheme:固定为 "dataability"
  • authority设备 ID(跨设备时填写;本地设备为空,因此紧跟着是 ///);
  • path:资源路径(如表/集合/条目);
  • query/fragment:可选,用于过滤/定位子资源。
    示例
  • 本地设备:dataability:///com.example.note.provider/notes/1
  • 跨设备:dataability://{device_id}/com.example.note.provider/notes/10
    (本地因为 device_id 为空,所以 dataability: 后有 三个斜杠,这点很多同学第一次会写错。)(华为开发者官网)

2) 组件声明中的 urivisible

config.json(或等价的模块配置)里,提供方需要给 DataAbility 声明一个基础 uri,以及是否对外可见:

  • "type": "data"(标识它是 DataAbility);
  • "uri": "dataability:///com.example.note.provider"
  • "visible": true(对其他应用可见,才能被外部访问)。(华为开发者官网)

Tips:URI 只是“入口前缀”,你依旧可以在 path 里细化到表/行级路径,例如 /notes/notes/123 等,配合谓词实现复杂筛选。

三、数据访问权限:静态 + 动态,双保险

DataAbility 的权限控制分两层:

  • 静态权限(安装前/拉起时检查):在 config.json 中配置 readPermission / writePermission
  • 动态权限(真正操作时检查):按接口类型判断“读/写”权限是否满足。官方文档明确说明了这两块。(华为开发者官网)

1) 静态权限(配置在提供方)

{
  "module": {
    "abilities": [
      {
        "name": ".NoteDataAbility",
        "type": "data",
        "uri": "dataability:///com.example.note.provider",
        "visible": true,
        "readPermission": "ohos.permission.READ_NOTES",
        "writePermission": "ohos.permission.WRITE_NOTES"
      }
    ]
  }
}
  • 含义:没有 READ_NOTES 的客户端,拉起后也读不到;没有 WRITE_NOTES 的客户端,不能改
  • 注意:客户端还需要在自身的配置里 声明使用这些权限(通常是 requestPermissions / 安装授权等),否则会出现常见的 Permission Denied。(华为开发者官网)

2) 动态权限(按接口“读/写”判定)

  • 读权限接口:query/normalizeUri/denormalizeUri/openFile('r') 等;
  • 写权限接口:insert/batchInsert/update/delete/openFile('w') 等;
  • 有的接口(如 executeBatch)会按子操作判定权限。具体映射在官方/社区资料均有一致说明。(华为开发者官网)

经验谈:读写分权尽量设计得“开口小”一些;真的需要“可写”的访问方,要么签白名单、要么多一层业务网关,别把数据表“裸暴露”。

四、实战|从 0 写一个可共享的 Note DataAbility

需求:提供方 App 暴露一个“便签表”,可被外部 App 插入/查询/更新。内部用 RDB 存储。

1) 提供方:DataAbility(实现 insert/query/update)

// src/main/ets/ability/NoteDataAbility.ets
import dataAbility from '@ohos.data.dataAbility'
import rdb from '@ohos.data.rdb'
import hilog from '@ohos.hilog'

const TAG = 'NoteDataAbility'
const TABLE = 'notes'

export default class NoteDataAbility extends dataAbility.DataAbility {
  private store?: rdb.RdbStore

  async onInitialize() {
    hilog.info(0x0, TAG, 'onInitialize')
    const config: rdb.StoreConfig = { name: 'notes.db', securityLevel: rdb.SecurityLevel.S1 }
    this.store = await rdb.getRdbStore(this.context, config, 1, (db: rdb.RdbStore) => {
      db.executeSql(`CREATE TABLE IF NOT EXISTS ${TABLE} (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        content TEXT,
        updated_at INTEGER
      )`)
    })
  }

  // INSERT
  async insert(uri: string, value: dataAbility.ValuesBucket): Promise<number> {
    this.assertWritable('insert')
    const now = Date.now()
    value.putLong('updated_at', now)
    const row = await this.store!.insert(TABLE, value)
    hilog.info(0x0, TAG, `insert row=${row}`)
    // 通知变更(可选)
    this.notifyChange(uri)
    return row
  }

  // QUERY
  async query(
    uri: string,
    predicates: dataAbility.DataAbilityPredicates,
    columns: Array<string>
  ): Promise<dataAbility.ResultSet> {
    this.assertReadable('query')
    const rs = await this.store!.query(TABLE, columns.length ? columns : ['*'], predicates)
    return rs
  }

  // UPDATE
  async update(
    uri: string,
    value: dataAbility.ValuesBucket,
    predicates: dataAbility.DataAbilityPredicates
  ): Promise<number> {
    this.assertWritable('update')
    value.putLong('updated_at', Date.now())
    const changed = await this.store!.update(value, TABLE, predicates)
    this.notifyChange(uri)
    return changed
  }

  // (需要的话再实现 delete/batchInsert/executeBatch)

  private assertReadable(op: string) { if (!this.store) throw new Error(`Store not ready: ${op}`) }
  private assertWritable(op: string) { if (!this.store) throw new Error(`Store not ready: ${op}`) }
}

你会发现:DataAbility 的形态跟“数据库 DAO 层”很像,但它的入口从函数变成了 URI + 方法,从而满足“跨应用调用”的约束。官方“创建 DataAbility”的文档也强调了实现 insert/query/update/delete 即可覆盖大多数场景,批量操作(batchInsert/executeBatch)会基于这些基础能力完成遍历。(华为开发者官网)

2) 访问方:DataAbilityHelper 的“增、查、改”

// src/main/ets/model/NoteRepo.ets
import featureAbility from '@ohos.ability.featureAbility'   // 获取 DataAbilityHelper
import dataAbility from '@ohos.data.dataAbility'

const NOTE_URI = 'dataability:///com.example.note.provider/notes'

export class NoteRepo {
  private helper?: dataAbility.DataAbilityHelper

  async init() {
    this.helper = featureAbility.acquireDataAbilityHelper(NOTE_URI)
  }

  async add(title: string, content: string) {
    const v = new dataAbility.ValuesBucket()
    v.putString('title', title)
    v.putString('content', content)
    const rowId = await this.helper!.insert(NOTE_URI, v)
    return rowId
  }

  async list(keyword = ''): Promise<Array<{ id: number; title: string; content: string }>> {
    const pred = new dataAbility.DataAbilityPredicates()
    if (keyword) pred.like('title', `%${keyword}%`)
    const rs = await this.helper!.query(NOTE_URI, pred, ['id', 'title', 'content'])
    const out: any[] = []
    while (rs.goToNextRow()) {
      out.push({
        id: rs.getLong(rs.getColumnIndex('id')),
        title: rs.getString(rs.getColumnIndex('title')),
        content: rs.getString(rs.getColumnIndex('content')),
      })
    }
    rs.close()
    return out
  }

  async rename(id: number, newTitle: string) {
    const v = new dataAbility.ValuesBucket()
    v.putString('title', newTitle)
    const pred = new dataAbility.DataAbilityPredicates().equalTo('id', id)
    const changed = await this.helper!.update(NOTE_URI, v, pred)
    return changed
  }
}

这一套 DataAbilityHelper 的获取与调用方式,是官方 API 的标准范式:acquireDataAbilityHelper(uri) → insert/query/update/delete。你可以把它当成“定位到某个 provider 的数据端点,再执行操作”。(华为开发者官网)

五、把“URI 机制 + 权限 + 读写”拉通,再谈跨设备

DataAbility 的 URI 天生支持 跨设备寻址dataability://{device_id}/...)。要真正跨设备访问,前置依赖是设备间可信关系/组网等协同前提,这通常由系统协同层完成;就 URI 语义而言,写法如上一节所示。具体到你项目:

  • 若你的应用群已覆盖多设备(手机/平板/PC/…),且存在同账号可信,就可以按目标设备 ID 构造 URI;
  • 数据层仍按 insert/query/update 调用,只不过“路由”到了另一台设备上的 DataAbility。

URI 写法与配置见官方 DataAbility 组件配置与 URI 说明。(华为开发者官网)

六、工程级实践:可维护、可演进、可上线

1) 版本/模型选择建议

  • 全新 Stage 项目:若你一开始就走 Stage,可以评估 DataShare 体系(服务端用 DataShareExtensionAbility,客户端用 dataShare 模块),在“Stage-only 环境”里更贴新版 API。(华为开发者官网)
  • 既有 FA 项目:优先按本文方案实现 DataAbility;如果未来切 Stage,再在“URI & 调用层”做一层适配封装,把“数据端点”抽象出来,避免散落在业务代码里逐个替换。

2) 协议 & 约束:不要把表结构直接裸露成接口

  • 声明一个稳定的“数据契约”(表名、列名、可过滤字段、返回列清单),像“接口文档”一样发给接入方;
  • 对外只公开必要列,敏感字段别公开(或返回脱敏值);
  • 统一失败码/错误信息(例如 PERMISSION_DENIEDILLEGAL_ARGUMENTNOT_FOUND),方便对方排查。

3) 性能与稳定性

  • 分页/游标:大表查询一定要分页;
  • 变更通知:写操作后 notifyChange(uri),让订阅方收到更新,少轮询;
  • 索引:对查询高频的列(如 title)建索引,不要让对方“搜一次卡半天”;
  • 崩溃兜底:insert/update 里永远做输入校验,避免对方传个奇怪的值把你库“打花”。

4) 安全复盘

  • 静态权限 + 动态权限 双重校验,拒绝“偷着写”;
  • 对写操作记录 审计日志(调用方包名、时间、影响行数);
  • 若涉及用户隐私,二次确认/告知机制别省。

七、常见坑位(都是“被 Permission Denied 打醒”后记下的)

  1. visible 忘了设 true:外部 App 永远拉不到你这端;
  2. URI 拼错:尤其是本地设备 dataability:///三个斜杠
  3. 只配了静态权限,客户端没声明/没授权:拉起能拉起,操作还是 Permission Denied
  4. ResultSet 没 close():内存与句柄泄漏;
  5. 值类型与列类型不匹配:比如把时间戳当字符串塞,查询排序全乱;
  6. 忘了索引:模糊搜索一打就“半分钟没回音”。

第 3 条最常见,官方/社区问答里也反复强调“服务端的 read/writePermission + 客户端的权限声明”要同时满足。(华为开发者官网)

八、(可选)把这套“对外数据层”做成可迁移的“适配器”

目标:无论 DataAbility(FA)还是 DataShare(Stage),上层业务只面向一个“数据端点接口”。

// adapter/Endpoint.ts
export interface Endpoint {
  insert(path: string, values: Record<string, any>): Promise<number>
  query(path: string, where?: { [k: string]: any }, columns?: string[]): Promise<any[]>
  update(path: string, values: Record<string, any>, where?: { [k: string]: any }): Promise<number>
}
  • DataAbilityEndpoint:内部用 DataAbilityHelper
  • DataShareEndpoint:内部用 dataShare 的 publish/get/update 等;
    这样你能在一个开关下切换实现,迁移成本立刻可控。(DataShare API 参考官方文档)(华为开发者官网)

九、完整最小示例(清单)

  • Provider(提供方 App)

  • Consumer(访问方 App)

    • manifest:声明使用 ohos.permission.READ_NOTES/WRITE_NOTES
    • NoteRepo.etsacquireDataAbilityHelperinsert/query/update。(华为开发者官网)

十、收个尾:把“共享数据”当成 产品能力,而不是“临时通道”

我最喜欢 DataAbility 的地方,是它把“跨应用数据共享”从“个案协商”变成了“平台化契约”:URI 可寻址、权限可校验、接口有边界。你不需要在每个接入方面前再解释一次“我这张表长啥样、我这口子怎么开”,用稳定的 URI + 权限模型 + 增删改查就能把合作关系变得可维护

参考(要点直达)

  • DataAbilityHelper 官方 API(获取 Helper 并执行 insert/query/update 等)。(华为开发者官网)
  • 创建 DataAbility(实现 insert/query/update/delete,批量操作依赖基础 CRUD)。(华为开发者官网)
  • DataAbility 组件配置与 URI 说明dataability scheme、device_idvisibleuri 等)。(华为开发者官网)
  • DataAbility 权限控制(静态 readPermission/writePermission + 动态按接口校验)。(华为开发者官网)
  • DataShare(Stage 路线)API(Stage 模型下的数据共享接口族,可作为 DataAbility 的替代方案)。(华为开发者官网)

(未完待续)

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐