系列文章《鸿蒙开发从入门到专家》第 56 篇 · 高级
前面讲的都是应用内部的事。这篇讲应用之间:A 应用怎么读写 B 应用的数据,跨进程通信在鸿蒙里怎么实现。
鸿蒙的跨进程通信不是直接调 Binder(Android 的方式),而是通过系统提供的 ExtensionAbility 框架。其中 DataShareExtensionAbility 是专门用于跨应用数据共享的扩展能力。
一、为什么需要跨进程通信
几个实际场景:
输入法应用需要读取联系人应用的通讯录数据。
系统设置应用需要修改壁纸应用提供的图片列表。
企业应用 A 需要获取应用 B 维护的审批流程数据。
多个应用共享同一份配置(如公司内部所有应用共享登录态)。
这些场景的共同点:数据所有者是应用 B,应用 A 需要访问。直接读 B 的沙盒文件不可能(应用沙盒隔离),需要通过系统提供的安全通道。
二、DataShareExtension 架构
┌──────────────────────────────────────────────────────┐
│ 数据访问方(应用 A) │
│ │
│ DataShareHelper │
│ ├── createDataShareHelper(uri) │
│ ├── query(uri, predicates, columns) │
│ ├── insert(uri, value) │
│ ├── update(uri, predicates, value) │
│ └── delete(uri, predicates) │
│ │
├──────────────── IPC 通信 ────────────────────────────┤
│ │
├──────────────────────────────────────────────────────┤
│ 数据提供方(应用 B) │
│ │
│ DataShareExtensionAbility │
│ ├── onCreate(want, callback) ← 初始化 │
│ ├── query(uri, predicates, ...) ← 查询 │
│ ├── insert(uri, value, ...) ← 插入 │
│ ├── update(uri, predicates, ...) ← 更新 │
│ ├── delete(uri, predicates, ...) ← 删除 │
│ └── batchInsert(...) ← 批量插入 │
│ │
│ 底层数据存储(RDB / 文件 / 内存) │
└──────────────────────────────────────────────────────┘
数据提供方(应用 B)实现 DataShareExtensionAbility,对外暴露增删改查接口。数据访问方(应用 A)通过 DataShareHelper 创建连接,像操作数据库一样操作对方的数据。中间通过 IPC 完成跨进程通信。
需要注意:DataShareExtensionAbility 的数据提供方目前仅限系统应用。第三方应用可以作为数据访问方使用 DataShareHelper,但不能自己实现 DataShareExtensionAbility 提供数据。
三、数据提供方实现
3.1 创建 ExtensionAbility
// DataShareExtAbility.ets
import { DataShareExtensionAbility, dataShare, dataSharePredicates,
relationalStore, DataShareResultSet } from '@kit.ArkData';
import { Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const DB_NAME = 'shared_data.db';
const TBL_NAME = 'contacts';
const DDL = `CREATE TABLE IF NOT EXISTS ${TBL_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
phone TEXT NOT NULL,
email TEXT
)`;
let rdbStore: relationalStore.RdbStore;
export default class DataShareExtAbility extends DataShareExtensionAbility {
// 初始化:创建数据库和表
onCreate(want: Want, callback: Function): void {
hilog.info(0x0000, 'DataShare', 'onCreate');
relationalStore.getRdbStore(this.context, {
name: DB_NAME,
securityLevel: relationalStore.SecurityLevel.S3
}, (err: BusinessError, data: relationalStore.RdbStore) => {
if (err) {
hilog.error(0x0000, 'DataShare', 'getRdbStore failed: %{public}s', err.message);
callback();
return;
}
rdbStore = data;
rdbStore.executeSql(DDL, [], (err) => {
if (err) {
hilog.error(0x0000, 'DataShare', 'executeSql failed: %{public}s', err.message);
}
callback();
});
});
}
// 查询
query(uri: string, predicates: dataSharePredicates.DataSharePredicates,
columns: Array<string>, callback: Function): void {
hilog.info(0x0000, 'DataShare', 'query: %{public}s', uri);
if (!rdbStore) {
callback(null, null);
return;
}
rdbStore.query(TBL_NAME, predicates, columns, (err, resultSet) => {
if (err) {
hilog.error(0x0000, 'DataShare', 'query failed: %{public}s', err.message);
callback(err, null);
} else {
callback(null, resultSet);
}
});
}
// 插入
insert(uri: string, value: relationalStore.ValuesBucket,
callback: Function): void {
hilog.info(0x0000, 'DataShare', 'insert: %{public}s', uri);
rdbStore.insert(TBL_NAME, value, (err, rowId) => {
if (err) {
callback(err, -1);
} else {
callback(null, rowId);
}
});
}
// 更新
update(uri: string, predicates: dataSharePredicates.DataSharePredicates,
value: relationalStore.ValuesBucket, callback: Function): void {
rdbStore.update(TBL_NAME, value, predicates, (err, rowId) => {
callback(err, rowId);
});
}
// 删除
delete(uri: string, predicates: dataSharePredicates.DataSharePredicates,
callback: Function): void {
rdbStore.delete(TBL_NAME, predicates, (err, rowId) => {
callback(err, rowId);
});
}
}
3.2 配置 module.json5
{
"module": {
"name": "provider_app",
"type": "feature",
"extensionAbilities": [
{
"name": "DataShareExtAbility",
"type": "dataShare",
"uri": "datashare://com.example.provider_app/contacts",
"metadata": [
{
"name": "ohos.extension.dataShare",
"resource": "$profile:data_share_config"
}
]
}
]
}
}
data_share_config.json:
{
"visibility": "private",
"readPermission": "ohos.permission.READ_CONTACTS",
"writePermission": "ohos.permission.WRITE_CONTACTS"
}
readPermission/writePermission:声明访问方需要持有的权限。visibility:private表示仅持有权限的应用可访问,public表示所有应用可访问。
四、数据访问方实现
// consumer/ConsumerPage.ets
import { dataShare, dataSharePredicates } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
const PROVIDER_URI = 'datashare://com.example.provider_app/contacts';
@Entry
@Component
struct ConsumerPage {
@State contacts: Contact[] = [];
private helper: dataShare.DataShareHelper | null = null;
async aboutToAppear(): Promise<void> {
const context = getContext(this) as common.UIAbilityContext;
// 创建 DataShareHelper
this.helper = await dataShare.createDataShareHelper(context, PROVIDER_URI);
// 查询所有联系人
await this.queryContacts();
}
async queryContacts(): Promise<void> {
if (!this.helper) return;
const predicates = new dataSharePredicates.DataSharePredicates();
const columns = ['id', 'name', 'phone', 'email'];
const resultSet = await this.helper.query(PROVIDER_URI, predicates, columns);
const list: Contact[] = [];
while (resultSet.goToNextRow()) {
list.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
phone: resultSet.getString(resultSet.getColumnIndex('phone')),
email: resultSet.getString(resultSet.getColumnIndex('email'))
});
}
this.contacts = list;
resultSet.close();
}
async addContact(name: string, phone: string): Promise<void> {
if (!this.helper) return;
const value: relationalStore.ValuesBucket = {
'name': name,
'phone': phone
};
await this.helper.insert(PROVIDER_URI, value);
await this.queryContacts(); // 刷新列表
}
build() {
Column() {
// 列表渲染
List() {
ForEach(this.contacts, (item: Contact) => {
ListItem() {
Column() {
Text(item.name).fontSize(16)
Text(item.phone).fontSize(14).fontColor('#666')
}
}
})
}
Button('添加联系人')
.onClick(() => {
this.addContact('张三', '13800001111');
})
}
}
}
五、条件查询
DataSharePredicates 支持丰富的条件查询:
async searchContacts(keyword: string): Promise<void> {
if (!this.helper) return;
const predicates = new dataSharePredicates.DataSharePredicates();
// WHERE name LIKE '%keyword%' OR phone LIKE '%keyword%'
predicates.beginsWith('name', keyword);
predicates.or();
predicates.beginsWith('phone', keyword);
// 排序
predicates.orderByAsc('name');
// 分页
predicates.limitAs(20);
predicates.offsetAs(0);
const columns = ['id', 'name', 'phone', 'email'];
const resultSet = await this.helper.query(PROVIDER_URI, predicates, columns);
// ... 处理结果集
}
六、权限处理
访问方在使用 DataShareHelper 之前,需要声明并请求对应权限:
// module.json5
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.READ_CONTACTS",
"reason": "$string:read_contacts_reason"
},
{
"name": "ohos.permission.WRITE_CONTACTS",
"reason": "$string:write_contacts_reason"
}
]
}
}
代码中动态请求:
import { abilityAccessCtrl } from '@kit.AbilityKit';
async function ensurePermissions(): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
const context = getContext(this) as common.UIAbilityContext;
const permissions = ['ohos.permission.READ_CONTACTS', 'ohos.permission.WRITE_CONTACTS'];
try {
const result = await atManager.requestPermissionsFromUser(context, permissions);
return result.authStatuses.every(status => status === 0);
} catch (e) {
console.error('Permission request failed: ' + e);
return false;
}
}
七、对比其他跨进程方案
鸿蒙有多种跨进程/跨应用通信方式,各有适用场景:
DataShareExtension 的优势在于:访问方像操作本地数据库一样操作远端数据,API 友好。劣势是只支持 CRUD 语义,不适合做实时消息推送或复杂方法调用。
八、踩坑记录
坑 1:URI 格式必须精确匹配。 DataShareHelper 的 URI 必须和提供方 module.json5 中声明的 URI 完全一致,包括 scheme(datashare://)和路径。少一个斜杠都连不上,且报错信息不明确。
坑 2:初始化异步等待。 onCreate 是异步的(创建数据库需要时间),如果在 onCreate 的 callback 执行之前就收到 query 请求,rdbStore 可能还没准备好。要在 onCreate 中确保 callback 被调用,框架才会认为初始化完成。
坑 3:结果集必须关闭。 DataShareResultSet 使用共享内存实现,不调用 close() 会导致内存泄漏。特别是在循环查询场景,每次查完都要 close。
坑 4:DataShareHelper 生命周期。 Helper 创建后会持有连接,页面销毁时需要释放。目前没有显式的 release 方法,Helper 对象被 GC 时自动断开连接。但如果 Helper 被长期持有(如放在单例中),连接不会断开。
小结
跨应用数据共享在鸿蒙里走 DataShareExtension + DataShareHelper 这条路。提供方实现 ExtensionAbility 的 CRUD 回调,访问方通过 Helper 像操作本地数据库一样操作远端数据。关键注意权限配置、URI 精确匹配和结果集关闭。第三方应用目前只能做访问方,提供方限系统应用。
下篇讲内存管理。Allocation 工具怎么用、泄漏怎么排查、组件生命周期怎么治理。
参考阅读:
第 23 篇:鸿蒙 RDB 数据库进阶
第 24 篇:鸿蒙文件管理与文档处理
官方文档:DataShareExtensionAbility、DataShareHelper