提交 ffd8ddca 编写于 作者: Z zengyawen

update docs

Signed-off-by: Nzengyawen <zengyawen1@huawei.com>
上级 69fc5d83
# 数据管理 # 数据管理
- 分布式数据服务 - [数据管理概述](data-mgmt-overview.md)
- [分布式数据服务概述](database-mdds-overview.md) - 应用数据持久化
- [分布式数据服务开发指导](database-mdds-guidelines.md) - [应用数据持久化概述](app-data-persistence-overview.md)
- 关系型数据库 - [通过用户首选项实现数据持久化](data-persistence-by-preferences.md)
- [关系型数据库概述](database-relational-overview.md) - [通过键值型数据库实现数据持久化](data-persistence-by-kv-store.md)
- [关系型数据库开发指导](database-relational-guidelines.md) - [通过关系型数据库实现数据持久化](data-persistence-by-rdb-store.md)
- 首选项 - 同应用跨设备数据同步(分布式)
- [首选项概述](database-preference-overview.md) - [同应用跨设备数据同步概述](sync-app-data-across-devices-overview.md)
- [首选项开发指导](database-preference-guidelines.md) - [键值型数据库跨设备数据同步](data-sync-of-kv-store.md)
- 分布式数据对象 - [关系型数据库跨设备数据同步](data-sync-of-rdb-store.md)
- [分布式数据对象概述](database-distributedobject-overview.md) - [分布式数据对象跨设备数据同步](data-sync-of-distributed-data-object.md)
- [分布式数据对象开发指导](database-distributedobject-guidelines.md) - 数据可靠性与安全性
- 数据共享 - [数据可靠性与安全性概述](data-reliability-security-overview.md)
- [数据共享概述](database-datashare-overview.md) - [数据库备份与恢复](data-backup-and-restore.md)
- [数据共享开发指导](database-datashare-guidelines.md) - [数据库加密](data-encryption.md)
- [基于设备分类和数据分级的访问控制](access-control-by-device-and-data-level.md)
- 同设备跨应用数据共享(仅对系统应用开放)
- [同设备跨应用数据共享概述](share-device-data-across-apps-overview.md)
- [通过DataShareExtensionAbility实现数据共享](share-data-by-datashareextensionability.md)
- [通过静默数据访问实现数据共享](share-data-by-silent-access.md)
# 基于设备分类和数据分级的访问控制
## 基本概念
分布式数据管理对数据实施分类分级保护,提供基于数据安全标签以及设备安全等级的访问控制机制。
数据安全标签和设备安全等级越高,加密措施和访问控制措施越严格,数据安全性越高。
### 数据安全标签
按照数据分类分级规范要求,可将数据分为S1、S2、S3、S4四个安全等级。
| 风险等级 | 风险标准 | 定义 | 样例 |
| -------- | -------- | -------- | -------- |
| 严重 | S4 | 业界法律法规定义的特殊数据类型,涉及个人的最私密领域的信息或一旦泄露、篡改、破坏、销毁可能会给个人或组织造成重大的不利影响的数据。 | 政治观点、宗教和哲学信仰、工会成员资格、基因数据、生物信息、健康和性生活状况,性取向等或设备认证鉴权、个人信用卡等财物信息等。 |
| 高 | S3 | 数据的泄露、篡改、破坏、销毁可能会给个人或组织导致严峻的不利影响 | 个人实时精确定位信息、运动轨迹等。 |
| 中 | S2 | 数据的泄露、篡改、破坏、销毁可能会给个人或组织导致严重的不利影响 | 个人的详细通信地址、姓名昵称等。 |
| 低 | S1 | 数据的泄露、篡改、破坏、销毁可能会给个人或组织导致有限的不利影响 | 性别、国籍、用户申请记录等。 |
### 设备安全等级
根据设备安全能力,比如是否有TEE、是否有安全存储芯片等,将设备安全等级分为SL1、SL2、SL3、SL4、SL5五个等级。例如,开发板rk3568、hi3516为低安全的SL1设备,平板通常为高安全的SL4设备。
在设备组网时可以通过`hidumper -s 3511`查看设备安全等级,例如,rk3568设备的安全等级查询如下:
![zh-cn_image_0000001542496993](figures/zh-cn_image_0000001542496993.png)
## 跨设备同步访问控制机制
数据跨设备同步时,数据管理基于数据安全标签和设备安全等级进行访问控制。规则为,在本设备的数据安全标签不高于对端设备的设备安全等级时,数据才能从本设备同步到对端设备,否则不能同步。具体访问控制矩阵如下:
| | | | | | |
| -------- | -------- | -------- | -------- | -------- | -------- |
| 设备安全级别 | SL1 | SL2 | SL3 | SL4 | SL5 |
| 可同步的数据安全标签 | S1 | S1~S2 | S1~S3 | S1~S4 | S1~S4 |
例如,对于类似rk3568、hi3516的开发板设备,设备安全等级为SL1。若创建数据安全标签为S1的数据库,则此数据库数据可以在这些设备间同步;若创建的数据库标签为S2-S4,则不能在这些设备间同步。
## 场景介绍
分布式数据库的访问控制机制确保了数据存储和同步时的安全能力。在创建数据库时,应当基于数据分类分级规范合理地设置数据库的安全标签,确保数据库内容和数据标签的一致性。
## 使用键值型数据库实现数据分级
键值型数据库,通过securityLevel参数设置数据库的安全等级。此处以创建安全等级为S1的数据库为例。
具体接口及功能,可见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
let kvManager;
let context = getContext(this);
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest'
}
try {
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
let kvStore;
try {
const options = {
createIfMissing: true,
encrypt: true,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
kvManager.getKVStore('storeId', options, (err, store) => {
if (err) {
console.error(`Failed to get KVStore. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
kvStore = store;
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
## 使用关系型数据库实现数据分级
关系型数据库,通过securityLevel参数设置数据库的安全等级。此处以创建安全等级为S1的数据库为例。
具体接口及功能,可见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
```js
import relationalStore from '@ohos.data.relationalStore';
let store;
const STORE_CONFIG = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S1
};
let promise = relationalStore.getRdbStore(this.context, STORE_CONFIG);
promise.then(async (rdbStore) => {
store = rdbStore;
console.info('Succeeded in getting RdbStore.')
}).catch((err) => {
console.error(`Failed to get RdbStore. Code:${err.code},message:${err.message}`);
})
```
# 应用数据持久化概述
应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。
OpenHarmony标准系统支持典型的存储数据形态,包括用户首选项、键值型数据库、关系型数据库。
开发者可以根据如下功能介绍,选择合适的数据形态以满足自己应用数据的持久化需要。
- **用户首选项(Preferences)**:通常用于保存应用的配置信息。数据通过文本的形式保存在设备中,应用使用过程中会将文本中的数据全量加载到内存中,所以访问速度快、效率高,但不适合需要存储大量数据的场景。
- **键值型数据库(KV-Store)**:一种非关系型数据库,其数据以“键值”对的形式进行组织、索引和存储,其中“键”作为唯一标识符。适合很少数据关系和业务关系的业务数据存储,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。相比于关系型数据库,更容易做到跨设备跨版本兼容。
- **关系型数据库(RelationalStore)**:一种关系型数据库,以行和列的形式存储数据,广泛用于应用中的关系型数据的处理,包括一系列的增、删、改、查等接口,开发者也可以运行自己定义的SQL语句来满足复杂业务场景的需要。
# 数据库备份与恢复
## 场景介绍
当应用在处理一项重要的操作,显然是不能被打断的。例如:写入多个表关联的事务。此时,每个表的写入都是单独的,但是表与表之间的事务关联性不能被分割。
如果操作的过程中出现问题,开发者可以使用恢复功能,将数据库恢复到之前的状态,重新对数据库进行操作。
在数据库被篡改、删除、或者设备断电场景下,数据库可能会因为数据丢失、数据损坏、脏数据等而不可用,可以通过数据库的备份恢复能力将数据库恢复至可用状态。
键值型数据库和关系型数据库均支持对数据库的备份和恢复。另外,键值型数据库还支持删除数据库备份,以释放本地存储空间。
## 键值型数据库备份、恢复与删除
键值型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复,通过deletebackup接口删除数据库备份。具体接口及功能,可见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
1. 创建数据库。
(1) 创建kvManager。
(2) 配置数据库参数。
(3) 创建kvStore。
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
let kvManager;
let context = getContext(this);
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest'
}
try {
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
let kvStore;
try {
const options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S2
};
kvManager.getKVStore('storeId', options, (err, store) => {
if (err) {
console.error(`Fail to get KVStore. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
kvStore = store;
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
2. 使用put()方法插入数据。
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Fail to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
3. 使用backup()方法备份数据。
```js
let file = 'BK001';
try {
kvStore.backup(file, (err) => {
if (err) {
console.error(`Fail to backup data.code:${err.code},message:${err.message}`);
} else {
console.info('Succeeded in backupping data.');
}
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
4. 使用delete()方法删除数据(模拟意外删除、篡改场景)。
```js
try {
kvStore.delete(KEY_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Fail to delete data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in deleting data.');
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
5. 使用restore()方法恢复数据。
```js
let file = 'BK001';
try {
kvStore.restore(file, (err) => {
if (err) {
console.error(`Fail to restore data. Code:${err.code},message:${err.message}`);
} else {
console.info('Succeeded in restoring data.');
}
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
6. 当本地设备存储空间有限或需要重新备份时,还可使用deleteBackup()方法删除备份,释放存储空间。
```js
let kvStore;
let files = ['BK001'];
try {
kvStore.deleteBackup(files).then((data) => {
console.info(`Succeed in deleting Backup. Data:filename is ${data[0]},result is ${data[1]}.`);
}).catch((err) => {
console.error(`Fail to delete Backup. Code:${err.code},message:${err.message}`);
})
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
## 关系型数据库备份与恢复
关系型数据库,通过backup接口实现数据库备份,通过restore接口实现数据库恢复。具体接口及功能,可见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
1. 使用getRdbStore()方法创建关系型数据库。
```js
import relationalStore from '@ohos.data.relationalStore';
let store;
let context = getContext(this);
const STORE_CONFIG = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S1
};
relationalStore.getRdbStore(context, STORE_CONFIG, (err, rdbStore) => {
store = rdbStore;
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code},message:${err.message}`);
return;
}
store.executeSql("CREATE TABLE IF NOT EXISTS EMPLOYEE (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, salary INTEGER, codes Uint8Array);", null);
console.info('Succeeded in getting RdbStore.');
})
```
2. 使用insert()方法插入数据。
```js
const valueBucket = {
'NAME': 'Lisa',
'AGE': 18,
'SALARY': 100.5,
'CODES': new Uint8Array([1, 2, 3, 4, 5])
};
store.insert('EMPLOYEE', valueBucket, relationalStore.ConflictResolution.ON_CONFLICT_REPLACE, (err, rowId) => {
if (err) {
console.error(`Failed to insert data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in inserting data. rowId:${rowId}`);
})
```
3. 使用backup()方法备份数据。
```js
store.backup('dbBackup.db', (err) => {
if (err) {
console.error(`Failed to backup data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in backuping data.`);
})
```
4. 使用delete()方法删除数据(模拟意外删除、篡改场景)。
```js
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo('NAME', 'Lisa');
let promise = store.delete(predicates);
promise.then((rows) => {
console.info(`Delete rows: ${rows}`);
}).catch((err) => {
console.error(`Failed to delete data. Code:${err.code},message:${err.message}`);
})
```
5. 使用restore()方法恢复数据。
```js
store.restore('dbBackup.db', (err) => {
if (err) {
console.error(`Failed to restore data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in restoring data.`);
})
```
# 数据库加密
## 场景介绍
为了增强数据库的安全性,数据库提供了一个安全适用的数据库加密能力,从而对数据库存储的内容实施有效保护。通过数据库加密等安全方法实现了数据库数据存储的保密性和完整性要求,使得数据库以密文方式存储并在密态方式下工作,确保了数据安全。
加密后的数据库只能通过接口进行访问,无法通过其它方式打开数据库文件。数据库的加密属性在创建数据库时确认,无法变更。
键值型数据库和关系型数据库均支持数据库加密操作。
## 键值型数据库加密
键值型数据库,通过options中encrypt参数来设置是否加密,默认为false,表示不加密。encrypt参数为true时表示加密。
具体接口及功能,可见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
let kvManager;
let context = getContext(this);
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest',
}
try {
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
let kvStore;
try {
const options = {
createIfMissing: true,
// 设置数据库加密
encrypt: true,
backup: false,
autoSync: true,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S2
};
kvManager.getKVStore('storeId', options, (err, store) => {
if (err) {
console.error(`Fail to get KVStore. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
kvStore = store;
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
## 关系型数据库加密
关系型数据库,通过StoreConfig中encrypt属性来设置是否加密,默认为false,表示不加密。encrypt参数为true时表示加密。
具体接口及功能,可见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
```js
import relationalStore from '@ohos.data.relationalStore';
let store;
let context = getContext(this);
const STORE_CONFIG = {
name: 'RdbTest.db',
securityLevel: relationalStore.SecurityLevel.S1,
encrypt: true
};
relationalStore.getRdbStore(context, STORE_CONFIG, (err, rdbStore) => {
store = rdbStore;
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in getting RdbStore.`);
})
```
# 数据管理概述
## 功能介绍
数据管理为开发者提供数据存储、数据管理和数据同步能力,比如联系人应用数据可以保存到数据库中,提供数据库的安全、可靠以及共享访问等管理机制,也支持与手表同步联系人信息。
- 数据存储:提供通用数据持久化能力,根据数据特点,分为用户首选项、键值型数据库和关系型数据库。
- 数据管理:提供高效的数据管理能力,包括权限管理、数据备份恢复、数据共享框架等。
- 数据同步:提供跨设备数据同步能力,比如分布式对象支持内存对象跨设备共享能力,分布式数据库支持跨设备数据库访问能力。
应用创建的数据库,都保存到应用沙盒,当应用卸载时,数据库也会自动删除。
## 运作机制
数据管理模块包括用户首选项、键值型数据管理、关系型数据管理、分布式数据对象和跨应用数据管理。Interface接口层提供标准JS API接口,定义这些部件接口描述,供开发者参考。Frameworks&amp;System service层负责实现部件数据存储、同步功能,还有一些SQLite和其他子系统的依赖。
**图1** 数据管理架构图  
![dataManagement](figures/dataManagement.jpg)
- 用户首选项(Preferences):提供了轻量级配置数据的持久化能力,并支持订阅数据变化的通知能力。不支持分布式同步,常用于保存应用配置信息、用户偏好设置等。
- 键值型数据管理(KV-Store):提供了键值型数据库的读写、加密、手动备份以及订阅通知能力。应用需要使用键值型数据库的分布式能力时,KV-Store会将同步请求发送给DatamgrService由其完成跨设备数据同步。
- 关系型数据管理(RelationalStore):提供了关系型数据库的增删改查、加密、手动备份以及订阅通知能力。应用需要使用关系型数据库的分布式能力时,RelationalStore部件会将同步请求发送给DatamgrService由其完成跨设备数据同步。
- 分布式数据对象(DataObject):独立提供对象型结构数据的分布式能力。如果应用需要重启后仍获取之前的对象数据(包含跨设备应用和本设备应用),则使用数据管理服务(DatamgrService)的对象持久化能力,做暂时保存。
- 跨应用数据管理(DataShare):提供了数据提供者provider、数据消费者consumer以及同设备跨应用数据交互的增、删、改、查以及订阅通知等能力。DataShare不与任何数据库绑定,可以对接关系型数据库、键值型数据库。如果开发C/C++应用甚至可以自行封装数据库。在提供标准的provider-consumer模式基础上,同时提供了静默数据访问能力,即不再拉起provider而是直接通过DatamgrService代理访问provider的数据(目前仅关系型数据库支持静默数据访问方式)。
- 数据管理服务(DatamgrService):提供其它部件的同步及跨应用共享能力,包括RelationalStore和KV-Store跨设备同步,DataShare静默访问provider数据,暂存DataObject同步对象数据等。
# 通过键值型数据库实现数据持久化
## 场景介绍
键值型数据库存储键值对形式的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应价格、员工工号及今日是否已出勤等,由于数据复杂度低,更容易兼容不同数据库版本和设备类型,因此推荐使用键值型数据库持久化此类数据。
## 约束限制
- 设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度&lt;4 MB。
- 单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度&lt;4 MB。
- 每个应用程序最多支持同时打开16个分布式数据库。
- 键值型数据库事件回调方法中不允许进行阻塞操作,例如修改UI组件。
## 接口说明
以下是键值型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
| 接口名称 | 描述 |
| -------- | -------- |
| createKVManager(config: KVManagerConfig): KVManager | 创建一个KVManager对象实例,用于管理数据库对象。 |
| getKVStore&lt;T&gt;(storeId: string, options: Options, callback: AsyncCallback&lt;T&gt;): void | 指定Options和storeId,创建并得到指定类型的KVStore数据库。 |
| put(key: string, value: Uint8Array\|string\|number\|boolean, callback: AsyncCallback&lt;void&gt;): void | 添加指定类型的键值对到数据库。 |
| get(key: string, callback: AsyncCallback&lt;Uint8Array\|string\|boolean\|number&gt;): void | 获取指定键的值。 |
| delete(key: string, callback: AsyncCallback&lt;void&gt;): void | 从数据库中删除指定键值的数据。 |
## 开发步骤
1. 若要使用键值型数据库,首先要获取一个KVManager实例,用于管理数据库对象。示例代码如下所示:
Stage模型示例:
```js
// 导入模块
import distributedKVStore from '@ohos.data.distributedKVStore';
// Stage模型
import UIAbility from '@ohos.app.ability.UIAbility';
let kvManager;
export default class EntryAbility extends UIAbility {
onCreate() {
let context = this.context;
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest'
};
try {
// 创建KVManager实例
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
// 继续创建获取数据库
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
}
}
```
FA模型示例:
```js
// 导入模块
import distributedKVStore from '@ohos.data.distributedKVStore';
// FA模型
import featureAbility from '@ohos.ability.featureAbility';
let kvManager;
let context = featureAbility.getContext(); // 获取context
const kvManagerConfig = {
context: context,
bundleName: 'com.example.datamanagertest'
};
try {
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
// 继续创建获取数据库
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
```
2. 创建并获取键值数据库。示例代码如下所示:
```js
try {
const options = {
createIfMissing: true, // 当数据库文件不存在时是否创建数据库,默认创建
encrypt: false, // 当数据库文件不存在时是否创建数据库,默认创建
backup: false, // 设置数据库文件是否备份,默认备份
autoSync: true, // 设置数据库文件是否自动同步。默认为false,即手动同步;设置为true时,表示自动同步
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION, // 设置要创建的数据库类型,默认为多设备协同数据库
securityLevel: distributedKVStore.SecurityLevel.S2 // 设置数据库安全级别
};
// storeId为数据库唯一标识符
kvManager.getKVStore('storeId', options, (err, kvStore) => {
if (err) {
console.error(`Failed to get KVStore. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
// 进行相关数据操作
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
3. 调用put()方法向键值数据库中插入数据。示例代码如下所示:
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
> **说明:**
>
> 当Key值存在时,put()方法会修改其值,否则新增一条数据。
4. 调用get()方法获取指定键的值。示例代码如下所示:
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
kvStore.get(KEY_TEST_STRING_ELEMENT, (err, data) => {
if (err !== undefined) {
console.error(`Failed to get data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in getting data. data:${data}`);
});
});
} catch (e) {
console.error(`Failed to get data. Code:${e.code},message:${e.message}`);
}
```
5. 调用delete()方法删除指定键值的数据。示例代码如下所示:
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
kvStore.delete(KEY_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to delete data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in deleting data.');
});
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
# 通过用户首选项实现数据持久化
## 场景介绍
用户首选项为应用提供Key-Value键值型的数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。当用户希望有一个全局唯一存储的地方,可以采用用户首选项来进行存储。Preferences会将该数据缓存在内存中,当用户读取的时候,能够快速从内存中获取数据。Preferences会随着存放的数据量越多而导致应用占用的内存越大,因此,Preferences不适合存放过多的数据,适用的场景一般为应用保存用户的个性化设置(字体大小,是否开启夜间模式)等。
## 运作机制
如图所示,用户程序通过JS接口调用用户首选项读写对应的数据文件。开发者可以将用户首选项持久化文件的内容加载到Preferences实例,每个文件唯一对应到一个Preferences实例,系统会通过静态容器将该实例存储在内存中,直到主动从内存中移除该实例或者删除该文件。
**图1** 用户首选项运作机制  
![preferences](figures/preferences.jpg)
## 约束限制
- Key键为string类型,要求非空且长度不超过80个字节。
- 如果Value值为string类型,可以为空,不为空时长度不超过8192个字节。
- 内存会随着存储数据量的增大而增大,所以存储的数据量应该是轻量级的,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销。
## 接口说明
以下是用户首选项持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[用户首选项](../reference/apis/js-apis-data-preferences.md)
| 接口名称 | 描述 |
| -------- | -------- |
| getPreferences(context: Context, name: string, callback: AsyncCallback&lt;Preferences&gt;): void | 获取Preferences实例。 |
| put(key: string, value: ValueType, callback: AsyncCallback&lt;void&gt;): void | 将数据写入Preferences实例,可通过flush将Preferences实例持久化。 |
| has(key: string, callback: AsyncCallback&lt;boolean&gt;): void | 检查Preferences实例是否包含名为给定Key的存储键值对。给定的Key值不能为空。 |
| get(key: string, defValue: ValueType, callback: AsyncCallback&lt;ValueType&gt;): void | 获取键对应的值,如果值为null或者非默认值类型,返回默认数据defValue。 |
| delete(key: string, callback: AsyncCallback&lt;void&gt;): void | 从Preferences实例中删除名为给定Key的存储键值对。 |
| flush(callback: AsyncCallback&lt;void&gt;): void | 将当前Preferences实例的数据异步存储到用户首选项持久化文件中。 |
| on(type: 'change', callback: Callback&lt;{ key : string }&gt;): void | 订阅数据变更,订阅的Key的值发生变更后,在执行flush方法后,触发callback回调。 |
| off(type: 'change', callback?: Callback&lt;{ key : string }&gt;): void | 取消订阅数据变更。 |
| deletePreferences(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void | 从内存中移除指定的Preferences实例。若Preferences实例有对应的持久化文件,则同时删除其持久化文件。 |
## 开发步骤
1. 导入`@ohos.data.preferences`模块。
```js
import dataPreferences from '@ohos.data.preferences';
```
2. 要通过用户首选项实现数据持久化,首先要获取Preferences实例。读取指定文件,将数据加载到Preferences实例,用于数据操作。
Stage模型示例:
```js
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
try {
dataPreferences.getPreferences(this.context, 'mystore', (err, preferences) => {
if (err) {
console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting preferences.');
// 进行相关数据操作
})
} catch (err) {
console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
}
}
}
```
FA模型示例:
```js
import featureAbility from '@ohos.ability.featureAbility';
// 获取context
let context = featureAbility.getContext();
try {
dataPreferences.getPreferences(context, 'mystore', (err, preferences) => {
if (err) {
console.error(`Failed to get preferences. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting preferences.');
// 进行相关数据操作
})
} catch (err) {
console.error(`Failed to get preferences. Code is ${err.code},message:${err.message}`);
}
```
3. 写入数据。
使用put()方法保存数据到缓存的Preferences实例中。在写入数据后,如有需要,可使用flush()方法将Preferences实例的数据存储到持久化文件。
> **说明:**
>
> 当对应的键已经存在时,put()方法会修改其值。如果仅需要在键值对不存在时新增键值对,而不修改已有键值对,需使用has()方法检查是否存在对应键值对;如果不关心是否会修改已有键值对,则直接使用put()方法即可。
示例代码如下所示:
```js
try {
preferences.has('startup', function (err, val) {
if (err) {
console.error(`Failed to check the key 'startup'. Code:${err.code}, message:${err.message}`);
return;
}
if (val) {
console.info("The key 'startup' is contained.");
} else {
console.info("The key 'startup' does not contain.");
// 此处以此键值对不存在时写入数据为例
try {
preferences.put('startup', 'auto', (err) => {
if (err) {
console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
})
} catch (err) {
console.error(`Failed to put data. Code: ${err.code},message:${err.message}`);
}
}
})
} catch (err) {
console.error(`Failed to check the key 'startup'. Code:${err.code}, message:${err.message}`);
}
```
4. 读取数据。
使用get()方法获取数据,即指定键对应的值。如果值为null或者非默认值类型,则返回默认数据。示例代码如下所示:
```js
try {
preferences.get('startup', 'default', (err, val) => {
if (err) {
console.error(`Failed to get value of 'startup'. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in getting value of 'startup'. val: ${val}.`);
})
} catch (err) {
console.error(`Failed to get value of 'startup'. Code:${err.code}, message:${err.message}`);
}
```
5. 删除数据。
使用delete()方法删除指定键值对,示例代码如下所示:
```js
try {
preferences.delete('startup', (err) => {
if (err) {
console.error(`Failed to delete the key 'startup'. Code:${err.code}, message:${err.message}`);
return;
}
console.info("Succeeded in deleting the key 'startup'.");
})
} catch (err) {
console.error(`Failed to delete the key 'startup'. Code:${err.code}, message:${err.message}`);
}
```
6. 数据持久化。
应用存入数据到Preferences实例后,可以使用flush()方法实现数据持久化。示例代码如下所示:
```js
try {
preferences.flush((err) => {
if (err) {
console.error(`Failed to flush. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in flushing.');
})
} catch (err) {
console.error(`Failed to flush. Code:${err.code}, message:${err.message}`);
}
```
7. 订阅数据变更。
应用订阅数据变更需要指定observer作为回调方法。订阅的Key值发生变更后,当执行flush()方法时,observer被触发回调。示例代码如下所示:
```js
let observer = function (key) {
console.info('The key' + key + 'changed.');
}
preferences.on('change', observer);
// 数据产生变更,由'auto'变为'manual'
preferences.put('startup', 'manual', (err) => {
if (err) {
console.error(`Failed to put the value of 'startup'. Code:${err.code},message:${err.message}`);
return;
}
console.info("Succeeded in putting the value of 'startup'.");
preferences.flush((err) => {
if (err) {
console.error(`Failed to flush. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in flushing.');
})
})
```
8. 删除指定文件。
使用deletePreferences()方法从内存中移除指定文件对应的Preferences实例,包括内存中的数据。若该Preference存在对应的持久化文件,则同时删除该持久化文件,包括指定文件及其备份文件、损坏文件。
> **说明:**
>
> - 调用该接口后,应用不允许再使用该Preferences实例进行数据操作,否则会出现数据一致性问题。
>
> - 成功删除后,数据及文件将不可恢复。
示例代码如下所示:
```js
try {
dataPreferences.deletePreferences(this.context, 'mystore', (err, val) => {
if (err) {
console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting preferences.');
})
} catch (err) {
console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
}
```
# 通过关系型数据库实现数据持久化
## 场景介绍
关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。
## 基本概念
- **谓词**:数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。
- **结果集**:指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便地拿到用户想要的数据。
## 运作机制
关系型数据库对应用提供通用的操作接口,底层使用SQLite作为持久化存储引擎,支持SQLite具有的数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译SQL语句。
**图1** 关系型数据库运作机制
![relationStore_local](figures/relationStore_local.jpg)
## 约束限制
- 系统默认日志方式是WAL(Write Ahead Log)模式,系统默认落盘方式是FULL模式。
- 数据库中连接池的最大数量是4个,用以管理用户的读操作。
- 为保证数据的准确性,数据库同一时间只能支持一个写操作。
- 当应用被卸载完成后,设备上的相关数据库文件及临时文件会被自动清除。
## 接口说明
以下是关系型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
| 接口名称 | 描述 |
| -------- | -------- |
| getRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback&lt;RdbStore&gt;): void | 获得一个相关的RdbStore,操作关系型数据库,用户可以根据自己的需求配置RdbStore的参数,然后通过RdbStore调用相关接口可以执行相关的数据操作。 |
| executeSql(sql: string, bindArgs: Array&lt;ValueType&gt;, callback: AsyncCallback&lt;void&gt;):void | 执行包含指定参数但不返回值的SQL语句。 |
| insert(table: string, values: ValuesBucket, callback: AsyncCallback&lt;number&gt;):void | 向目标表中插入一行数据。 |
| update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback&lt;number&gt;):void | 根据RdbPredicates的指定实例对象更新数据库中的数据。 |
| delete(predicates: RdbPredicates, callback: AsyncCallback&lt;number&gt;):void | 根据RdbPredicates的指定实例对象从数据库中删除数据。 |
| query(predicates: RdbPredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;ResultSet&gt;):void | 根据指定条件查询数据库中的数据。 |
| deleteRdbStore(context: Context, name: string, callback: AsyncCallback&lt;void&gt;): void | 删除数据库。 |
## 开发指导
1. 使用关系型数据库实现数据持久化,需要获取一个RdbStore。示例代码如下所示:
Stage模型示例:
```js
import relationalStore from '@ohos.data.relationalStore'; // 导入模块
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
const STORE_CONFIG = {
name: 'RdbTest.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1 // 数据库安全级别
};
const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)'; // 建表Sql语句
relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in getting RdbStore.`);
store.executeSql(SQL_CREATE_TABLE); // 创建数据表
// 这里执行数据库的增、删、改、查等操作
});
}
}
```
FA模型示例:
```js
import relationalStore from '@ohos.data.relationalStore'; // 导入模块
import featureAbility from '@ohos.ability.featureAbility';
// 获取context
let context = featureAbility.getContext();
const STORE_CONFIG = {
name: 'RdbTest.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1 // 数据库安全级别
};
const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)'; // 建表Sql语句
relationalStore.getRdbStore(context, STORE_CONFIG, (err, store) => {
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in getting RdbStore.`);
store.executeSql(SQL_CREATE_TABLE); // 创建数据表
// 这里执行数据库的增、删、改、查等操作
});
```
> **说明:**
>
> - 应用创建的数据库与其上下文(Context)有关,即使使用同样的数据库名称,但不同的应用上下文,会产生多个数据库,例如每个UIAbility都有各自的上下文。
>
> - 当应用首次获取数据库(调用getRdbStore)后,在应用沙箱内会产生对应的数据库文件。使用数据库的过程中,数据库文件相同的目录下,可以会产生以-wal和-shm结尾的临时文件,此时开发者希望移到数据库文件到其它地方使用可查看,需要同时移动这些临时文件。当应用被卸载完成后,其在设备上产生的数据库文件及临时文件也会被移除。
2. 获取到RdbStore后,调用insert()接口插入数据。示例代码如下所示:
```js
const valueBucket = {
'NAME': 'Lisa',
'AGE': 18,
'SALARY': 100.5,
'CODES': new Uint8Array([1, 2, 3, 4, 5])
};
store.insert('EMPLOYEE', valueBucket, (err, rowId) => {
if (err) {
console.error(`Failed to insert data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in inserting data. rowId:${rowId}`);
})
```
> **说明:**
>
> 关系型数据库没有显式的flush操作实现持久化,数据插入即保存在持久化文件。
3. 根据谓词指定的实例对象,对数据进行修改或删除。
调用update()方法修改数据,调用delete()方法删除数据。示例代码如下所示:
```js
// 修改数据
const valueBucket = {
'NAME': 'Rose',
'AGE': 22,
'SALARY': 200.5,
'CODES': new Uint8Array([1, 2, 3, 4, 5])
};
let predicates = new relationalStore.RdbPredicates('EMPLOYEE'); // 创建表'EMPLOYEE'的predicates
predicates.equalTo('NAME', 'Lisa'); // 匹配表'EMPLOYEE'中'NAME'为'Lisa'的字段
store.update(valueBucket, predicates, (err, rows) => {
if (err) {
console.error(`Failed to update data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in updating data. row count: ${rows}`);
})
// 删除数据
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo('NAME', 'Lisa');
store.delete(predicates, (err, rows) => {
if (err) {
console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Delete rows: ${rows}`);
})
```
4. 根据谓词指定的查询条件查找数据。
调用query()方法查找数据,返回一个ResultSet结果集。示例代码如下所示:
```js
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo('NAME', 'Rose');
store.query(predicates, ['ID', 'NAME', 'AGE', 'SALARY', 'CODES'], (err, resultSet) => {
if (err) {
console.error(`Failed to query data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`ResultSet column names: ${resultSet.columnNames}`);
console.info(`ResultSet column count: ${resultSet.columnCount}`);
})
```
> **说明:**
>
> 当应用完成查询数据操作,不再使用结果集(ResultSet)时,请及时调用close方法关闭结果集,释放系统为其分配的内存。
5. 删除数据库。
调用deleteRdbStore()方法,删除数据库及数据库相关文件。示例代码如下:
Stage模型示例:
```js
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
relationalStore.deleteRdbStore(this.context, 'RdbTest.db', (err) => {
if (err) {
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting RdbStore.');
});
}
}
```
FA模型示例:
```js
import featureAbility from '@ohos.ability.featureAbility';
// 获取context
let context = featureAbility.getContext();
relationalStore.deleteRdbStore(context, 'RdbTest.db', (err) => {
if (err) {
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting RdbStore.');
});
```
# 数据可靠性与安全性概述
## 功能场景
在系统运行中,存储损坏、存储空间不足、文件系统权限、系统掉电等都可能导致数据库发生故障。比如联系人应用的数据库损坏,导致用户的联系人丢失;日历应用的数据库损坏,导致丢失日历提醒等。为此数据管理提供了数据可靠性与安全性相关的解决方案和能力保障。
- 备份、恢复功能:重要业务应用(如银行)数据丢失,出现严重异常场景,可以通过备份恢复数据库,保证关键数据不丢失。
- 数据库加密功能:当数据库中存储如认证凭据、财务数据等高敏感信息时,可对数据库进行加密,提高数据库安全性。
- 数据库分类分级:数据跨设备同步时,数据管理基于数据安全标签和设备安全等级进行访问控制,保证数据安全。
另外,备份数据库存储在应用的沙箱内,当存储空间不足时,可以选择删除本地的数据库备份,释放空间。
## 基本概念
在进行数据可靠性与安全性相关功能的开发前,请先了解以下相关概念。
### 数据库备份与恢复
- **数据库备份**:指对当前数据库的数据库文件进行完整备份。OpenHarmony数据库备份针对数据库全量文件进行完整的备份。
在进行数据库备份的时候,无需关闭数据库,直接调用对应的数据库备份接口就能完成对数据库文件的备份。
- **数据库恢复**:从指定的备份文件恢复到当前数据库文件。恢复完成时,当前数据库数据恢复到和指定备份文件一致。
### 数据库加密
数据库加密是对整个数据库文件的加密,可以增强数据库的安全性,有效保护数据库内容。
### 数据库分类分级
分布式数据管理对数据实施分类分级保护,提供基于数据安全标签以及设备安全等级的访问控制机制。
数据安全标签和设备安全等级越高,加密措施和访问控制措施越严格,数据安全性越高。
## 运作机制
### 数据库备份与恢复机制
数据库在备份时,会将当前的数据库备份在指定的文件中,后续对数据库的操作不会影响备份的数据库文件。只有当恢复指定数据库文件时,才会将备份的数据库文件覆盖当前数据库,实现数据的回滚。
- 键值型数据库备份路径:/data/service/el1(el2)/public/database/...{appId}/kvdb/backup/...{storeId}
- 关系型数据库备份路径:/data/app/el1(el2)/100/database/...{bundlename}/rdb
### 数据库加密机制
OpenHarmony数据库加密时,应用开发者无需传入密钥,只需要设置数据库加密的状态即可。系统会自动帮助开发者将数据库加密,使用[huks通用密钥库系统](../reference/apis/js-apis-huks.md),完成数据库密钥的生成及加密保护。
## 约束限制
- 数据库加密的密钥一年自动更换一次。
- 键值型数据库最多可以备份5份。
- 键值型数据库的自动备份需要在熄屏且充电的状态下进行。
# 分布式数据对象跨设备数据同步
## 场景介绍
传统方式下,设备之间的数据同步,需要开发者完成消息处理逻辑,包括:建立通信链接、消息收发处理、错误重试、数据冲突解决等操作,工作量非常大。而且设备越多,调试复杂度也将同步增加。
其实设备之间的状态、消息发送进度、发送的数据等都是“变量”。如果这些变量支持“全局”访问,那么开发者跨设备访问这些变量就能像操作本地变量一样,从而能够自动高效、便捷地实现数据多端同步。
分布式数据对象即实现了对“变量”的“全局”访问。向应用开发者提供内存对象的创建、查询、删除、修改、订阅等基本数据对象的管理能力,同时具备分布式能力。为开发者在分布式应用场景下提供简单易用的JS接口,轻松实现多设备间同应用的数据协同,同时设备间可以监听对象的状态和数据变更。满足超级终端场景下,相同应用多设备间的数据对象协同需求。与传统方式相比,分布式数据对象大大减少了开发者的工作量。
## 基本概念
- **分布式内存数据库**
分布式内存数据库将数据缓存在内存中,以便应用获得更快的数据存取速度,不会将数据进行持久化。若数据库关闭,则数据不会保留。
- **分布式数据对象**
分布式数据对象是一个JS对象型的封装。每一个分布式数据对象实例会创建一个内存数据库中的数据表,每个应用程序创建的内存数据库相互隔离,对分布式数据对象的“读取”或“赋值”会自动映射到对应数据库的get/put操作。
分布式数据对象的生命周期包括以下状态:
- 未初始化:未实例化,或已被销毁。
- 本地数据对象:已创建对应的数据表,但是还无法进行数据同步。
- 分布式数据对象:已创建对应的数据表,设备在线且组网内设置同样sessionId的对象数&gt;=2,可以跨设备同步数据。若设备掉线或将sessionId置为空,分布式数据对象退化为本地数据对象。
## 运作机制
**图1** 分布式数据对象运作机制 
 
![distributedObject](figures/distributedObject.jpg)
分布式数据对象生长在分布式内存数据库之上,在分布式内存数据库上进行了JS对象型的封装,能像操作本地变量一样操作分布式数据对象,数据的跨设备同步由系统自动完成。
### JS对象型存储与封装机制
- 为每个分布式数据对象实例创建一个内存数据库,通过SessionId标识,每个应用程序创建的内存数据库相互隔离。
- 在分布式数据对象实例化的时候,(递归)遍历对象所有属性,使用“Object.defineProperty”定义所有属性的set和get方法,set和get中分别对应数据库一条记录的put和get操作,Key对应属性名,Value对应属性值。
- 在开发者对分布式数据对象进行“读取”或者“赋值”的时候,都会自动调用到set和get方法,映射到对应数据库的操作。
**表1** 分布式数据对象和分布式数据库的对应关系
| 分布式对象实例 | 对象实例 | 属性名称 | 属性值 |
| -------- | -------- | -------- | -------- |
| 分布式内存数据库 | 一个数据库(sessionID标识) | 一条数据库记录的key | 一条数据库记录的value |
### 跨设备同步和数据变更通知机制
分布式数据对象,最重要的功能就是对象之间的数据同步。可信组网内的设备可以在本地创建分布式数据对象,并设置sessionID。不同设备上的分布式数据对象,通过设置相同的sessionID,建立对象之间的同步关系。
如下图所示,设备A和设备B上的“分布式数据对象1”,其sessionID均为session1,这两个对象建立了session1的同步关系。
**图2** 对象的同步关系  
![distributedObject_sync](figures/distributedObject_sync.jpg)
一个同步关系中,一个设备只能有一个对象加入。比如上图中,设备A的“分布式数据对象1”已经加入了session1的同步关系,所以设备A的“分布式数据对象2”就加入失败了。
建立同步关系后,每个Session有一份共享对象数据。加入了同一个Session的对象,支持以下操作:
(1)读取/修改Session中的数据。
(2)监听数据变更,感知其他设备对共享对象数据的修改。
(3)监听状态变更,感知其他设备的加入和退出。
### 同步的最小单位
关于分布式数据对象的数据同步,值得注意的是,同步的最小单位是“属性”。比如,下图中对象1包含三个属性:name、age和parents。当其中一个属性变更时,则数据同步时只需同步此变更的属性。
**图3** 数据同步视图 
![distributedObject_syncView](figures/distributedObject_syncView.jpg)
### 对象持久化缓存机制
分布式对象主要运行在应用程序的进程空间。当调用分布式对象持久化接口时,通过分布式数据库对对象进行持久化和同步,进程退出后数据也不会丢失。
该场景是分布式对象的扩展场景,主要用于以下情况:
- 在设备上创建持久化对象后APP退出,重新打开APP,创建持久化对象,加入同一个Session,数据可以恢复到APP退出前的数据。
- 在设备A上创建持久化对象并同步后持久化到设备B后,A设备的APP退出,设备B打开APP,创建持久化对象,加入同一个Session,数据可以恢复到A设备退出前的数据。
## 约束限制
- 不同设备间只有相同bundleName的应用才能直接同步。
- 分布式数据对象的数据同步发生在同一个应用程序下,且同sessionID之间。
- 不建议创建过多的分布式数据对象,每个分布式数据对象将占用100-150KB内存。
- 每个分布式数据对象大小不超过500KB。
- 设备A修改1KB数据,设备B收到变更通知,50ms内完成。
- 单个应用程序最多只能创建16个分布式数据对象实例。
- 考虑到性能和用户体验,最多不超过3个设备进行数据协同。
- 如对复杂类型的数据进行修改,仅支持修改根属性,暂不支持下级属性修改。
- 支持JS接口间的互通,与其他语言不互通。
## 接口说明
以下是分布式对象跨设备数据同步功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[分布式数据对象](reference/apis/js-apis-data-distributedobject.md)
| 接口名称 | 描述 |
| -------- | -------- |
| create(context: Context, source: object): DataObject | 创建并得到一个分布式数据对象实例。 |
| genSessionId(): string | 创建一个sessionId,可作为分布式数据对象的sessionId。 |
| setSessionId(sessionId: string, callback: AsyncCallback&lt;void&gt;): void | 设置同步的sessionId,当可信组网中有多个设备时,多个设备间的对象如果设置为同一个sessionId,就能自动同步。 |
| setSessionId(callback: AsyncCallback&lt;void&gt;): void | 退出所有已加入的session。 |
| on(type: 'change', callback: Callback&lt;{ sessionId: string, fields: Array&lt;string&gt; }&gt;): void | 监听分布式数据对象的数据变更。 |
| on(type: 'status', callback: Callback&lt;{ sessionId: string, networkId: string, status: 'online' \| 'offline' }&gt;): void | 监听分布式数据对象的上下线。 |
| save(deviceId: string, callback: AsyncCallback&lt;SaveSuccessResponse&gt;): void | 保存分布式数据对象。 |
| revokeSave(callback: AsyncCallback&lt;RevokeSaveSuccessResponse&gt;): void | 撤回保存的分布式数据对象。 |
## 开发步骤
以一次分布式数据对象同步为例,说明开发步骤。
1. 导入`@ohos.data.distributedDataObject`模块。
```js
import distributedDataObject from '@ohos.data.distributedDataObject';
```
2. 请求权限。
1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)
2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)
3. 创建并得到一个分布式数据对象实例。
Stage模型示例:
```js
// 导入模块
import distributedDataObject from '@ohos.data.distributedDataObject';
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
// 创建对象,该对象包含4个属性类型:string、number、boolean和Object
let localObject = distributedDataObject.create(this.context, {
name: 'jack',
age: 18,
isVis: false,
parent: { mother: 'jack mom', father: 'jack Dad' },
list: [{ mother: 'jack mom' }, { father: 'jack Dad' }]
});
}
}
```
FA模型示例:
```js
// 导入模块
import distributedDataObject from '@ohos.data.distributedDataObject';
import featureAbility from '@ohos.ability.featureAbility';
// 获取context
let context = featureAbility.getContext();
// 创建对象,该对象包含4个属性类型:string、number、boolean和Object
let localObject = distributedDataObject.create(context, {
name: 'jack',
age: 18,
isVis: false,
parent: { mother: 'jack mom', father: 'jack Dad' },
list: [{ mother: 'jack mom' }, { father: 'jack Dad' }]
});
```
4. 加入同步组网。同步组网中的数据对象分为发起方和被拉起方。
```js
// 设备1加入sessionId
let sessionId = '123456';
localObject.setSessionId(sessionId);
// 和设备1协同的设备2加入同一个session
// 创建对象,该对象包含4个属性类型:string、number、boolean和Object
let remoteObject = distributedDataObject.create(this.context, {
name: undefined,
age: undefined, // undefined表示数据来自对端
isVis: true,
parent: undefined,
list: undefined
});
// 收到status上线后remoteObject同步数据,即name变成jack,age是18
remoteObject.setSessionId(sessionId);
```
5. 监听对象数据变更。可监听对端数据的变更,以callback作为变更回调实例。
```js
function changeCallback(sessionId, changeData) {
console.info(`change: ${sessionId}`);
if (changeData !== null && changeData !== undefined) {
changeData.forEach(element => {
console.info(`The element ${localObject[element]} changed.`);
});
}
}
// 发起方要在changeCallback里刷新界面,则需要将正确的this绑定给changeCallback
localObject.on("change", this.changeCallback.bind(this));
```
6. 修改对象属性,对象属性支持基本类型(数字类型、布尔类型、字符串类型)以及复杂类型(数组、基本类型嵌套等)。
```js
localObject.name = 'jack1';
localObject.age = 19;
localObject.isVis = false;
localObject.parent = { mother: 'jack1 mom', father: 'jack1 Dad' };
localObject.list = [{ mother: 'jack1 mom' }, { father: 'jack1 Dad' }];
```
> **说明:**
>
> 针对复杂类型的数据修改,目前仅支持对根属性的修改,暂不支持对下级属性的修改。
```js
// 支持的修改方式
localObject.parent = { mother: 'mom', father: 'dad' };
// 不支持的修改方式
localObject.parent.mother = 'mom';
```
7. 访问对象。可以通过直接获取的方式访问到分布式数据对象的属性,且该数据为组网内的最新数据。
```js
console.info(`name:${localObject['name']}`);
```
8. 删除监听数据变更。可以指定删除监听的数据变更回调;也可以不指定,这将会删除该分布式数据对象的所有数据变更回调。
```js
// 删除变更回调changeCallback
localObject.off('change', this.changeCallback);
// 删除所有的变更回调
localObject.off('change');
```
9. 监听分布式数据对象的上下线。可以监听对端分布式数据对象的上下线。
```js
function statusCallback(sessionId, networkId, status) {
// 业务处理
}
localObject.on('status', this.statusCallback);
```
10. 保存和撤回已保存的数据对象。
```js
// 保存数据对象,如果应用退出后组网内设备需要恢复对象数据时调用
localObject.save('local').then((result) => {
console.info(`Succeeded in saving. SessionId:${result.sessionId},version:${result.version},deviceId:${result.deviceId}`);
}).catch((err) => {
console.error(`Failed to save. Code:${err.code},message:${err.message}`);
});
// 撤回保存的数据对象
localObject.revokeSave().then((result) => {
console.info(`Succeeded in revokeSaving. Session:${result.sessionId}`);
}).catch((err) => {
console.error(`Failed to revokeSave. Code:${err.code},message:${err.message}`);
});
```
11. 删除监听分布式数据对象的上下线。可以指定删除监听的上下线回调;也可以不指定,这将会删除该分布式数据对象的所有上下线回调。
```js
// 删除上下线回调statusCallback
localObject.off('status', this.statusCallback);
// 删除所有的上下线回调
localObject.off('status');
```
12. 退出同步组网。分布式数据对象退出组网后,本地的数据变更对端不会同步。
```js
localObject.setSessionId(() => {
console.info('leave all lession.');
});
```
# 键值型数据库跨设备数据同步
## 场景介绍
键值型数据库适合不涉及过多数据关系和业务关系的业务数据存储,比SQL数据库存储拥有更好的读写性能,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。
## 基本概念
在使用键值型数据库跨设备数据同步前,请先了解以下概念。
### 单版本数据库
单版本是指数据在本地是以单个条目为单位的方式保存,当数据在本地被用户修改时,不管它是否已经被同步出去,均直接在这个条目上进行修改。多个设备全局只保留一份数据,多个设备的相同记录(主码相同)会按时间最新保留一条记录,数据不分设备,设备之间修改相同的key会覆盖。同步也以此为基础,按照它在本地被写入或更改的顺序将当前最新一次修改逐条同步至远端设备,常用于联系人、天气等应用存储场景。
![singleKVStore](figures/singleKVStore.jpg)
### 多设备协同数据库
多设备协同分布式数据库建立在单版本数据库之上,对应用程序存入的键值型数据中的Key前面拼接了本设备的DeviceID标识符,这样能保证每个设备产生的数据严格隔离。数据以设备的维度管理,不存在冲突;支持按照设备的维度查询数据。
底层按照设备的维度管理这些数据,多设备协同数据库支持以设备的维度查询分布式数据,但是不支持修改远端设备同步过来的数据。需要分开查询各设备数据的可以使用设备协同版本数据库。常用于图库缩略图存储场景。
![deviceKVStore](figures/deviceKVStore.jpg)
## 同步方式
数据管理服务提供了两种同步方式:手动同步和自动同步。键值型数据库可选择其中一种方式实现同应用跨设备数据同步。
- **手动同步**:由应用程序调用sync接口来触发,需要指定同步的设备列表和同步模式。同步模式分为PULL_ONLY(将远端数据拉取到本端)、PUSH_ONLY(将本端数据推送到远端)和PUSH_PULL(将本端数据推送到远端同时也将远端数据拉取到本端)。[带有Query参数的同步接口](../reference/apis/js-apis-distributedKVStore.md#sync-1),支持按条件过滤的方法进行同步,将符合条件的数据同步到远端。手动同步功能,仅系统应用可用。
- **自动同步**:由分布式数据库自动将本端数据推送到远端,同时也将远端数据拉取到本端来完成数据同步,同步时机包括设备上线、应用程序更新数据等,应用不需要主动调用sync接口。
## 运作机制
底层通信组件完成设备发现和认证,会通知上层应用程序设备上线。收到设备上线的消息后数据管理服务可以在两个设备之间建立加密的数据传输通道,利用该通道在两个设备之间进行数据同步。
### 数据跨设备同步机制
![kvStore](figures/kvStore.jpg)
如图所示,通过put、delete接口触发自动同步,将分布式数据通过通信适配层发送给对端设备,实现分布式数据的自动同步;
手动同步则是手动调用sync接口触发同步,将分布式数据通过通信适配层发送给对端设备。
### 数据变化通知机制
增、删、改数据库时,会给订阅者发送数据变化的通知。主要分为本地数据变化通知和分布式数据变化通知。
- **本地数据变化通知**:本地设备的应用内订阅数据变化通知,数据库增删改数据时,会收到通知。
- **分布式数据变化通知**:同一应用订阅组网内其他设备数据变化的通知,其他设备增删改数据时,本设备会收到通知。数据变化通知可以让用户及时感知到两端数据的不同,并进行同步操作,保证分布式数据库的一致性。
## 约束限制
- 设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度&lt;4 MB。
- 单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度&lt;4 MB。
- 键值型数据库不支持应用程序自定义冲突解决策略。
- 每个应用程序最多支持同时打开16个分布式数据库。
- 允许同时订阅数据变化通知的数据库最大数量为8个。
- 手动同步功能,仅系统应用可用。
## 接口说明
以下是单版本键值型分布式数据库跨设备数据同步功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
| 接口名称 | 描述 |
| -------- | -------- |
| createKVManager(config: KVManagerConfig): KVManager | 创建一个KVManager对象实例,用于管理数据库对象。 |
| getKVStore&lt;T&gt;(storeId: string, options: Options, callback: AsyncCallback&lt;T&gt;): void | 指定Options和storeId,创建并得到指定类型的KVStore数据库。 |
| put(key: string, value: Uint8Array\|string\|number\|boolean, callback: AsyncCallback&lt;void&gt;): void | 插入和更新数据。 |
| on(event: 'dataChange', type: SubscribeType, listener: Callback&lt;ChangeNotification&gt;): void | 订阅数据库中数据的变化。 |
| get(key: string, callback: AsyncCallback&lt;boolean \| string \| number \| Uint8Array&gt;): void | 查询指定Key键的值。 |
| sync(deviceIds: string[], mode: SyncMode, delayMs?: number): void | 在手动模式下,触发数据库同步。 |
## 开发步骤
此处以单版本键值型数据库跨设备数据同步的开发为例。以下是具体的开发流程和开发步骤。
![kvStore_development_process](figures/kvStore_development_process.png)
> **说明:**
>
> 数据只允许向数据安全标签不高于对端设备安全等级的设备同步数据,具体规则可见[跨设备同步访问控制机制](sync-app-data-across-devices-overview.md#跨设备同步访问控制机制)。
1. 导入模块。
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
```
2. 请求权限。
1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)
2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)
3. 根据配置构造分布式数据库管理类实例。
1. 根据应用上下文创建kvManagerConfig对象。
2. 创建分布式数据库管理器实例。
```js
// Stage模型获取context
import UIAbility from '@ohos.app.ability.UIAbility';
let kvManager;
let context = null;
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
context = this.context;
}
}
// FA模型获取context
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
// 获取context之后,构造分布式数据库管理类实例
try {
const kvManagerConfig = {
bundleName: 'com.example.datamanagertest',
context: context
}
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.info('Succeeded in creating KVManager.');
// 继续创建获取数据库
} catch (e) {
console.error(`Failed to create KVManager. Code:${e.code},message:${e.message}`);
}
```
4. 获取并得到指定类型的键值型数据库。
1. 声明需要创建的分布式数据库ID描述。
2. 创建分布式数据库,建议关闭自动同步功能(autoSync:false),方便后续对同步功能进行验证,需要同步时主动调用sync接口。
```js
try {
const options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: false,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
// 设备协同数据库:kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
kvManager.getKVStore('storeId', options, (err, kvStore) => {
if (err) {
console.error(`Failed to get KVStore: Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in getting KVStore.');
// 进行相关数据操作
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
5. 订阅分布式数据变化。
```js
try {
kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
console.info(`dataChange callback call data: ${data}`);
});
} catch (e) {
console.error(`An unexpected error occured. code:${e.code},message:${e.message}`);
}
```
6. 将数据写入分布式数据库。
1. 构造需要写入分布式数据库的Key(键)和Value(值)。
2. 将键值数据写入分布式数据库。
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
});
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
```
7. 查询分布式数据库数据。
1. 构造需要从单版本分布式数据库中查询的Key(键)。
2. 从单版本分布式数据库中获取数据。
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value_test_string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, (err) => {
if (err !== undefined) {
console.error(`Failed to put data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in putting data.');
kvStore.get(KEY_TEST_STRING_ELEMENT, (err, data) => {
if (err != undefined) {
console.error(`Failed to get data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`Succeeded in getting data. Data:${data}`);
});
});
} catch (e) {
console.error(`Failed to get data. Code:${e.code},message:${e.message}`);
}
```
8. 同步数据到其他设备。
选择同一组网环境下的设备以及同步模式(需用户在应用首次启动的弹窗中确认选择同步模式),进行数据同步。
> **说明:**
>
> 在手动同步的方式下,其中的deviceIds通过调用[devManager.getTrustedDeviceListSync](../reference/apis/js-apis-device-manager.md#gettrusteddevicelistsync)方法得到,deviceManager模块的接口均为系统接口,仅系统应用可用。
```js
import deviceManager from '@ohos.distributedHardware.deviceManager';
let devManager;
// create deviceManager
deviceManager.createDeviceManager('bundleName', (err, value) => {
if (!err) {
devManager = value;
// deviceIds由deviceManager调用getTrustedDeviceListSync方法得到
let deviceIds = [];
if (devManager !== null) {
//需要权限:ohos.permission.ACCESS_SERVICE_DM,仅系统应用可以获取
let devices = devManager.getTrustedDeviceListSync();
for (let i = 0; i < devices.length; i++) {
deviceIds[i] = devices[i].deviceId;
}
}
try {
// 1000表示最大延迟时间为1000ms
kvStore.sync(deviceIds, distributedKVStore.SyncMode.PUSH_ONLY, 1000);
} catch (e) {
console.error(`An unexpected error occurred. Code:${e.code},message:${e.message}`);
}
}
});
```
# 关系型数据库跨设备数据同步
## 场景介绍
当应用程序本地存储的关系型数据存在跨设备同步的需求时,可以将需求同步的表数据迁移到新的支持跨设备的表中,当然也可以在刚完成表创建时设置其支持跨设备。
## 基本概念
关系型数据库跨设备数据同步,支持应用在多设备间同步存储的关系型数据。
- 分布式列表,应用在数据库中新创建表后,可以设置其为分布式表。在查询远程设备数据库时,根据本地表名可以获取指定远程设备的分布式表名。
- 设备之间同步数据,数据同步有两种方式,将数据从本地设备推送到远程设备或将数据从远程设备拉至本地设备。
## 运作机制
底层通信组件完成设备发现和认证,会通知上层应用程序设备上线。收到设备上线的消息后数据管理服务可以在两个设备之间建立加密的数据传输通道,利用该通道在两个设备之间进行数据同步。
### 数据跨设备同步机制
![relationalStore_sync](figures/relationalStore_sync.jpg)
业务将数据写入关系型数据库或键值型数据库后,向数据管理服务发起同步请求。
数据管理服务从应用沙箱内读取待同步数据,根据对端设备的deviceId将数据发送到其他设备的数据管理服务。再由数据管理服务将数据写入同应用的数据库内。
### 数据变化通知机制
增、删、改数据库时,会给订阅者发送数据变化的通知。主要分为本地数据变化通知和分布式数据变化通知。
- **本地数据变化通知**:本地设备的应用内订阅数据变化通知,数据库增删改数据时,会收到通知。
- **分布式数据变化通知**:同一应用订阅组网内其他设备数据变化的通知,其他设备增删改数据时,本设备会收到通知。数据变化通知可以让用户及时感知到两端数据的不同,并进行同步操作,保证分布式数据库的一致性。
## 约束限制
- 每个应用程序最多支持同时打开16个分布式数据库。
- 允许同时订阅数据变化通知的数据库最大数量为8个。
- 不支持非系统应用调用需要指定设备的分布式能力接口。
## 接口说明
以下是关系型设备协同分布式数据库跨设备数据同步功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见[关系型数据库](../reference/apis/js-apis-data-relationalStore.md)
| 接口名称 | 描述 |
| -------- | -------- |
| setDistributedTables(tables: Array&lt;string&gt;, callback: AsyncCallback&lt;void&gt;): void | 设置分布式同步表。 |
| sync(mode: SyncMode, predicates: RdbPredicates, callback: AsyncCallback&lt;Array&lt;[string, number]&gt;&gt;): void | 分布式数据同步。 |
| on(event: 'dataChange', type: SubscribeType, observer: Callback&lt;Array&lt;string&gt;&gt;): void | 订阅分布式数据变化。 |
| off(event:'dataChange', type: SubscribeType, observer: Callback&lt;Array&lt;string&gt;&gt;): void | 取消订阅分布式数据变化。 |
| obtainDistributedTableName(device: string, table: string, callback: AsyncCallback&lt;string&gt;): void; | 根据本地数据库表名获取指定设备上的表名。 |
| remoteQuery(device: string, table: string, predicates: RdbPredicates, columns: Array&lt;string&gt; , callback: AsyncCallback&lt;ResultSet&gt;): void | 根据指定条件查询远程设备数据库中的数据。 |
## 开发步骤
> **说明:**
>
> 数据只允许向数据安全标签不高于对端设备安全等级的设备同步数据,具体规则可见[跨设备同步访问控制机制](sync-app-data-across-devices-overview.md#跨设备同步访问控制机制)。
1. 导入模块。
```js
import relationalStore from '@ohos.data.relationalStore';
```
2. 请求权限。
1. 需要申请ohos.permission.DISTRIBUTED_DATASYNC权限,配置方式请参见[配置文件权限声明](../security/accesstoken-guidelines.md#配置文件权限声明)
2. 同时需要在应用首次启动时弹窗向用户申请授权,使用方式请参见[向用户申请授权](../security/accesstoken-guidelines.md#向用户申请授权)
3. 创建关系型数据库,设置将需要进行分布式同步的表。
```js
const STORE_CONFIG = {
name: 'RdbTest.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1 // 数据库安全级别
};
relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
store.executeSql('CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)', null, (err) => {
// 设置分布式同步表。
store.setDistributedTables(['EMPLOYEE']);
// 进行数据的相关操作
})
})
```
4. 分布式数据同步。使用SYNC_MODE_PUSH触发同步后,数据将从本设备向组网内的其它所有设备同步。
```js
// 构造用于同步分布式表的谓词对象
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
// 调用同步数据的接口
store.sync(relationalStore.SyncMode.SYNC_MODE_PUSH, predicates, (err, result) => {
// 判断数据同步是否成功
if (err) {
console.error(`Failed to sync data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in syncing data.');
for (let i = 0; i < result.length; i++) {
console.info(`device:${result[i][0]},status:${result[i][1]}`);
}
})
```
5. 分布式数据订阅。数据同步变化将触发订阅回调方法执行,回调方法的入参为发生变化的设备ID。
```js
let observer = function storeObserver(devices) {
for (let i = 0; i < devices.length; i++) {
console.info(`The data of device:${devices[i]} has been changed.`);
}
}
try {
// 调用分布式数据订阅接口,注册数据库的观察者
// 当分布式数据库中的数据发生更改时,将调用回调
store.on('dataChange', relationalStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, observer);
} catch (err) {
console.error('Failed to register observer. Code:${err.code},message:${err.message}');
}
// 当前不需要订阅数据变化时,可以将其取消。
try {
store.off('dataChange', relationalStore.SubscribeType.SUBSCRIBE_TYPE_REMOTE, observer);
} catch (err) {
console.error('Failed to register observer. Code:${err.code},message:${err.message}');
}
```
6. 跨设备查询。如果数据未完成同步,或未触发数据同步,应用可以使用此接口从指定的设备上查询数据。
> **说明:**
>
> deviceIds通过调用[devManager.getTrustedDeviceListSync](../reference/apis/js-apis-device-manager.md#gettrusteddevicelistsync)方法得到,deviceManager模块的接口均为系统接口,仅系统应用可用。
```js
// 获取deviceIds
import deviceManager from '@ohos.distributedHardware.deviceManager';
deviceManager.createDeviceManager("com.example.appdatamgrverify", (err, manager) => {
if (err) {
console.info(`Failed to create device manager. Code:${err.code},message:${err.message}`);
return;
}
let devices = manager.getTrustedDeviceListSync();
let deviceId = devices[0].deviceId;
// 构造用于查询分布式表的谓词对象
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
// 调用跨设备查询接口,并返回查询结果
store.remoteQuery(deviceId, 'EMPLOYEE', predicates, ['ID', 'NAME', 'AGE', 'SALARY', 'CODES'],
function (err, resultSet) {
if (err) {
console.error(`Failed to remoteQuery data. Code:${err.code},message:${err.message}`);
return;
}
console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
}
)
})
```
# 数据共享开发指导
DataShare即数据共享模块,提供了向其他应用共享以及管理其数据的方法。目前仅支持同个设备上应用之间的数据共享。
## 接口说明
**表1** 数据提供方API说明
|接口名|描述|
|:------|:------|
|onCreate?(want: Want, callback: AsyncCallback&lt;void&gt;): void|DataShareExtensionAbility生命周期回调,在数据提供方应用创建时回调,执行初始化业务逻辑操作,如创建数据库。|
|insert?(uri: string, valueBucket: ValuesBucket, callback: AsyncCallback&lt;number&gt;): void|业务函数,在访问方向数据库中插入数据时回调。|
|update?(uri: string, predicates: DataSharePredicates, valueBucket: ValuesBucket, callback: AsyncCallback&lt;number&gt;): void|业务函数,在访问方更新数据时回调。|
|query?(uri: string, predicates: DataSharePredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;Object&gt;): void|业务函数,在访问方查询数据时回调。|
|delete?(uri: string, predicates: DataSharePredicates, callback: AsyncCallback&lt;number&gt;): void|业务函数,在访问方删除数据时回调。|
完整的数据提供方接口请见[DataShareExtensionAbility](../reference/apis/js-apis-application-dataShareExtensionAbility.md)
**表2** 数据访问方API说明
| 接口名 | 描述 |
| :----------------------------------------------------------- | :--------------------------------- |
| createDataShareHelper(context: Context, uri: string, callback: AsyncCallback&lt;DataShareHelper&gt;): void | 创建DataShare工具类。 |
| insert(uri: string, value: ValuesBucket, callback: AsyncCallback&lt;number&gt;): void | 将单条数据记录插入数据库。 |
| update(uri: string, predicates: DataSharePredicates, value: ValuesBucket, callback: AsyncCallback&lt;number&gt;): void | 更新数据库中的数据记录。 |
| query(uri: string, predicates: DataSharePredicates, columns: Array&lt;string&gt;, callback: AsyncCallback&lt;DataShareResultSet&gt;): void | 查询数据库中的数据。 |
| delete(uri: string, predicates: DataSharePredicates, callback: AsyncCallback&lt;number&gt;): void | 从数据库中删除一条或多条数据记录。 |
完整的数据访问方接口请见[DataShareHelper](../reference/apis/js-apis-data-dataShare.md)
## 开发场景
数据共享可分为数据的提供方和访问方两部分。
- 提供方可以选择性实现数据的增、删、改、查,以及文件打开等功能,并对外共享这些数据。
- 访问方利用工具类,便可以访问提供方提供的这些数据。
以下是数据提供方和数据访问方应用的各自开发示例。
### 数据提供方应用的开发(仅限系统应用)
[DataShareExtensionAbility](../reference/apis/js-apis-application-dataShareExtensionAbility.md)提供以下API,根据需要重写对应回调方法。
- **onCreate**
DataShare客户端连接DataShareExtensionAbility服务端时,服务端回调此接口,执行初始化业务逻辑操作。该方法可以选择性重写。
- **insert**
业务函数,客户端请求插入数据时回调此接口,服务端需要在此回调中实现插入数据功能,该方法可以选择性重写。
- **update**
业务函数,客户端请求更新数据时回调此接口,服务端需要在此回调中实现更新数据功能,该方法可以选择性重写。
- **delete**
业务函数,客户端请求删除数据时回调此接口,服务端需要在此回调中实现删除数据功能,该方法可以选择性重写。
- **query**
业务函数,客户端请求查询数据时回调此接口,服务端需要在此回调中实现查询数据功能,该方法可以选择性重写。
- **batchInsert**
业务函数,客户端请求批量插入数据时回调此接口,服务端需要在此回调中实现批量插入数据数据功能,该方法可以选择性重写。
- **normalizeUri**
业务函数,客户端给定的URI转换为服务端使用的URI时回调此接口,该方法可以选择性重写。
- **denormalizeUri**
业务函数,服务端使用的URI转换为客户端传入的初始URI时服务端回调此接口,该方法可以选择性重写。
开发者在实现一个数据共享服务时,需要在DevEco Studio工程中手动新建一个DataShareExtensionAbility,具体步骤如下。
1. 在工程Module对应的ets目录下,右键选择“New &gt; Directory”,新建一个目录并命名为DataShareAbility。
2. 在DataShareAbility目录,右键选择“New &gt; TypeScript File”,新建一个TypeScript文件并命名为DataShareAbility.ts。
3. 在DataShareAbility.ts文件中,增加导入DataShareExtensionAbility的依赖包,开发者可根据应用需求选择性重写其业务实现。例如数据提供方只提供插入、删除和查询服务,则可只重写这些接口。
4. 导入基础依赖包。
```ts
import Extension from '@ohos.application.DataShareExtensionAbility';
import rdb from '@ohos.data.relationalStore';
import fileIo from '@ohos.fileio';
import dataSharePredicates from '@ohos.data.dataSharePredicates';
```
5. 数据提供方(也称服务端)继承于DataShareExtensionAbility,开发者可根据应用需求选择性重写其业务实现。例如数据提供方只提供查询服务,则可只重写查询接口。
6. 数据提供方的业务实现由开发者自定义。例如可以通过数据库、读写文件或访问网络等各方式实现数据提供方的数据存储。
```ts
const DB_NAME = "DB00.db";
const TBL_NAME = "TBL00";
const DDL_TBL_CREATE = "CREATE TABLE IF NOT EXISTS "
+ TBL_NAME
+ " (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER, isStudent BOOLEAN, Binary BINARY)";
let rdbStore;
let result;
export default class DataShareExtAbility extends Extension {
private rdbStore_;
// 重写onCreate接口
onCreate(want, callback) {
result = this.context.cacheDir + '/datashare.txt';
// 业务实现使用RDB
rdb.getRdbStore(this.context, {
name: DB_NAME,
securityLevel: rdb.SecurityLevel.S1
}, function (err, data) {
rdbStore = data;
rdbStore.executeSql(DDL_TBL_CREATE, [], function (err) {
console.log('DataShareExtAbility onCreate, executeSql done err:' + JSON.stringify(err));
});
if (callback) {
callback();
}
});
}
// 重写query接口
query(uri, predicates, columns, callback) {
if (predicates == null || predicates == undefined) {
console.info('invalid predicates');
}
try {
rdbStore.query(TBL_NAME, predicates, columns, function (err, resultSet) {
if (resultSet != undefined) {
console.info('resultSet.rowCount: ' + resultSet.rowCount);
}
if (callback != undefined) {
callback(err, resultSet);
}
});
} catch (err) {
console.error('error' + err);
}
}
// 可根据应用需求,选择性重写各个接口
// ...
};
```
7. 在module.json5中定义DataShareExtensionAbility。
| Json重要字段 | 备注说明 |
| ------------ | ------------------------------------------------------------ |
| "name" | Ability名称,对应Ability派生的ExtensionAbility类名。 |
| "type" | Ability类型,DataShare对应的Ability类型为”dataShare“,表示基于datashare模板开发的。 |
| "uri" | 通信使用的URI,是客户端链接服务端的唯一标识。 |
| "visible" | 对其他应用是否可见,设置为true时,才能与其他应用进行通信传输数据。 |
**module.json5配置样例**
```json
"extensionAbilities": [
{
"srcEntrance": "./ets/DataShareExtAbility/DataShareExtAbility.ts",
"name": "DataShareExtAbility",
"icon": "$media:icon",
"description": "$string:description_datashareextability",
"type": "dataShare",
"uri": "datashare://com.samples.datasharetest.DataShare",
"visible": true
}
]
```
### 数据访问方应用的开发
1. 导入基础依赖包。
```ts
import UIAbility from '@ohos.app.ability.UIAbility';
import dataShare from '@ohos.data.dataShare';
import dataSharePredicates from '@ohos.data.dataSharePredicates';
```
2. 定义与数据提供方通信的URI字符串。
```ts
// 作为参数传递的URI,与module.json5中定义的URI的区别是多了一个"/",是因为作为参数传递的URI中,在第二个与第三个"/"中间,存在一个DeviceID的参数
let dseUri = ("datashare:///com.samples.datasharetest.DataShare");
```
3. 创建工具接口类对象。
```ts
let dsHelper;
let abilityContext;
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
abilityContext = this.context;
dataShare.createDataShareHelper(abilityContext, dseUri, (err, data)=>{
dsHelper = data;
});
}
}
```
4. 获取到接口类对象后,便可利用其提供的接口访问提供方提供的服务,如进行数据的增删改查等。
```ts
// 构建一条数据
let valuesBucket = { "name": "ZhangSan", "age": 21, "isStudent": false, "Binary": new Uint8Array([1, 2, 3]) };
let updateBucket = { "name": "LiSi", "age": 18, "isStudent": true, "Binary": new Uint8Array([1, 2, 3]) };
let predicates = new dataSharePredicates.DataSharePredicates();
let valArray = ['*'];
// 插入一条数据
dsHelper.insert(dseUri, valuesBucket, (err, data) => {
console.log("dsHelper insert result: " + data);
});
// 更新数据
dsHelper.update(dseUri, predicates, updateBucket, (err, data) => {
console.log("dsHelper update result: " + data);
});
// 查询数据
dsHelper.query(dseUri, predicates, valArray, (err, data) => {
console.log("dsHelper query result: " + data);
});
// 删除指定的数据
dsHelper.delete(dseUri, predicates, (err, data) => {
console.log("dsHelper delete result: " + data);
});
```
# 数据共享概述
## 数据共享简介
DataShare即数据共享模块,用于应用管理其自身数据,也提供了向其他应用共享以及管理其数据的方法。目前仅支持同个设备上应用之间的数据共享。
DataShare需要与[DataShareExtensionAbility](../reference/apis/js-apis-application-dataShareExtensionAbility.md)配合使用。
在许多应用场景中都需要用到数据共享,比如将电话簿、短信、媒体库中的数据共享给其他应用等。当然,不是所有的数据都允许其他应用访问,比如帐号、密码等;有些数据也只允许其他应用查询而不允许其删改,比如短信等。所以对于各种数据共享场景,DataShare这样一个安全、便捷的可以跨应用的数据共享机制是十分必需的。
对于数据提供者来说,无需进行繁琐的封装,可直接使用DataShare框架实现向其他应用共享数据;对于数据访问方来说,因DataShare的访问方式不会因数据提供的方式而不同,所以只需要学习和使用一套接口即可,大大减少了学习时间和开发难度。
## 基本概念
在进行应用的开发前,开发者应了解以下基本概念:
- **数据提供方**
DataShareExtensionAbility,基于Stage模型,选择性实现对数据的增、删、改、查以及文件打开等功能,并对外共享这些数据。实现跨应用数据共享的相关业务。
- **数据访问方**
DataShareHelper,由[createDataShareHelper()](../reference/apis/js-apis-data-dataShare.md#datasharecreatedatasharehelper)方法所创建的工具类,数据访问方利用工具类,便可访问数据提供方提供的数据。
- **数据集**
用户要插入的数据集合,可以是一条或多条数据。数据集以键值对的形式存在,键为字符串类型,值支持数字、字符串、布尔值、无符号整型数组等多种数据类型。
- **结果集**
用户查询之后的结果集合,其提供了灵活的数据访问方式,以便用户获取各项数据。
- **谓词**
用户访问数据库中的数据所使用的筛选条件,经常被应用在更新数据、删除数据和查询数据等场景。
## 运作机制
**图 1** DataShare运作机制<a name="fig3330103712254"></a>
![](figures/zh-cn_DataShare.png)
- DataShareExtensionAbility模块为数据提供方,实现跨应用数据共享的相关业务。
- DataShareHelper模块为数据访问方,提供各种访问数据的接口,包括增删改查等。
- 数据访问方与提供方通过IPC进行通信,数据提供方可以通过数据库实现,也可以通过其他数据存储方式实现。
- ResultSet模块通过共享内存实现,用于存储查询数据得到的结果集,并提供了遍历结果集的方法。
## 约束与限制
- DataShare受到数据提供方所使用数据库的一些限制。例如支持的数据模型、Key的长度、Value的长度、每个应用程序支持同时打开数据库的最大数量等,都会受到使用的数据库的限制。
- 因DataShare内部实现依赖于IPC通信,所以数据集、谓词、结果集等的载荷受到IPC通信的约束与限制。
# 分布式数据对象开发指导
## 场景介绍
分布式数据对象为开发者在分布式应用场景下提供简单易用的功能接口,可实现多设备间同应用的数据协同,同时设备间还可以监听对象的状态和数据变更。
比如,当设备1上应用A的分布式数据对象增、删、改数据后,设备2上应用A也可以获取到对应的数据变化,同时还能监听数据变更以及对端数据对象的上下线。
## 接口说明
分布式数据对象相关功能接口请见[分布式数据对象](../reference/apis/js-apis-data-distributedobject.md)
### 创建数据对象实例
创建一个分布式数据对象实例,开发者可以通过source指定分布式数据对象中的属性。
**表1** 分布式数据对象实例创建接口
| 包名 | 接口名 | 描述 |
| -------- | -------- | -------- |
| ohos.data.distributedDataObject| createDistributedObject(source: object): DistributedObject | 创建一个分布式数据对象实例,用于数据操作。 <br>-&nbsp;source:设置分布式数据对象的属性。<br>-&nbsp;DistributedObject:返回值是创建好的分布式数据对象。 |
### 创建分布式数据对象sessionId
创建一个随机的sessionId,可将其设置为一个分布式数据对象的sessionId。
**表2** 分布式数据对象sessionId创建接口
| 包名 | 接口名 | 描述 |
| -------- | -------- | -------- |
| ohos.data.distributedDataObject| genSessionId(): string | 创建一个sessionId,可作为分布式数据对象的sessionId。 |
### 设置分布式数据对象sessionId
设置分布式数据对象的sessionId,sessionId是一次(多设备)协同的唯一标识,同步的多个数据对象需要关联同一个sessionId。
**表3** 分布式数据对象sessionId设置接口
| 类名 | 接口名 | 描述 |
| -------- | -------- | -------- |
| DistributedDataObject | setSessionId(sessionId?: string): boolean | 为分布式数据对象设置sessionId。<br>&nbsp;sessionId:分布式数据对象在可信组网中的标识ID。如果要退出分布式组网,设置为""或不设置均可。 |
### 订阅数据变更
订阅数据变更需要指定Callback作为回调方法,订阅的数据对象发生数据变更后,Callback被回调。
**表4** 分布式数据对象数据变更订阅接口
| 类名 | 接口名 | 描述 |
| -------- | -------- | -------- |
| DistributedDataObject| on(type: 'change', callback: Callback<{ sessionId: string, fields: Array&lt;string&gt; }>): void | 订阅数据变更。 |
| DistributedDataObject| off(type: 'change', callback?: Callback<{ sessionId: string, fields: Array&lt;string&gt; }>): void | 注销订阅。需要删除的变更回调,若不设置则删除该对象所有的变更回调。 |
### 订阅数据对象上下线
订阅数据对象上下线需要指定Callback作为回调方法,订阅的数据对象上线/下线后,对端的数据对象会收到Callback回调。
**表5** 分布式数据对象数据上下线订阅接口
| 类名 | 接口名 | 描述 |
| -------- | -------- | -------- |
| DistributedDataObject| on(type: 'status', callback: Callback<{ sessionId: string, networkId: string, status: 'online' \| 'offline' }>): void | 订阅数据对象上下线。 |
| DistributedDataObject| off(type: 'status', callback?: Callback<{ sessionId: string, deviceId: string, status: 'online' \| 'offline' }>): void | 注销订阅。 |
### 保存和撤回已保存的数据对象
保存数据对象:数据对象保存成功后,当应用存在时不会释放对象数据;当应用退出后,重新进入应用时,恢复保存在设备上的数据。
撤回保存的数据对象:如果该对象保存在本地设备,那么将删除所有受信任设备上所保存的数据;如果对象保存在其他设备,那么将删除本地设备上的数据。
有以下几种情况时,保存的数据将会被释放:
- 存储时间超过24小时。
- 应用卸载。
- 成功恢复数据之后。
**表6** 分布式数据对象保存和撤回保存接口
| 类名 | 接口名 | 描述 |
| -------- | -------- | -------- |
| DistributedDataObject | save(deviceId: string): Promise&lt;SaveSuccessResponse&gt; | 保存数据对象。 |
| DistributedDataObject| revokeSave(): Promise&lt;RevokeSaveSuccessResponse&gt; | 撤回已保存的数据对象。 |
## 开发步骤
以一次分布式数据对象同步为例,说明开发步骤。
1. 准备工作,导入@ohos.data.distributedDataObject模块到开发环境。
```js
import distributedObject from '@ohos.data.distributedDataObject';
```
2. 请求权限。
需要在`config.json`文件里进行配置请求权限(FA模型)。
```json
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
}
}
```
Stage模型下的权限请求请参见[权限声明-Stage模型](../security/accesstoken-guidelines.md#stage模型)
这个权限还需要在应用首次启动的时候弹窗获取用户授权。
```js
// FA模型
import featureAbility from '@ohos.ability.featureAbility';
function grantPermission() {
console.info('grantPermission');
let context = featureAbility.getContext();
context.requestPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], 666, function (result) {
console.info(`requestPermissionsFromUser CallBack`);
})
console.info('end grantPermission');
}
grantPermission();
```
```ts
// Stage模型
import UIAbility from '@ohos.app.ability.UIAbility';
let context = null;
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
context = this.context;
}
}
function grantPermission() {
let permissions = ['ohos.permission.DISTRIBUTED_DATASYNC'];
context.requestPermissionsFromUser(permissions).then((data) => {
console.info('success: ${data}');
}).catch((error) => {
console.error('failed: ${error}');
});
}
grantPermission();
```
3. 获取分布式数据对象实例。
```js
let localObject = distributedObject.createDistributedObject({
name: undefined,
age: undefined,
isVis: true,
parent: undefined,
list: undefined
});
let sessionId = distributedObject.genSessionId();
```
4. 加入同步组网。同步组网中的数据对象分为发起方和被拉起方。
```js
// 发起方
let localObject = distributedObject.createDistributedObject({
name: "jack",
age: 18,
isVis: true,
parent: { mother: "jack mom", father: "jack Dad" },
list: [{ mother: "jack mom" }, { father: "jack Dad" }]
});
localObject.setSessionId(sessionId);
// 被拉起方
let remoteObject = distributedObject.createDistributedObject({
name: undefined,
age: undefined,
isVis: true,
parent: undefined,
list: undefined
});
// 收到status上线后remoteObject同步数据,即name变成jack,age是18
remoteObject.setSessionId(sessionId);
```
5. 监听对象数据变更。可监听对端数据的变更,以Callback作为变更回调实例。
```js
function changeCallback(sessionId, changeData) {
console.info("change" + sessionId);
if (changeData != null && changeData != undefined) {
changeData.forEach(element => {
console.info("changed !" + element + " " + localObject[element]);
});
}
}
// 发起方要在changeCallback里刷新界面,则需要将正确的this绑定给changeCallback
localObject.on("change", this.changeCallback.bind(this));
```
6. 修改对象属性,对象属性支持基本类型(数字类型、布尔类型、字符串类型)以及复杂类型(数组、基本类型嵌套等)。
```js
localObject.name = "jack";
localObject.age = 19;
localObject.isVis = false;
localObject.parent = { mother: "jack mom", father: "jack Dad" };
localObject.list = [{ mother: "jack mom" }, { father: "jack Dad" }];
```
> **说明:**
> 针对复杂类型的数据修改,目前支持对根属性的修改,暂不支持对下级属性的修改。
```js
// 支持的修改方式
localObject.parent = { mother: "mom", father: "dad" };
// 不支持的修改方式
localObject.parent.mother = "mom";
```
7. 访问对象。可以通过直接获取的方式访问到分布式数据对象的属性,且该数据为组网内的最新数据。
```js
console.info("name " + localObject["name"]);
```
8. 删除监听数据变更。可以指定删除监听的数据变更回调;也可以不指定,这将会删除该分布式数据对象的所有数据变更回调。
```js
// 删除变更回调changeCallback
localObject.off("change", changeCallback);
// 删除所有的变更回调
localObject.off("change");
```
9. 监听分布式数据对象的上下线。可以监听对端分布式数据对象的上下线。
```js
function statusCallback(sessionId, networkId, status) {
this.response += "status changed " + sessionId + " " + status + " " + networkId;
}
localObject.on("status", this.statusCallback);
```
10. 保存和撤回已保存的数据对象。
```js
// 保存数据对象
localObject.save("local").then((result) => {
console.info("save sessionId " + result.sessionId);
console.info("save version " + result.version);
console.info("save deviceId " + result.deviceId);
}, (result) => {
console.info("save local failed.");
});
// 撤回保存的数据对象
localObject.revokeSave().then((result) => {
console.info("revokeSave success.");
}, (result) => {
console.info("revokeSave failed.");
});
```
11. 删除监听分布式数据对象的上下线。可以指定删除监听的上下线回调;也可以不指定,这将会删除该分布式数据对象的所有上下线回调。
```js
// 删除上下线回调statusCallback
localObject.off("status", this.statusCallback);
// 删除所有的上下线回调
localObject.off("status");
```
12. 退出同步组网。分布式数据对象退出组网后,本地的数据变更对端不会同步。
```js
localObject.setSessionId("");
```
## 相关实例
针对分布式数据对象,有以下相关实例可供参考:
- [`DistributedNote`:分布式备忘录(ArkTS)(API9)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/DistributedNote)
- [`DistributedObjectDms`:分布式跑马灯(ArkTS)(API9)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/DistributedObjectDms)
\ No newline at end of file
# 分布式数据对象概述
分布式数据对象管理框架是一款面向对象的内存数据管理框架。向应用开发者提供内存对象的创建、查询、删除、修改、订阅等基本数据对象的管理能力;同时具备分布式能力,满足超级终端场景下,相同应用多设备间的数据对象协同需求。
## 基本概念
- **分布式内存数据库**
分布式内存数据库将数据缓存在内存中,以便应用获得更快的数据存取速度,不会将数据进行持久化。若数据库关闭,则数据不会保留。
- **分布式数据对象**
分布式数据对象是一个JS对象型的封装。每一个分布式数据对象实例会创建一个内存数据库中的数据表,每个应用程序创建的内存数据库相互隔离,对分布式数据对象的“读取”或“赋值”会自动映射到对应数据库的get/put操作。
分布式数据对象的生命周期包括以下状态:
- **未初始化**:未实例化,或已被销毁。
- **本地数据对象**:已创建对应的数据表,但是还无法进行数据同步。
- **分布式数据对象**:已创建对应的数据表,设备在线且组网内设置同样sessionId的对象数>=2,可以跨设备同步数据。若设备掉线或将sessionId置为空,分布式数据对象退化为本地数据对象。
## 运作机制
分布式数据对象生长在分布式内存数据库之上,在分布式内存数据库上进行了JS对象型的封装,能像操作本地变量一样操作分布式数据对象,数据的跨设备同步由系统自动完成。
**图1** 分布式数据对象运行机制
![how-distributedobject-works](figures/how-distributedobject-works.png)
## 约束与限制
- 不同设备间只有相同bundleName的应用才能直接同步。
- 不建议创建过多的分布式数据对象,每个分布式数据对象将占用100-150KB内存。
- 每个分布式数据对象大小不超过500KB。
- 如对复杂类型的数据进行修改,仅支持修改根属性,暂不支持下级属性修改。
- 支持JS接口间的互通,与其他语言不互通。
# 分布式数据服务开发指导
## 场景介绍
分布式数据服务主要实现用户设备中应用程序数据内容的分布式同步。当设备1上的应用A在分布式数据库中增、删、改数据后,设备2上的应用A也可以获取到该数据库变化。可在分布式图库、信息、通讯录、文件管理器等场景中使用。
## 接口说明
分布式数据相关功能接口请见[分布式键值数据库](../reference/apis/js-apis-distributedKVStore.md)
**表1** 分布式数据服务关键API功能介绍
| 接口名称 | 描述 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| createKVManager(config: KVManagerConfig): KVManager | 创建一个`KVManager`对象实例,用于管理数据库对象。 |
| getKVStore&lt;T extends KVStore&gt;(storeId: string, options: Options, callback: AsyncCallback&lt;T&gt;): void<br/>getKVStore&lt;T extends KVStore&gt;(storeId: string, options: Options): Promise&lt;T&gt; | 指定`Options``storeId`,创建并获取指定类型`KVStore`数据库。 |
| put(key: string, value: Uint8Array\|string\|number\|boolean, callback: AsyncCallback&lt;void&gt;): void<br/>put(key: string, value: Uint8Array\|string\|number\|boolean): Promise&lt;void> | 插入和更新数据。 |
| delete(key: string, callback: AsyncCallback&lt;void&gt;): void<br/>delete(key: string): Promise&lt;void> | 删除数据。 |
| get(key: string, callback: AsyncCallback&lt;Uint8Array\|string\|boolean\|number&gt;): void<br/>get(key: string): Promise&lt;Uint8Array\|string\|boolean\|number> | 获取数据。 |
| on(event: 'dataChange', type: SubscribeType, observer: Callback&lt;ChangeNotification&gt;): void<br/>on(event: 'syncComplete', syncCallback: Callback&lt;Array&lt;[string,number]&gt;&gt;): void | 订阅数据库中数据的变化。 |
| sync(deviceIdList: string[], mode: SyncMode, delayMs?: number): void | 在手动模式下,触发数据库同步。 |
## 开发步骤
以单版本分布式数据库为例,说明开发步骤。
1. 导入模块。
```js
import distributedKVStore from '@ohos.data.distributedKVStore';
```
2. 请求权限(同步操作时进行该步骤)。
需要在`config.json`文件里进行配置请求权限(FA模型),示例代码如下:
```json
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
]
}
}
```
Stage模型下的权限请求请参见[权限声明-Stage模型](../security/accesstoken-guidelines.md#stage模型)
这个权限还需要在应用首次启动的时候弹窗获取用户授权,可以通过如下代码实现:
```js
// FA模型
import featureAbility from '@ohos.ability.featureAbility';
function grantPermission() {
console.info('grantPermission');
let context = featureAbility.getContext();
context.requestPermissionsFromUser(['ohos.permission.DISTRIBUTED_DATASYNC'], 666).then((data) => {
console.info('success: ${data}');
}).catch((error) => {
console.error('failed: ${error}');
})
}
grantPermission();
// Stage模型
import UIAbility from '@ohos.app.ability.UIAbility';
let context = null;
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
let context = this.context;
}
}
function grantPermission() {
let permissions = ['ohos.permission.DISTRIBUTED_DATASYNC'];
context.requestPermissionsFromUser(permissions).then((data) => {
console.log('success: ${data}');
}).catch((error) => {
console.error('failed: ${error}');
});
}
grantPermission();
```
3. 根据配置构造分布式数据库管理类实例。
1. 根据应用上下文创建`kvManagerConfig`对象。
2. 创建分布式数据库管理器实例。
以下为创建分布式数据库管理器的代码示例:
```js
// FA模型获取context
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext();
// Stage模型获取context
import UIAbility from '@ohos.app.ability.UIAbility';
let context = null;
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage){
context = this.context;
}
}
let kvManager;
try {
const kvManagerConfig = {
bundleName: 'com.example.datamanagertest',
context:context,
}
kvManager = distributedKVStore.createKVManager(kvManagerConfig);
console.log("Succeeded in creating KVManager");
} catch (e) {
console.error(`Failed to create KVManager.code is ${e.code},message is ${e.message}`);
}
```
4. 获取/创建分布式数据库。
1. 声明需要创建的分布式数据库ID描述。
2. 创建分布式数据库,建议关闭自动同步功能(`autoSync:false`),需要同步时主动调用`sync`接口。
以下为创建分布式数据库的代码示例:
```js
let kvStore;
try {
const options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: false,
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
securityLevel: distributedKVStore.SecurityLevel.S1
};
kvManager.getKVStore('storeId', options, function (err, store) {
if (err) {
console.error(`Failed to get KVStore: code is ${err.code},message is ${err.message}`);
return;
}
console.log('Succeeded in getting KVStore');
kvStore = store;
});
} catch (e) {
console.error(`An unexpected error occurred.code is ${e.code},message is ${e.message}`);
}
```
> **说明:**
>
> 组网设备间同步数据的场景,建议在应用启动时打开分布式数据库,获取数据库的句柄。在该句柄(如示例中的`kvStore`)的生命周期内无需重复创建数据库,可直接使用句柄对数据库进行数据的插入等操作。
5. 订阅分布式数据变化。
以下为订阅单版本分布式数据库数据变化通知的代码示例:
```js
try{
kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, function (data) {
console.log(`dataChange callback call data: ${data}`);
});
}catch(e){
console.error(`An unexpected error occured.code is ${e.code},message is ${e.message}`);
}
```
6. 将数据写入分布式数据库。
1. 构造需要写入分布式数据库的`Key`(键)和`Value`(值)。
2. 将键值数据写入分布式数据库。
以下为将字符串类型键值数据写入分布式数据库的代码示例:
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value-test-string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, function (err,data) {
if (err != undefined) {
console.error(`Failed to put.code is ${err.code},message is ${err.message}`);
return;
}
console.log("Succeeded in putting");
});
}catch (e) {
console.error(`An unexpected error occurred.code is ${e.code},message is ${e.message}`);
}
```
7. 查询分布式数据库数据。
1. 构造需要从单版本分布式数据库中查询的`Key`(键)。
2. 从单版本分布式数据库中获取数据。
以下为从分布式数据库中查询字符串类型数据的代码示例:
```js
const KEY_TEST_STRING_ELEMENT = 'key_test_string';
const VALUE_TEST_STRING_ELEMENT = 'value-test-string';
try {
kvStore.put(KEY_TEST_STRING_ELEMENT, VALUE_TEST_STRING_ELEMENT, function (err,data) {
if (err != undefined) {
console.error(`Failed to put.code is ${err.code},message is ${err.message}`);
return;
}
console.log("Succeeded in putting");
kvStore.get(KEY_TEST_STRING_ELEMENT, function (err,data) {
if (err != undefined) {
console.error(`Failed to get.code is ${err.code},message is ${err.message}`);
return;
}
console.log(`Succeeded in getting data:${data}`);
});
});
}catch (e) {
console.error(`Failed to get.code is ${e.code},message is ${e.message}`);
}
```
8. 同步数据到其他设备。
选择同一组网环境下的设备以及同步模式,进行数据同步。
> **说明**:
>
> 其中`deviceManager`模块的接口均为系统接口。
以下为单版本分布式数据库进行数据同步的代码示例:
```js
import deviceManager from '@ohos.distributedHardware.deviceManager';
let devManager;
// create deviceManager
deviceManager.createDeviceManager('bundleName', (err, value) => {
if (!err) {
devManager = value;
// deviceIds由deviceManager调用getTrustedDeviceListSync方法得到
let deviceIds = [];
if (devManager != null) {
var devices = devManager.getTrustedDeviceListSync();
for (var i = 0; i < devices.length; i++) {
deviceIds[i] = devices[i].deviceId;
}
}
try{
// 1000表示最大延迟时间为1000ms
kvStore.sync(deviceIds, distributedKVStore.SyncMode.PUSH_ONLY, 1000);
} catch (e) {
console.error(`An unexpected error occurred. code is ${e.code},message is ${e.message}`);
}
}
});
```
## 相关实例
针对分布式数据开发,有以下相关实例可供参考:
- [`DistributedCalc`:分布式计算器(JS)(API8)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/common/DistributeCalc)
- [`DistributedCalc`:分布式计算器(ArkTS)(API8)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/Preset/DistributeCalc)
- [`DistributedDataGobang`:分布式五子棋(ArkTS)(API9)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/DistributedDataGobang)
- [`DDMQuery`:结果集与谓词(ArkTS)(API8)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/DDMQuery)
- [`KvStore`:分布式数据库(ArkTS)(API8)(Full SDK)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/Kvstore)
- [分布式数据库(JS)(API8)](https://gitee.com/openharmony/codelabs/tree/master/Data/JsDistributedData)
\ No newline at end of file
# 分布式数据服务概述
分布式数据服务(Distributed Data Service,DDS)为应用程序提供不同设备间数据库的分布式协同能力。
通过调用分布式数据接口,应用程序将数据保存到分布式数据库中。通过结合帐号、应用和数据库三元组,分布式数据服务对属于不同应用的数据进行隔离,保证不同应用之间的数据不能通过分布式数据服务互相访问。在通过可信认证的设备间,分布式数据服务支持应用数据相互同步,为用户提供在多种终端设备上最终一致的数据访问体验。
关于数据库锁机制,开发者无需关注其具体实现。
## 基本概念
### KV数据模型
“KV数据模型”是“Key-Value数据模型”的简称,“Key-Value”即“键-值”;其数据以键值对的形式进行组织、索引和存储。
KV数据模型适合不涉及过多数据关系和业务关系的业务数据存储,比SQL数据库存储拥有更好的读写性能,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。分布式数据库也是基于KV数据模型,对外提供KV类型的访问接口。
### 分布式数据库事务性
分布式数据库事务支持本地事务(和传统数据库的事务概念一致)和同步事务。同步事务是指在设备之间同步数据时,以本地事务为单位进行同步,一次本地事务的修改要么都同步成功,要么都同步失败。
### 分布式数据库一致性
在分布式场景中一般会涉及多个设备,组网内设备之间看到的数据是否一致称为分布式数据库的一致性。分布式数据库一致性可以分为**强一致性****弱一致性****最终一致性**
- **强一致性**:是指某一设备成功增、删、改数据后,组网内设备对该数据的读取操作都将得到更新后的值。
- **弱一致性**:是指某一设备成功增、删、改数据后,组网内设备可能读取到本次更新数据,也可能读取不到,不能保证在多长时间后每个设备的数据一定是一致的。
- **最终一致性**:是指某一设备成功增、删、改数据后,组网内设备可能读取不到本次更新数据,但在某个时间窗口之后组网内设备的数据能够达到一致状态。
强一致性对分布式数据的管理要求非常高,在服务器的分布式场景可能会遇到。因为移动终端设备的不常在线、以及无中心的特性,分布式数据服务不支持强一致性,只支持最终一致性。
### 分布式数据库同步
底层通信组件完成设备发现和认证,会通知上层应用程序(包括分布式数据服务)设备上线。收到设备上线的消息后分布式数据服务可以在两个设备之间建立加密的数据传输通道,利用该通道在两个设备之间进行数据同步。
分布式数据服务提供了两种同步方式:**手动同步****自动同步**
- **手动同步**:由应用程序调用sync接口来触发,需要指定同步的设备列表和同步模式。同步模式分为PULL_ONLY(将远端数据拉到本端)、PUSH_ONLY(将本端数据推送到远端)和PUSH_PULL(将本端数据推送到远端同时也将远端数据拉取到本端)。内部接口支持按条件过滤同步,将符合条件的数据同步到远端。
- **自动同步**:包括全量同步和按条件订阅同步。全量同步由分布式数据库自动将本端数据推送到远端,同时也将远端数据拉取到本端来完成数据同步,同步时机包括设备上线、应用程序更新数据等,应用不需要主动调用sync接口;内部接口支持按条件订阅同步,将远端符合订阅条件的数据自动同步到本端。
### 单版本分布式数据库
单版本分布式数据库是指数据在本地保存是以单个KV条目为单位的方式保存,对每个Key最多只保存一个条目项,当数据在本地被用户修改时,不管它是否已经被同步出去,均直接在这个条目上进行修改。同步也以此为基础,按照它在本地被写入或更改的顺序将当前最新一次修改逐条同步至远端设备。
### 设备协同分布式数据库
设备协同分布式数据库建立在单版本分布式数据库之上,对应用程序存入的KV数据中的Key前面拼接了本设备的DeviceID标识符,这样能保证每个设备产生的数据严格隔离,底层按照设备的维度管理这些数据,设备协同分布式数据库支持以设备的维度查询分布式数据,但是不支持修改远端设备同步过来的数据。
### 分布式数据库冲突解决策略
分布式数据库多设备提交冲突场景,在给提交冲突做合并的过程中,如果多个设备同时修改了同一数据,则称这种场景为数据冲突。数据冲突采用默认冲突解决策略(Last-write-wins),基于提交时间戳,取时间戳较大的提交数据,当前不支持定制冲突解决策略。
### 数据库Schema化管理与谓词查询
单版本数据库支持在创建和打开数据库时指定Schema,数据库根据Schema定义感知KV记录的Value格式,以实现对Value值结构的检查,并基于Value中的字段实现索引建立和谓词查询。
### 分布式数据库备份能力
提供分布式数据库备份能力,业务通过设置backup属性为true,可以触发分布式数据服务每日备份。当分布式数据库发生损坏,分布式数据服务会删除损坏数据库,并且从备份数据库中恢复上次备份的数据。如果不存在备份数据库,则创建一个新的数据库。同时支持加密数据库的备份能力。
## 运作机制
分布式数据服务支撑OpenHarmony系统上应用程序数据库数据分布式管理,支持数据在相同帐号的多端设备之间相互同步,为用户在多端设备上提供一致的用户体验,分布式数据服务包含五部分:
- **服务接口:** 分布式数据服务提供专门的数据库创建、数据访问、数据订阅等接口给应用程序调用,接口支持KV数据模型,支持常用的数据类型,同时确保接口的兼容性、易用性和可发布性。
- **服务组件:** 服务组件负责服务内元数据管理、权限管理、加密管理、备份和恢复管理以及多用户管理等、同时负责初始化底层分布式DB的存储组件、同步组件和通信适配层。
- **存储组件:** 存储组件负责数据的访问、数据的缩减、事务、快照、数据库加密,以及数据合并和冲突解决等特性。
- **同步组件:** 同步组件连结了存储组件与通信组件,其目标是保持在线设备间的数据库数据一致性,包括将本地产生的未同步数据同步给其他设备,接收来自其他设备发送过来的数据,并合并到本地设备中。
- **通信适配层:** 通信适配层负责调用底层公共通信层的接口完成通信管道的创建、连接,接收设备上下线消息,维护已连接和断开设备列表的元数据,同时将设备上下线信息发送给上层同步组件,同步组件维护连接的设备列表,同步数据时根据该列表,调用通信适配层的接口将数据封装并发送给连接的设备。
应用程序通过调用分布式数据服务接口实现分布式数据库创建、访问、订阅功能,服务接口通过操作服务组件提供的能力,将数据存储至存储组件,存储组件调用同步组件实现将数据同步,同步组件使用通信适配层将数据同步至远端设备,远端设备通过同步组件接收数据,并更新至本端存储组件,通过服务接口提供给应用程序使用。
**图1** 数据分布式运作示意图
![zh-cn_image_0000001183386164](figures/zh-cn_image_0000001183386164.png)
## 约束与限制
- 分布式数据服务的数据模型仅支持KV数据模型,不支持外键、触发器等关系型数据库中的功能。
- 分布式数据服务支持的KV数据模型规格:
- 设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度&lt;4 MB。
- 单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度&lt;4 MB。
- 每个应用程序最多支持同时打开16个分布式数据库。
- 分布式数据库与本地数据库的使用场景不同,因此开发者应识别需要在设备间进行同步的数据,并将这些数据保存到分布式数据库中。
- 分布式数据服务当前不支持应用程序自定义冲突解决策略。
- 分布式数据服务针对每个应用程序当前的流控机制:KvStore的接口1秒最大访问1000次,1分钟最大访问10000次;KvManager的接口1秒最大访问50次,1分钟最大访问500次。
- 分布式数据库事件回调方法中不允许进行阻塞操作,例如修改UI组件。
# 首选项开发指导
> **说明:**
>
> 该功能特性从API Version 9开始支持。API Version 9之前可使用[轻量级存储](../reference/apis/js-apis-data-storage.md)的相关功能接口。
## 场景介绍
首选项功能通常用于保存应用的一些常用配置信息,并不适合需要存储大量数据和频繁改变数据的场景。应用的数据保存在文件中,这些文件可以持久化地存储在设备上。
需要注意的是,应用访问的实例包含文件所有数据,这些数据会一直加载在设备的内存中,直到应用主动从内存中将其移除前,应用都可以通过Preferences API进行相关数据操作。
## 接口说明
首选项为应用提供Key-Value键值型的文件数据处理能力,支持应用持久化轻量级数据,并对其修改和查询。
数据存储形式为键值对,键的类型为字符串型,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型。
更多首选项相关接口,请见[首选项API](../reference/apis/js-apis-data-preferences.md)
### 创建存储实例
读取指定文件,将数据加载到Preferences实例,即可创建一个存储实例,用于数据操作。
**表1** 首选项实例创建接口
| 包名 | 接口名 | 描述 |
| --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| ohos.data.preferences | getPreferences(context: Context, name: string): Promise\<Preferences> | 读取指定首选项持久化文件,将数据加载到Preferences实例,用于数据操作。 |
### 数据处理
通过put系列方法,可以增加或修改Preferences实例中的数据。
通过调用get系列方法,可以读取Preferences中的数据。
通过调用getAll系列方法,可以获取Preferences中包含所有键值的Object对象。
通过调用delete系列方法,可以删除Preferences中名为给定Key的存储键值对。
**表2** 首选项数据处理接口
| 类名 | 接口名 | 描述 |
| ----------- | ---------------------------------------------------------- | ------------------------------------------------------------ |
| Preferences | put(key: string, value: ValueType): Promise\<void> | 支持存入值为number、string、boolean、Array\<number>、Array\<string>、Array\<boolean>类型的数据。 |
| Preferences | get(key: string, defValue: ValueType): Promise\<ValueType> | 支持获取值为number、string、boolean、Array\<number>、Array\<string>、Array\<boolean>类型的数据。 |
| Preferences | getAll(): Promise\<Object> | 支持获取含有所有键值的Object对象。 |
| Preferences | delete(key: string): Promise\<void> | 支持从Preferences实例中删除名为给定Key的存储键值对。 |
### 数据持久化
通过执行flush方法,应用可以将缓存的数据再次写回文本文件中进行持久化存储。
**表4** 首选项持久化接口
| 类名 | 接口名 | 描述 |
| ----------- | ----------------------- | ------------------------------------------- |
| Preferences | flush(): Promise\<void> | 将Preferences实例通过异步线程回写入文件中。 |
### 订阅数据变更
订阅数据变更,订阅的Key的值发生变更后,在执行flush方法后,会触发callback回调。
**表5** 首选项变化订阅接口
| 类名 | 接口名 | 描述 |
| ----------- | ------------------------------------------------------------ | -------------- |
| Preferences | on(type: 'change', callback: Callback<{ key : string }>): void | 订阅数据变更。 |
| Preferences | off(type: 'change', callback: Callback<{ key : string }>): void | 注销订阅。 |
### 删除数据文件
通过调用以下两种接口,可以删除数据实例或对应的文件。
**表6** 首选项删除接口
| 包名 | 接口名 | 描述 |
| --------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| ohos.data.preferences | deletePreferences(context: Context, name: string): Promise\<void> | 从缓存中移除已加载的Preferences对象,同时从设备上删除对应的文件。 |
| ohos.data.preferences | removePreferencesFromCache(context: Context, name: string): Promise\<void> | 仅从缓存中移除已加载的Preferences对象,主要用于释放内存。 |
## 开发步骤
1. 准备工作,导入@ohos.data.preferences以及相关的模块到开发环境。
```js
import data_preferences from '@ohos.data.preferences';
```
2. 获取Preferences实例。
读取指定文件,将数据加载到Preferences实例,用于数据操作。
FA模型示例:
```js
// 获取context
import featureAbility from '@ohos.ability.featureAbility'
let context = featureAbility.getContext();
let preferences = null;
let promise = data_preferences.getPreferences(context, 'mystore');
promise.then((pref) => {
preferences = pref;
}).catch((err) => {
console.info("Failed to get preferences.");
})
```
Stage模型示例:
```ts
// 获取context
import UIAbility from '@ohos.app.ability.UIAbility';
let preferences = null;
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
let promise = data_preferences.getPreferences(this.context, 'mystore');
promise.then((pref) => {
preferences = pref;
}).catch((err) => {
console.info("Failed to get preferences.");
})
}
}
```
3. 存入数据。
使用put方法保存数据到缓存的实例中。
```js
let putPromise = preferences.put('startup', 'auto');
putPromise.then(() => {
console.info("Succeeded in putting the value of 'startup'.");
}).catch((err) => {
console.info("Failed to put the value of 'startup'. Cause: " + err);
})
```
4. 读取数据。
使用get方法读取数据。
```js
let getPromise = preferences.get('startup', 'default');
getPromise.then((value) => {
console.info("The value of 'startup' is " + value);
}).catch((err) => {
console.info("Failed to get the value of 'startup'. Cause: " + err);
})
```
5. 数据持久化。
应用存入数据到Preferences实例后,可以通过flush方法将Preferences实例回写到文件中。
```js
preferences.flush();
```
6. 订阅数据变更。
应用订阅数据变更需要指定observer作为回调方法。订阅的Key的值发生变更后,当执行flush方法时,observer被触发回调。
```js
let observer = function (key) {
console.info("The key" + key + " changed.");
}
preferences.on('change', observer);
// 数据产生变更,由'auto'变为'manual'
preferences.put('startup', 'manual', function (err) {
if (err) {
console.info("Failed to put the value of 'startup'. Cause: " + err);
return;
}
console.info("Succeeded in putting the value of 'startup'.");
preferences.flush(function (err) {
if (err) {
console.info("Failed to flush. Cause: " + err);
return;
}
console.info("Succeeded in flushing."); // observer will be called.
})
})
```
7. 删除指定文件。
使用deletePreferences方法从内存中移除指定文件对应的Preferences单实例,并删除指定文件及其备份文件、损坏文件。删除指定文件时,应用不允许再使用该实例进行数据操作,否则会出现数据一致性问题。删除后,数据及文件将不可恢复。
```js
let proDelete = data_preferences.deletePreferences(context, 'mystore');
proDelete.then(() => {
console.info("Succeeded in deleting.");
}).catch((err) => {
console.info("Failed to delete. Cause: " + err);
})
```
## 相关实例
针对首选项开发,有以下相关实例可供参考:
- [`Preferences`:首选项(ArkTS)(API9)](https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-3.2-Release/data/Preferences)
- [首选项(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/Data/Preferences)
\ No newline at end of file
# 首选项概述
首选项Preferences,适用于对`Key-Value`结构的数据进行存取和持久化操作。
应用获取某个`Preferences`对象后,该存储对象中的数据将会被缓存在内存中,以便应用获得更快的数据存取速度。
应用也可以将缓存的数据再次写回文本文件中进行持久化存储,由于文件读写将产生不可避免的系统资源开销,建议应用降低对持久化文件的读写频率。
关于数据库锁机制,开发者无需关注其具体实现。
## 基本概念
- **Key-Value数据结构**
一种键值型的数据结构。`Key`是不重复的关键字,`Value`是数据值。
- **非关系型数据库**
区别于关系数据库,不保证遵循ACID(Atomic、Consistency、Isolation及Durability)特性,不采用关系模型来组织数据,数据之间无关系。比如,以`Key-Value`数据结构组成的数据库。
## 运作机制
1. 应用通过指定首选项持久化文件将其中的数据加载到`Preferences`实例,系统会通过静态容器将该实例存储在内存中,同一应用或进程中每个文件仅存在一个`Preferences`实例,直到应用主动从内存中移除该实例或者删除该首选项持久化文件。
2. 应用获取到首选项持久化文件对应的实例后,可以从`Preferences`实例中读取数据,或者将数据存入`Preferences`实例中。通过调用flush方法可以将`Preferences`实例中的数据回写到文件里。
**图1** 首选项运作机制
![zh-cn_image_0000001199139454](figures/zh-cn_image_0000001199139454.png)
## 约束与限制
-`Preferences`实例会加载到内存中,建议存储的数据不超过一万条,并注意及时清理不再使用的实例,以便减少非内存开销。
- 数据中的`Key``string`类型,要求非空且字符长度不超过80个字节。
- 当数据中的`Value``string`类型时,允许为空,字符长度不超过8192个字节。
\ No newline at end of file
# 关系型数据库概述
关系型数据库(Relational Database,RDB)是一种基于关系模型来管理数据的数据库。关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。当应用卸载后,其相关数据库会被自动清除。
关于数据库锁机制,开发者无需关注其具体实现。
## 基本概念
- **关系型数据库**
基于关系模型来管理数据的数据库,以行和列的形式存储数据。
- **谓词**
数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。
- **结果集**
指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便的拿到用户想要的数据。
- **SQLite数据库**
一款遵守ACID的轻型开源关系型数据库管理系统。
## 运作机制
关系型数据库对外提供通用的操作接口,底层使用SQLite作为持久化存储引擎,支持SQLite具有的所有数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译SQL语句。
**图1** 关系型数据库运作机制
![how-rdb-works](figures/how-rdb-works.png)
## 默认配置
- 系统默认日志方式是WAL(Write Ahead Log)模式。
- 系统默认落盘方式是FULL模式。
- OpenHarmony数据库使用的共享内存默认大小是2MB。
## 约束与限制
- 数据库中连接池的最大数量是4个,用以管理用户的读操作。
- 为保证数据的准确性,数据库同一时间只能支持一个写操作。
\ No newline at end of file
# 通过DataShareExtensionAbility实现数据共享
## 场景介绍
跨应用访问数据时,可以通过DataShareExtensionAbility拉起数据提供方的应用以实现对数据的访问。
此种方式支持跨应用拉起数据提供方的DataShareExtension,数据提供方的开发者可以在回调中实现灵活的业务逻辑。用于跨应用复杂业务场景。
## 运作机制
数据共享可分为数据的提供方和访问方两部分。
- 数据提供方:[DataShareExtensionAbility](../reference/apis/js-apis-application-dataShareExtensionAbility.md),可以选择性实现数据的增、删、改、查,以及文件打开等功能,并对外共享这些数据。
- 数据访问方:由[createDataShareHelper()](../reference/apis/js-apis-data-dataShare.md/#datasharecreatedatasharehelper)方法所创建的工具类,利用工具类,便可以访问提供方提供的这些数据。
**图1** 数据共享运作机制  
![dataShare](figures/dataShare.jpg)
- DataShareExtensionAbility模块为数据提供方,实现跨应用数据共享的相关业务。
- DataShareHelper模块为数据访问方,提供各种访问数据的接口,包括增删改查等。
- 数据访问方与提供方通过IPC进行通信,数据提供方可以通过数据库实现,也可以通过其他数据存储方式实现。
- ResultSet模块通过共享内存实现,用于存储查询数据得到的结果集,并提供了遍历结果集的方法。
## 实现说明
### 数据提供方应用的开发(仅限系统应用)
[DataShareExtensionAbility](../reference/apis/js-apis-application-dataShareExtensionAbility.md)提供以下API,根据需要重写对应回调方法。
- **onCreate**:DataShare客户端连接DataShareExtensionAbility服务端时,服务端需要在此回调中实现初始化业务逻辑,该方法可以选择性重写。
- **insert**:业务函数,客户端请求插入数据时回调此接口,服务端需要在此回调中实现插入数据功能,该方法可以选择性重写。
- **update**:业务函数,客户端请求更新数据时回调此接口,服务端需要在此回调中实现更新数据功能,该方法可以选择性重写。
- **delete**:业务函数,客户端请求删除数据时回调此接口,服务端需要在此回调中实现删除数据功能,该方法可以选择性重写。
- **query**:业务函数,客户端请求查询数据时回调此接口,服务端需要在此回调中实现查询数据功能,该方法可以选择性重写。
- **batchInsert**:业务函数,客户端请求批量插入数据时回调此接口,服务端需要在此回调中实现批量插入数据的功能,该方法可以选择性重写。
- **normalizeUri**:业务函数,客户端给定的URI转换为服务端使用的URI时回调此接口,该方法可以选择性重写。
- **denormalizeUri**:业务函数,服务端使用的URI转换为客户端传入的初始URI时服务端回调此接口,该方法可以选择性重写。
开发者在实现一个数据共享服务时,需要在DevEco Studio工程中手动新建一个DataShareExtensionAbility,具体步骤如下。
1. 在工程Module对应的ets目录下,右键选择“New &gt; Directory”,新建一个目录并命名为DataShareExtAbility。
2. 在DataShareAbility目录,右键选择“New &gt; TypeScript File”,新建一个TypeScript文件并命名为DataShareExtAbility.ts。
3. 在DataShareExtAbility.ts文件中,导入
`@ohos.application.DataShareExtensionAbility`模块,开发者可根据应用需求选择性重写其业务实现。例如数据提供方只提供插入、删除和查询服务,则可只重写这些接口,并导入对应的基础依赖模块。
```js
import Extension from '@ohos.application.DataShareExtensionAbility';
import rdb from '@ohos.data.relationalStore';
import dataSharePredicates from '@ohos.data.dataSharePredicates';
```
4. 数据提供方的业务实现由开发者自定义。例如可以通过数据库、读写文件或访问网络等各方式实现数据提供方的数据存储。
```js
const DB_NAME = 'DB00.db';
const TBL_NAME = 'TBL00';
const DDL_TBL_CREATE = "CREATE TABLE IF NOT EXISTS "
+ TBL_NAME
+ ' (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER, isStudent BOOLEAN, Binary BINARY)';
let rdbStore;
let result;
export default class DataShareExtAbility extends Extension {
private rdbStore_;
// 重写onCreate接口
onCreate(want, callback) {
result = this.context.cacheDir + '/datashare.txt';
// 业务实现使用RDB
rdb.getRdbStore(this.context, {
name: DB_NAME,
securityLevel: rdb.SecurityLevel.S1
}, function (err, data) {
rdbStore = data;
rdbStore.executeSql(DDL_TBL_CREATE, [], (err) => {
console.info(`DataShareExtAbility onCreate, executeSql done err:${err}`);
});
if (callback) {
callback();
}
});
}
// 重写query接口
query(uri, predicates, columns, callback) {
if (predicates === null || predicates === undefined) {
console.info('invalid predicates');
}
try {
rdbStore.query(TBL_NAME, predicates, columns, (err, resultSet) => {
if (resultSet !== undefined) {
console.info(`resultSet.rowCount:${resultSet.rowCount}`);
}
if (callback !== undefined) {
callback(err, resultSet);
}
});
} catch (err) {
console.error(`Failed to query. Code:${err.code},message:${err.message}`);
}
}
// 可根据应用需求,选择性重写各个接口
};
```
5. 在module.json5中定义DataShareExtensionAbility。
**表1** module.json5对应属性字段
| 属性名称 | 备注说明 | 必填 |
| -------- | -------- | -------- |
| name | Ability名称,对应Ability派生的ExtensionAbility类名。 | 是 |
| type | Ability类型,DataShare对应的Ability类型为“dataShare”,表示基于datashare模板开发的。 | 是 |
| uri | 通信使用的URI,是客户端链接服务端的唯一标识。 | 是 |
| exported | 对其他应用是否可见,设置为true时,才能与其他应用进行通信传输数据。 | 是 |
| readPermission | 访问数据时需要的权限,不配置默认不进行读权限校验。 | 否 |
| writePermission | 修改数据时需要的权限,不配置默认不进行写权限校验。 | 否 |
**module.json5配置样例:**
```json
"extensionAbilities": [
{
"srcEntrance": "./ets/DataShareExtAbility/DataShareExtAbility.ts",
"name": "DataShareExtAbility",
"icon": "$media:icon",
"description": "$string:description_datashareextability",
"type": "dataShare",
"uri": "datashare://com.samples.datasharetest.DataShare",
"exported": true
}
]
```
### 数据访问方应用的开发
1. 导入基础依赖包。
```js
import UIAbility from '@ohos.app.ability.UIAbility';
import dataShare from '@ohos.data.dataShare';
import dataSharePredicates from '@ohos.data.dataSharePredicates';
```
2. 定义与数据提供方通信的URI字符串。
```js
// 作为参数传递的URI,与module.json5中定义的URI的区别是多了一个"/",是因为作为参数传递的URI中,在第二个与第三个"/"中间,存在一个DeviceID的参数
let dseUri = ('datashare:///com.samples.datasharetest.DataShare');
```
3. 创建工具接口类对象。
```js
let dsHelper;
let abilityContext;
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage) {
abilityContext = this.context;
dataShare.createDataShareHelper(abilityContext, dseUri, (err, data) => {
dsHelper = data;
});
}
}
```
4. 获取到接口类对象后,便可利用其提供的接口访问提供方提供的服务,如进行数据的增删改查等。
```js
// 构建一条数据
let valuesBucket = { 'name': 'ZhangSan', 'age': 21, 'isStudent': false, 'Binary': new Uint8Array([1, 2, 3]) };
let updateBucket = { 'name': 'LiSi', 'age': 18, 'isStudent': true, 'Binary': new Uint8Array([1, 2, 3]) };
let predicates = new dataSharePredicates.DataSharePredicates();
let valArray = ['*'];
// 插入一条数据
dsHelper.insert(dseUri, valuesBucket, (err, data) => {
console.info(`dsHelper insert result:${data}`);
});
// 更新数据
dsHelper.update(dseUri, predicates, updateBucket, (err, data) => {
console.info(`dsHelper update result:${data}`);
});
// 查询数据
dsHelper.query(dseUri, predicates, valArray, (err, data) => {
console.info(`dsHelper query result:${data}`);
});
// 删除指定的数据
dsHelper.delete(dseUri, predicates, (err, data) => {
console.info(`dsHelper delete result:${data}`);
});
```
# 通过静默数据访问实现数据共享
## 场景介绍
据大数据统计,典型跨应用访问数据的用户场景下,一个用户一天平均拉起次数接近83次。
为了降低应用拉起次数,提高访问速度,OpenHarmony提供了一种不拉起数据提供方直接访问数据库的方式,即静默数据访问。
静默数据仅支持数据库的基本访问,如果有业务处理,建议将业务处理放到数据访问方。
如果业务过于复杂,无法放到数据访问方,建议通过DataShareExtensionAbility拉起数据提供方实现功能。
## 运作机制
**图1** 静默数据访问视图
![silent_dataShare](figures/silent_dataShare.jpg)
- 和跨应用数据共享方式不同的是,静默数据访问借助数据管理服务通过目录映射方式直接读取数据提供方的配置,按规则进行预处理后,并访问数据库。
- 数据访问方如果使用静默数据访问方式,URI需严格按照如下格式:
datashare:///{bundleName}/{moduleName}/{storeName}/{tableName}?Proxy=true
Proxy=true表示通过静默方式访问数据不拉起数据提供方,如果没有此项则会拉起数据提供方。
数据管理服务会读取对应bundleName作为数据提供方应用,读取配置,进行权限校验并访问对应数据。
## 约束与限制
- 目前仅关系型数据库支持静默数据访问方式。
- 整个系统最多同时并发16路查询,有多出来的查询请求需要排队处理。
- 不支持代理创建数据库,如果需要创建数据库,需要拉起数据提供方。
## 实现说明
URI需严格按照如下格式:
datashare:///{bundleName}/{moduleName}/{storeName}/{tableName}?Proxy=true
其他开发步骤与实现,具体可参照[通过DataShareExtensionAbility实现数据共享](share-data-by-datashareextensionability.md)
# 同设备跨应用数据共享概述
## 功能简介
数据共享(DataShare) 提供了向其他应用共享以及管理其数据的方法,支持同个设备上不同应用之间的数据共享。
在许多应用场景中都需要用到数据共享,比如将电话簿、短信、媒体库中的数据共享给其他应用等。当然,不是所有的数据都允许其他应用访问,比如帐号、密码等;有些数据也只允许其他应用查询而不允许其删改,比如短信等。所以对于各种数据共享场景,DataShare这样一个安全、便捷的可以跨应用的数据共享机制是十分必要的。
数据提供者无需进行繁琐的封装,可直接使用DataShare向其他应用共享数据;对数据访问方来说,因DataShare的访问方式不会因数据提供的方式而不同,只需要学习和使用一套接口即可,大大减少了学习时间和开发难度。
跨应用数据共享有两种方式:
- **使用DataShareExtensionAbility实现数据共享**
这种方式通过在HAP中实现一个extension,在extension中可以实现回调,在访问方调用对应接口时,会自动拉起提供方对应的extension,并调用对应回调。
这种方式适用于跨应用数据访问时有业务的操作,不仅是对数据库的增删改查的情况。
- **通过静默访问实现数据共享**
这种方式通过在HAP中配置数据库的访问规则,在访问方调用对应接口时,会自动通过系统服务读取HAP配置规则,按照规则返回数据,不会拉起数据提供方。
这种方式适用于跨应用数据访问仅为数据库的增删改查,没有特殊业务的情况。
## 基本概念
在进行同设备跨应用数据共享开发前,先了解以下相关概念。
- **数据提供方**:提供数据及实现相关业务的应用程序,也称为生产者或服务端。
- **数据访问方**:访问数据提供方所提供的数据或业务的应用程序,也称为消费者或客户端。
- **数据集**:用户要插入的数据集合,可以是一条或多条数据。数据集以键值对的形式存在,键为字符串类型,值支持数字、字符串、布尔值、无符号整型数组等多种数据类型。
- **结果集**:用户查询之后的结果集合,其提供了灵活的数据访问方式,以便用户获取各项数据。
- **谓词**:用户访问数据库中的数据所使用的筛选条件,经常被应用在更新数据、删除数据和查询数据等场景。
## 约束限制
- DataShare受到数据提供方所使用数据库的一些限制。例如支持的数据模型、Key的长度、Value的长度、每个应用程序支持同时打开数据库的最大数量等,都会受到使用的数据库的限制。
- 因DataShare内部实现依赖于IPC通信,所以数据集、谓词、结果集等的载荷受到IPC通信的约束与限制。
- 当前仅支持在Stage模型下,进行同设备跨应用数据共享相关能力的开发。
# 同应用跨设备数据同步概述
## 场景介绍
跨设备数据同步功能(即分布式功能),指将数据同步到一个组网环境中的其他设备。常用于用户应用程序数据内容在可信认证的不同设备间,进行自由同步、修改和查询。
例如:当设备1上的应用A在分布式数据库中增、删、改数据后,设备2上的应用A也可以获取到该数据库变化。可在分布式图库、备忘录、联系人、文件管理器等场景中使用。
不同应用间订阅数据库变化通知,请参考[同设备跨应用数据共享](share-device-data-across-apps.md)实现。
根据跨设备同步数据生命周期不同,可以分为:
- 临时数据生命周期较短,通常保存内存中。比如游戏应用产生的过程数据,建议使用分布式数据对象;
- 持久数据生命周期较长,需要保存到存储的数据库中,根据数据关系和特点,选择关系型或者键值型数据库。比如图库应用的各种相册、封面、图片等属性信息,建议使用关系型数据库;图库应用的具体图片缩略图,建议使用键值型数据库。
## 基本概念
在分布式场景中,会涉及多个设备,组网内设备之间看到的数据是否一致称为分布式数据库的一致性。
分布式数据库一致性可以分为强一致性、弱一致性和最终一致性。
- 强一致性:是指某一设备成功增、删、改数据后,组网内设备对该数据的读取操作都将得到更新后的值。对于已改变写的数据的读取,最终都能取得已更新的数据,但不完全保证能立即取得已更新的数据。
- 弱一致性:是指某一设备成功增、删、改数据后,组网内设备可能读取到本次更新后的数据,也可能读取不到,不能保证在多长时间后每个设备的数据一定是一致的。
- 最终一致性:是指某一设备成功增、删、改数据后,组网内设备可能读取不到本次更新后的数据,但在某个时间窗口之后组网内设备的数据能够达到一致状态。
强一致性对分布式数据的管理要求非常高,在服务器的分布式场景可能会遇到。因为移动终端设备的不常在线、以及无中心的特性,所以同应用跨设备数据同步不支持强一致性,只支持最终一致性。
## 跨设备同步访问控制机制
数据跨设备同步时,数据管理基于设备等级和[数据安全标签](access-control-by-device-and-data-level.md#基本概念)进行访问控制。规则为,数据只允许向数据安全标签不高于设备安全等级的设备同步数据,具体访问控制矩阵如下:
| | | | | | |
| -------- | -------- | -------- | -------- | -------- | -------- |
| 设备安全级别 | SL1 | SL2 | SL3 | SL4 | SL5 |
| 可同步的数据安全标签 | S1 | S1~S2 | S1~S3 | S1~S4 | S1~S4 |
例如,对于类似rk3568、hi3516的开发板设备,设备安全等级为SL1,若创建数据安全标签为S1的数据库,则此数据库数据可以在这些设备间同步;若创建的数据库标签为S2-S4,则不能同步。
# 文件管理 # 文件
- 媒体库管理
- [媒体库开发概述](medialibrary-overview.md)
- [媒体资源使用指导](medialibrary-resource-guidelines.md)
- [文件路径使用指导](medialibrary-filepath-guidelines.md)
- [相册资源使用指导](medialibrary-album-guidelines.md)
- 文件访问框架 - [文件管理概述](file-management-overview.md)
- [用户公共文件访问框架概述](file-access-framework-overview.md) - 应用文件
- [文件选择器使用指导](filepicker-guidelines.md) - [应用文件概述](app-file-overview.md)
\ No newline at end of file - [应用沙箱目录](app-sandbox-directory.md)
- 应用文件访问与管理
- [应用文件访问](app-file-access.md)
- [应用文件上传下载](app-file-upload-download.md)
- [应用及文件系统空间统计](app-fs-space-statistics.md)
- [向应用沙箱推送文件](send-file-to-app-sandbox.md)
- [应用文件分享](share-app-file.md)
- 用户文件
- [用户文件概述](user-file-overview.md)
- 选择与保存用户文件(FilePicker)
- [选择用户文件](select-user-file.md)
- [保存用户文件](save-user-file.md)
- [开发用户文件管理器(仅对系统应用开放)](dev-user-file-manager.md)
- [管理外置存储设备(仅对系统应用开放)](manage-external-storage.md)
- 分布式文件系统
- [分布式文件系统概述](distributed-fs-overview.md)
- [设置分布式文件数据等级](set-security-label.md)
- [跨设备文件访问](file-access-across-devices.md)
# 应用文件访问
应用需要对应用文件目录下的应用文件进行查看、创建、读写、删除、移动、复制、获取属性等访问操作,下文介绍具体方法。
## 接口说明
开发者通过基础文件操作接口([ohos.file.fs](../reference/apis/js-apis-file-fs.md))实现应用文件访问能力,主要功能如下表所示。
**表1** 基础文件操作接口功能
| 接口名 | 功能 | 接口类型 | 支持同步 | 支持异步 |
| -------- | -------- | -------- | -------- | -------- |
| access | 检查文件是否存在 | 方法 | √ | √ |
| close | 关闭文件 | 方法 | √ | √ |
| copyFile | 复制文件 | 方法 | √ | √ |
| createStream | 基于文件路径打开文件流 | 方法 | √ | √ |
| listFile | 列出文件夹下所有文件名 | 方法 | √ | √ |
| mkdir | 创建目录 | 方法 | √ | √ |
| moveFile | 移动文件 | 方法 | √ | √ |
| open | 打开文件 | 方法 | √ | √ |
| read | 从文件读取数据 | 方法 | √ | √ |
| rename | 重命名文件或文件夹 | 方法 | √ | √ |
| rmdir | 删除整个目录 | 方法 | √ | √ |
| stat | 获取文件详细属性信息 | 方法 | √ | √ |
| unlink | 删除单个文件 | 方法 | √ | √ |
| write | 将数据写入文件 | 方法 | √ | √ |
| Stream.close | 关闭文件流 | 方法 | √ | √ |
| Stream.flush | 刷新文件流 | 方法 | √ | √ |
| Stream.write | 将数据写入流文件 | 方法 | √ | √ |
| Stream.read | 从流文件读取数据 | 方法 | √ | √ |
| File.fd | 获取文件描述符 | 属性 | √ | × |
| OpenMode | 设置文件打开标签 | 属性 | √ | × |
| Filter | 设置文件过滤配置项 | 类型 | × | × |
## 开发示例
在对应用文件开始访问前,开发者需要[获取应用文件路径](../application-models/application-context-stage.md#获取应用开发路径)。以从UIAbilityContext获取HAP级别的文件路径为例进行说明,UIAbilityContext的获取方式请参见[获取UIAbility的上下文信息](../application-models/uiability-usage.md#获取uiability的上下文信息)
下面介绍几种常用操作示例。
### 新建并读写一个文件
以下示例代码演示了如何新建一个文件并对其读写。
```ts
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
function createFile() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 新建并打开文件
let file = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 写入一段内容至文件
let writeLen = fs.writeSync(file.fd, "Try to write str.");
console.info("The length of str is: " + writeLen);
// 从文件读取一段内容
let buf = new ArrayBuffer(1024);
let readLen = fs.readSync(file.fd, buf, { offset: 0 });
console.info("the content of file: " + String.fromCharCode.apply(null, new Uint8Array(buf.slice(0, readLen))));
// 关闭文件
fs.closeSync(file);
}
```
### 读取文件内容并写入到另一个文件
以下示例代码演示了如何从一个文件读写内容到另一个文件。
```ts
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
function readWriteFile() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 打开文件
let srcFile = fs.openSync(filesDir + '/test.txt', fs.OpenMode.READ_WRITE);
let destFile = fs.openSync(filesDir + '/destFile.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 读取源文件内容并写入至目的文件
let bufSize = 4096;
let readSize = 0;
let buf = new ArrayBuffer(bufSize);
let readLen = fs.readSync(srcFile.fd, buf, { offset: readSize });
while (readLen > 0) {
readSize += readLen;
fs.writeSync(destFile.fd, buf);
readLen = fs.readSync(srcFile.fd, buf, { offset: readSize });
}
// 关闭文件
fs.closeSync(srcFile);
fs.closeSync(destFile);
}
```
> **说明:**
>
> 使用读写接口时,需注意可选项参数offset的设置。对于已存在且读写过的文件,文件偏移指针默认在上次读写操作的终止位置。
### 以流的形式读写文件
以下示例代码演示了如何使用流接口进行文件读写:
```ts
// pages/xxx.ets
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
async function readWriteFileWithStream() {
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 打开文件流
let inputStream = fs.createStreamSync(filesDir + '/test.txt', 'r+');
let outputStream = fs.createStreamSync(filesDir + '/destFile.txt', "w+");
// 以流的形式读取源文件内容并写入目的文件
let bufSize = 4096;
let readSize = 0;
let buf = new ArrayBuffer(bufSize);
let readLen = await inputStream.read(buf, { offset: readSize });
readSize += readLen;
while (readLen > 0) {
await outputStream.write(buf);
readLen = await inputStream.read(buf, { offset: readSize });
readSize += readLen;
}
// 关闭文件流
inputStream.closeSync();
outputStream.closeSync();
}
```
> **说明:**
> 使用流接口时,需注意流的及时关闭。同时流的异步接口应严格遵循异步接口使用规范,避免同步、异步接口混用。流接口不支持并发读写。
### 查看文件列表
以下示例代码演示了如何查看文件列表:
```ts
// 查看文件列表
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
// 查看文件列表
let options = {
recursion: false,
listNum: 0,
filter: {
suffix: ['.png', '.jpg', '.txt'], // 匹配文件后缀名为'.png','.jpg','.txt'
displayName: ['test%'], // 匹配文件全名以'test'开头
fileSizeOver: 0, // 匹配文件大小大于等于0
lastModifiedAfter: new Date(0).getTime(), // 匹配文件最近修改时间在1970年1月1日之后
},
}
let files = fs.listFileSync(filesDir, options);
for (let i = 0; i < files.length; i++) {
console.info(`The name of file: ${files[i]}`);
}
```
# 应用文件概述
应用文件:文件所有者为应用,包括应用安装文件、应用资源文件、应用缓存文件等。
- 设备上应用所使用及存储的数据,以文件、键值对、数据库等形式保存在一个应用专属的目录内。该专属目录我们称为“应用文件目录”,该目录下所有数据以不同的文件格式存放,这些文件即应用文件。
- “应用文件目录”与一部分系统文件(应用运行必须使用的系统文件)所在的目录组成了一个集合,该集合称为“[应用沙箱目录](app-sandbox-directory.md)”,代表应用可见的所有目录范围。因此“应用文件目录”是在“应用沙箱目录”内的。
- 系统文件及其目录对于应用是只读的;应用仅能保存文件到“[应用文件目录](app-sandbox-directory.md#应用文件目录与应用文件路径)”下,根据目录的使用规范和注意事项来选择将数据保存到不同的子目录中。
下文将详细介绍应用沙箱、应用文件目录、应用文件访问与管理、应用文件分享等相关内容。
# 应用文件上传下载
应用可以将应用文件上传到网络服务器,也可以从网络服务器下载网络资源文件到本地应用文件目录。
## 上传应用文件
开发者可以使用上传下载模块([ohos.request](../reference/apis/js-apis-request.md))的上传接口将本地文件上传。文件上传过程使用系统服务代理完成。
> **说明:**
> 当前上传应用文件功能,仅支持上传应用缓存文件路径(cacheDir)下的文件。
>
> 使用上传下载模块,需[申请相关权限](../security/accesstoken-guidelines.md):ohos.permission.INTERNET。
以下示例代码演示了如何将应用缓存文件路径下的文件上传至网络服务器。
```ts
// pages/xxx.ets
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let cacheDir = context.cacheDir;
// 新建一个本地应用文件
let file = fs.openSync(cacheDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
fs.writeSync(file.fd, 'upload file test');
fs.closeSync(file);
// 上传任务配置项
let uploadConfig = {
url: 'https://xxx',
header: { key1: 'value1', key2: 'value2' },
method: 'POST',
files: [
{ filename: 'test.txt', name: 'test', uri: 'internal://cache/test.txt', type: 'txt' }
],
data: [
{ name: 'name', value: 'value' }
]
}
// 将本地应用文件上传至网络服务器
try {
request.uploadFile(context, uploadConfig)
.then((uploadTask) => {
uploadTask.on('complete', (taskStates) => {
for (let i = 0; i < taskStates.length; i++) {
console.info(`upload complete taskState: ${JSON.stringify(taskStates[i])}');
}
});
})
.catch((err) => {
console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
})
} catch (err) {
console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
}
```
## 下载网络资源文件至应用文件目录
开发者可以使用上传下载模块([ohos.request](../reference/apis/js-apis-request.md))的下载接口将网络资源文件下载到应用文件目录。对已下载的网络资源文件,开发者可以使用基础文件IO接口([ohos.file.fs](../reference/apis/js-apis-file-fs.md))对其进行访问,使用方式与[应用文件访问](app-file-access.md)一致。文件下载过程使用系统服务代理完成。
> **说明:**
> 当前网络资源文件仅支持下载至应用文件目录。
>
> 使用上传下载模块,需[申请相关权限](../security/accesstoken-guidelines.md):ohos.permission.INTERNET。
以下示例代码演示了如何将网络资源文件下载到应用文件目录:
```
// pages/xxx.ets
// 将网络资源文件下载到应用文件目录并读取一段内容
import common from '@ohos.app.ability.common';
import fs from '@ohos.file.fs';
import request from '@ohos.request';
// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let filesDir = context.filesDir;
try {
request.downloadFile(context, {
url: 'https://xxxx/xxxx.txt',
filePath: filesDir + '/xxxx.txt'
}).then((downloadTask) => {
downloadTask.on('complete', () => {
console.info('download complete');
let file = fs.openSync(filesDir + '/xxxx.txt', fs.OpenMode.READ_WRITE);
let buf = new ArrayBuffer(1024);
let readLen = fs.readSync(file.fd, buf);
console.info(`The content of file: ${String.fromCharCode.apply(null, new Uint8Array(buf.slice(0, readLen)))}`);
fs.closeSync(file);
})
}).catch((err) => {
console.error(`Invoke downloadTask failed, code is ${err.code}, message is ${err.message}`);
});
} catch (err) {
console.error(`Invoke downloadFile failed, code is ${err.code}, message is ${err.message}`);
}
```
# 应用及文件系统空间统计
在系统中,可能出现系统空间不够或者cacheDir等目录受系统配额限制等情况,需要应用开发者关注系统剩余空间,同时控制应用自身占用的空间大小。
## 接口说明
API的详细介绍请参见[ohos.file.statvfs](../reference/apis/js-apis-file-statvfs.md)[ohos.file.storageStatistics](../reference/apis/js-apis-storage-statistics.md)
**表1** 文件系统空间和应用空间统计
| 模块 | 接口名 | 功能 |
| -------- | -------- | -------- |
| \@ohos.file.storageStatistic | getCurrentBundleStats | 获取当前应用的存储空间大小(单位为Byte)。 |
| \@ohos.file.statvfs | getFreeSize | 获取指定文件系统的剩余空间大小(单位为Byte)。 |
| \@ohos.file.statvfs | getTotalSize | 获取指定文件系统的总空间大小(单位为Byte)。 |
**表2** 应用空间统计
| BundleStats属性 | 含义 | 统计路径 |
| -------- | -------- | -------- |
| appSize | 应用安装文件大小(单位为Byte) | 应用安装文件保存在以下目录:<br/>/data/storage/el1/bundle |
| cacheSize | 应用缓存文件大小(单位为Byte) | 应用的缓存文件保存在以下目录:<br/>/data/storage/el1/base/cache<br/>/data/storage/el1/base/haps/entry/cache<br/>/data/storage/el2/base/cache<br/>/data/storage/el2/base/haps/entry/cache |
| dataSize | 应用文件存储大小(除应用安装文件和缓存文件)(单位为Byte) | 应用文件由本地文件、分布式文件以及数据库文件组成。<br/>本地文件保存在以下目录(注意缓存文件目录为以下目录的子目录):<br/>/data/storage/el1/base<br/>/data/storage/el2/base<br/>分布式文件保存在以下目录:<br/>/data/storage/el2/distributedfiles<br/>数据库文件保存在以下目录:<br/>/data/storage/el1/database<br/>/data/storage/el2/database |
## 开发示例
- 获取文件系统数据分区剩余空间大小。
```ts
import statvfs from '@ohos.file.statvfs';
let path = "/data";
statvfs.getFreeSize(path, (err, number) => {
if (err) {
console.error(`Invoke getFreeSize failed, code is ${err.code}, message is ${err.message}`);
} else {
console.info(`Invoke getFreeSize succeeded, size is ${number}`);
}
});
```
- 获取当前应用的存储空间大小。
```ts
import storageStatistics from "@ohos.file.storageStatistics";
storageStatistics.getCurrentBundleStats((err, bundleStats) => {
if (err) {
console.error(`Invoke getCurrentBundleStats failed, code is ${err.code}, message is ${err.message}`);
} else {
console.info(`Invoke getCurrentBundleStats succeeded, appsize is ${bundleStats.appSize}`);
}
});
```
# 应用沙箱目录
应用沙箱是一种以安全防护为目的的隔离机制,避免数据受到恶意路径穿越访问。在这种沙箱的保护机制下,应用可见的目录范围即为“应用沙箱目录”。
- 对于每个应用,系统会在内部存储空间映射出一个专属的“应用沙箱目录”,它是“[应用文件目录](app-file-overview.md)”与一部分系统文件(应用运行必需的少量系统文件)所在的目录组成的集合。
- 应用沙箱限制了应用可见的数据的最小范围。在“应用沙箱目录”中,应用仅能看到自己的应用文件以及少量的系统文件(应用运行必需的少量系统文件)。因此,本应用的文件也不为其他应用可见,从而保护了应用文件的安全。
- 应用可以在“[应用文件目录](app-file-overview.md)”下保存和处理自己的应用文件;系统文件及其目录对于应用是只读的;而应用若需访问[用户文件](user-file-overview.md),则需要通过特定API同时经过用户的相应授权才能进行。
下图展示了应用沙箱下,应用可访问的文件范围和方式。
**图1** 应用沙箱文件访问关系图  
![Application sandbox file access relationship](figures/application-sandbox-file-access-relationship.png)
## 应用沙箱目录与应用沙箱路径
在应用沙箱保护机制下,应用无法获知除自身应用文件目录之外的其他应用或用户的数据目录位置及存在。同时,所有应用的目录可见范围均经过权限隔离与文件路径挂载隔离,形成了独立的路径视图,屏蔽了实际物理路径:
- 如下图所示,在普通应用(也称三方应用)视角下,不仅可见的目录与文件数量限制到了最小范围,并且可见的目录与文件路径也与系统进程等其他进程看到的不同。我们将普通应用视角下看到的“应用沙箱目录”下某个文件或某个具体目录的路径,称为“应用沙箱路径”。
- 一般情况下,开发者的hdc shell环境等效于系统进程视角,因此“应用沙箱路径”与开发者使用hdc工具调试时看到的真实物理路径不同,其对应关系详见[应用沙箱路径和调试进程视角下的真实物理路径](send-file-to-app-sandbox.md#应用沙箱路径和调试进程视角下的真实物理路径)
- 从实际物理路径推导物理路径与沙箱路径并不是1:1的映射关系,沙箱路径总是少于系统进程视角可见的物理路径。有些调试进程视角下的物理路径在对应的应用沙箱目录是无法找到的,而沙箱路径总是能够找到其对应的物理路径。
**图2** 应用沙箱路径(不同权限与角色的进程下可见的文件路径不同)  
![Application sandbox path](figures/application-sandbox-path.png)
## 应用文件目录与应用文件路径
如前文所述,“应用沙箱目录”内分为两类:应用文件目录和系统文件目录。
系统文件目录对应用的可见范围由OpenHarmony系统预置,开发者无需关注。
在此主要介绍应用文件目录,如下图所示。应用文件目录下某个文件或某个具体目录的路径称为应用文件路径。应用文件目录下的各个文件路径,具备不同的属性和特征。
**图3** 应用文件目录结构图  
![Application file directory structure](figures/application-file-directory-structure.png)
1. 一级目录data/:代表应用文件目录。
2. 二级目录storage/:代表本应用持久化文件目录。
3. 三级目录el1/、el2/:代表不同文件加密类型。
- el1,设备级加密区:设备开机后即可访问的数据区。
- el2,用户级加密区:设备开机后,需要至少一次解锁对应用户的锁屏界面(密码、指纹、人脸等方式或无密码状态)后,才能够访问的加密数据区。<br>
应用如无特殊需要,应将数据存放在el2加密目录下,以尽可能保证数据安全。但是对于某些场景,一些应用文件需要在用户解锁前就可被访问,例如时钟、闹铃、壁纸等,此时应用需要将这些文件存放到设备级加密区(el1)。切换应用文件加密类型目录的方法请参见[获取和修改加密分区](../application-models/application-context-stage.md#获取和修改加密分区)
4. 四级、五级目录:
通过ApplicationContext可以获取base下的files、cache、preferences、temp、distributedfiles等目录的应用文件路径,应用全局信息可以存放在这些目录下。
通过UIAbilityContext、AbilityStageContext、ExtensionContext可以获取hap级别应用文件路径。HAP信息可以存放在这些目录下,存放在此目录的文件会跟随HAP的卸载而删除,不会影响app级别目录下的文件。在开发态,一个应用包含一个或者多个HAP,详见[Stage模型应用程序包结构](../quick-start/application-package-structure-stage.md)
Context上下文获取及上述应用文件路径的获取,详见[应用上下文Context](../application-models/application-context-stage.md)
> **说明:**
> - 禁止直接使用上图中四级目录之前的目录名组成的路径字符串,否则可能导致后续应用版本因应用文件路径变化导致不兼容问题。
>
> - 应通过Context属性获取应用文件路径,包括但不限于上图中绿色背景的路径。
应用文件路径具体说明及生命周期如下表所示。
**表1** 应用文件路径详细说明
| 目录名 | Context属性名称 | 类型 | 说明 |
| -------- | -------- | -------- | -------- |
| bundle | bundleCodeDir | 安装文件路径 | 应用安装后的app的hap资源包所在目录;随应用卸载而清理。 |
| base | NA | 本设备文件路径 | 应用在本设备上存放持久化数据的目录,子目录包含files/、cache/、temp/和haps/;随应用卸载而清理。 |
| database | databaseDir | 数据库路径 | 应用在el1加密条件下存放通过分布式数据库服务操作的文件目录;随应用卸载而清理。 |
| distributedfiles | distributedFilesDir | 分布式文件路径 | 应用在el2加密条件下存放分布式文件的目录,应用将文件放入该目录可分布式跨设备直接访问;随应用卸载而清理。 |
| files | filesDir | 应用通用文件路径 | 应用在本设备内部存储上通用的存放默认长期保存的文件路径;随应用卸载而清理。 |
| cache | cacheDir | 应用缓存文件路径 | 应用在本设备内部存储上用于缓存下载的文件或可重新生成的缓存文件的路径,应用cache目录大小超过配额或者系统空间达到一定条件,自动触发清理该目录下文件;用户通过系统空间管理类应用也可能触发清理该目录。应用需判断文件是否仍存在,决策是否需重新缓存该文件。 |
| preferences | preferencesDir | 应用首选项文件路径 | 应用在本设备内部存储上通过数据库API存储配置类或首选项的目录;随应用卸载而清理。详见[通过用户首选项实现数据持久化](../database/data-persistence-by-preferences.md)。 |
| temp | tempDir | 应用临时文件路径 | 应用在本设备内部存储上仅在应用运行期间产生和需要的文件,应用退出后即清理。 |
对于上述各类应用文件路径,常见使用场景如下:
- 安装文件路径
可以用于存储应用的代码资源数据,主要包括应用安装的HAP资源包、可重复使用的库文件以及插件资源等。此路径下存储的代码资源数据可以被用于动态加载。
- 数据库路径
仅用于保存应用的私有数据库数据,主要包括数据库文件等。此路径下仅适用于存储分布式数据库相关文件数据。
- 分布式文件路径
可以用于保存应用分布式场景下的数据,主要包括应用多设备共享文件、应用多设备备份文件、应用多设备群组协助文件。此路径下存储这些数据,使得应用更加适合多设备使用场景。
- 应用通用文件路径
可以用于保存应用的任何私有数据,主要包括用户持久性文件、图片、媒体文件以及日志文件等。此路径下存储这些数据,使得数据保持私有、安全且持久有效。
- 应用缓存文件路径
可以用于保存应用的缓存数据,主要包括离线数据、图片缓存、数据库备份以及临时文件等。此路径下存储的数据可能会被系统自动清理,因此不要存储重要数据。
- 应用首选项文件路径
可以用于保存应用的首选项数据,主要包括应用首选项文件以及配置文件等。此路径下仅适用于存储小量数据。
- 应用临时文件路径
可以用于保存应用的临时生成的数据,主要包括数据库缓存、图片缓存、临时日志文件、以及下载的应用安装包文件等。此路径下存储使用后即可删除的数据。
# 开发用户文件管理器(仅对系统应用开放)
OpenHarmony预置了FileManager文件管理器。系统应用开发者也可以根据需要,按以下指导自行开发文件管理器。
## 接口说明
开发用户文件管理器的相关API详细介绍请参见[API参考](../reference/apis/js-apis-fileAccess.md)
## 开发步骤
1. 权限配置和导入模块。
申请ohos.permission.FILE_ACCESS_MANAGER和ohos.permission.GET_BUNDLE_INFO_PRIVILEGED权限,配置方式请参见[访问控制授权申请](../security/accesstoken-guidelines.md)
> **说明:**
>
> ohos.permission.FILE_ACCESS_MANAGER是使用文件访问框架接口的基础权限。
>
> ohos.permission.GET_BUNDLE_INFO_PRIVILEGED权限可以用于查询系统内当前支持的文件管理服务端应用信息。
2. 导入依赖模块。
```ts
import fileAccess from '@ohos.file.fileAccess';
import fileExtensionInfo from '@ohos.file.fileExtensionInfo';
```
其中fileAccess提供了文件基础操作的API,fileExtensionInfo提供了应用开发的关键结构体。
3. 查询设备列表。
开发者可以获取当前系统所有文件管理服务端管理的设备属性,也可以获取某个文件管理服务端管理的设备属性。应用开发者可以按需过滤设备。
在文件访问框架中,使用RootInfo用于表示设备的属性信息。以下示例可以获取所有设备的RootInfo。
```ts
// 创建连接系统内所有文件管理服务端的helper对象
let fileAccessHelperAllServer = null;
createFileAccessHelper() {
try { // this.context是EntryAbility传过来的Context
fileAccessHelperAllServer = fileAccess.createFileAccessHelper(this.context);
if (!fileAccessHelperAllServer) {
console.error("createFileAccessHelper interface returns an undefined object");
}
} catch (error) {
console.error("createFileAccessHelper failed, errCode:" + error.code + ", errMessage:" + error.message);
}
}
async getRoots() {
let rootIterator = null;
let rootInfos = [];
let isDone = false;
try {
rootIterator = await fileAccessHelperAllServer.getRoots();
if (!rootIterator) {
console.error("getRoots interface returns an undefined object");
return;
}
while (!isDone) {
let result = rootIterator.next();
console.info("next result = " + JSON.stringify(result));
isDone = result.done;
if (!isDone)
rootinfos.push(result.value);
}
} catch (error) {
console.error("getRoots failed, errCode:" + error.code + ", errMessage:" + error.message);
}
}
```
4. 浏览目录。
在文件访问框架中,使用FileInfo表示一个文件(目录)的基础信息。开发者可以使用listfile接口遍历下一级所有文件(目录)的迭代器对象;也可以通过scanfile过滤指定目录,获取满足条件的迭代器对象。
listfile和scanfile接口当前支持RootInfo对象调用,可用于支撑遍历下一级文件或过滤整个目录树。同时,接口也支持FileInfo对象调用,用于支撑遍历下一级文件或过滤指定目录。
```ts
// 从根目录开始
let rootInfo = rootinfos[0];
let fileInfos = [];
let isDone = false;
let filter = {suffix : [".txt", ".jpg", ".xlsx"]}; // 设定过滤条件
try {
let fileIterator = rootInfo.listFile(); // 遍历设备rootinfos[0]的根目录,返回迭代器对象
// let fileIterator = rootInfo.scanFile(filter); // 过滤设备rootinfos[0]满足指定条件的文件信息,返回迭代对象
if (!fileIterator) {
console.error("listFile interface returns an undefined object");
return;
}
while (!isDone) {
let result = fileIterator.next();
console.info("next result = " + JSON.stringify(result));
isDone = result.done;
if (!isDone)
fileInfos.push(result.value);
}
} catch (error) {
console.error("listFile failed, errCode:" + error.code + ", errMessage:" + error.message);
}
// 从指定的目录开始
let fileInfoDir = fileInfos[0]; // fileInfoDir 表示某个目录信息
let subFileInfos = [];
let isDone = false;
let filter = {suffix : [".txt", ".jpg", ".xlsx"]}; // 设定过滤条件
try {
let fileIterator = fileInfoDir.listFile(); // 遍历特定的目录fileinfo,返回迭代器对象
// let fileIterator = rootInfo.scanFile(filter); // 过滤特定的目录fileinfo,返回迭代器对象
if (!fileIterator) {
console.error("listFile interface returns an undefined object");
return;
}
while (!isDone) {
let result = fileIterator.next();
console.info("next result = " + JSON.stringify(result));
isDone = result.done;
if (!isDone)
subfileInfos.push(result.value);
}
} catch (error) {
console.error("listFile failed, errCode:" + error.code + ", errMessage:" + error.message);
}
```
5. 操作文件或目录。
开发者可以集成文件访问框架的接口,完成一些用户行为,比如删除文件(目录)、重命名文件(目录)、新建文件(目录)、移动文件(目录)等。以下示例展示了如何创建一个文件,其他接口请参见[API参考](../reference/apis/js-apis-fileAccess.md)
```ts
// 以本地设备为例
// 创建文件
// 示例代码sourceUri是Download目录的fileinfo中的URI
// 开发者应根据自己实际获取fileinfo的URI进行开发
let sourceUri = "datashare:///media/file/6";
let displayName = "file1";
let fileUri = null;
try {
// fileAccessHelper 参考 fileAccess.createFileAccessHelper 示例代码获取
fileUri = await fileAccessHelper.createFile(sourceUri, displayName);
if (!fileUri) {
console.error("createFile return undefined object");
return;
}
console.info("createFile sucess, fileUri: " + JSON.stringify(fileUri));
} catch (error) {
console.error("createFile failed, errCode:" + error.code + ", errMessage:" + error.message);
};
```
# 分布式文件系统概述
分布式文件系统(hmdfs,Harmony Distributed File System)提供跨设备的文件访问能力,适用于如下场景:
- 两台设备组网,用户可以利用一台设备上的编辑软件编辑另外一台设备上的文档。
- 平板保存的音乐,车载系统直接可见并可播放。
- 户外拍摄的照片,回家打开平板直接访问原设备拍摄的照片。
hmdfs在分布式软总线动态组网的基础上,为网络上各个设备结点提供一个全局一致的访问视图,支持开发者通过基础文件系统接口进行读写访问,具有高性能、低延时等优点。
## 分布式文件系统架构
![Distributed File System Architecture](figures/distributed-file-system-architecture.png)
- distributedfile_daemon:主要负责设备上线监听、通过软总线建立链路,并根据分布式的设备安全等级执行不同的数据流转策略。
- hmdfs:实现在内核的网络文件系统,包括缓存管理、文件访问、元数据管理和冲突管理等。
- 缓存管理
- 设备分布式组网后,hmdfs提供文件的互访能力,但不会主动进行文件数据传输和拷贝。如果应用需要将数据保存到本地,需主动拷贝。
- hmdfs保证Close-to-Open的一致性,即一端写关闭后,另外一端可以读取到最新数据,不保证文件内容的实时一致性。
- 数据在远端写入,但是由于网络原因未及时回刷,文件系统会在下次网络接入时回刷本地,但是如果远端已修改则无法回刷。
- 文件访问
- 文件访问接口与本地一致([ohos.file.fs](../reference/apis/js-apis-file-fs.md))。
- 如果文件在本地,则堆叠访问本地文件系统。
- 如果文件在其他设备,则同步网络访问远端设备文件。
> **说明:**
>
> symlink:不支持。
- 元数据管理
- 分布式组网下,文件一端创建、删除、修改,另一端可以“立即”查看到最新文件,看到速度取决于网络情况。
- 远端设备离线后,该设备数据将不再在本端设备呈现。但由于设备离线的感知具有延迟,可能会造成部分消息4s超时,因此开发者需要考虑接口的网络超时或一些文件虽然可以看到,但实际设备可能已离线的场景。
- 冲突处理
- 本地与远端冲突 ,远端文件被重命名,看到的同名文件是本地同名文件,远端文件被重命名。
- 远端多个设备冲突,以接入本设备ID为顺序,显示设备ID小的同名文件,其他文件被依次重命名。
- 如果组网场景,目录树下已经有远端文件,创建同名文件,提示文件已存在。
- 冲突文件显示_conflict_dev后依次加id,id从1自动递增。
- 同名目录之间仅融合不存在冲突,文件和远端目录同名冲突,远端目录后缀加_remote_directory。
# 跨设备文件访问
分布式文件系统为应用提供了跨设备文件访问的能力,开发者在多个设备安装同一应用时,通过[基础文件接口](app-file-access-mgmt.md),可跨设备读写其他设备该应用分布式文件路径(/data/storage/el2/distributedfiles/)下的文件。例如:多设备数据流转的场景,设备组网互联之后,设备A上的应用可访问设备B同应用分布式路径下的文件,当期望应用文件被其他设备访问时,只需将文件移动到分布式文件路径即可。
## 开发步骤
1. 完成分布式组网。
首先将需要进行跨设备访问的设备连接到同一局域网中,同帐号认证完成组网。
2. 访问跨设备文件。
同一应用不同设备之间实现跨设备文件访问,只需要将对应的文件放在应用沙箱的分布式文件路径即可。
设备A上在分布式路径下创建测试文件,并写入内容。示例中的context的获取方式请参见[获取UIAbility的上下文信息](../application-models/uiability-usage.md#获取uiability的上下文信息)
```ts
import fs from '@ohos.file.fs';
let context = ...; // 获取设备A的UIAbilityContext信息
let pathDir = context.distributedFilesDir;
// 获取分布式目录的文件路径
let filePath = pathDir + '/test.txt';
try {
// 在分布式目录下创建文件
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
console.info('Succeeded in createing.');
// 向文件中写入内容
fs.writeSync(file.fd, 'content');
// 关闭文件
fs.closeSync(file.fd);
} catch (err) {
console.error(`Failed to openSync / writeSync / closeSync. Code: ${err.code}, message: ${err.message}`);
}
```
设备B上在分布式路径下读取测试文件。
```ts
import fs from '@ohos.file.fs';
let context = ...; // 获取设备B的UIAbilityContext信息
let pathDir = context.distributedFilesDir;
// 获取分布式目录的文件路径
let filePath = pathDir + '/test.txt';
try {
// 打开分布式目录下的文件
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE);
// 定义接收读取数据的缓存
let buffer = new ArrayBuffer(4096);
// 读取文件的内容,返回值是读取到的字节个数
let num = fs.readSync(file.fd, buffer, {
offset: 0
});
// 打印读取到的文件数据
console.info('read result: ' + String.fromCharCode.apply(null, new Uint8Array(buffer.slice(0, num))));
} catch (err) {
console.error(`Failed to openSync / readSync. Code: ${err.code}, message: ${err.message}`);
}
```
# 用户公共文件访问框架概述
在搭载OpenHarmony 3.2 (API 9)及更高版本的设备上,应用可以基于FileAccessFramework(简称为FAF)对本地公共文件、分布式设备文件、外部存储设备文件、多用户共享文件进行访问。
出于对用户数据的隐私安全考虑,目前此框架仅支持用户通过**文件管理器****文件选择器**对文件访问服务端进行操作,包括创建、打开、删除、重命名、移动等。
应用卸载,不会影响到用户数据,实际用户数据仍保留在对应设备中。
其它系统应用如需要访问本地公共文件,请参考使用[媒体库接口](medialibrary-filepath-guidelines.md)
> **注意:**
> 1:如果应用是系统非管理类应用,比如:系统图库,请优先考虑直接使用媒体库接口,媒体库接口提供了一系列直接操作文件的接口。
> 2:FAF 接口与媒体库接口原则上不能混用。
## FileAccessFramework机制介绍
FAF依托于OpenHarmony上[ExtensionAbility机制](../application-models/extensionability-overview.md),实现了一套对外提供能力的统一接口。应用可以通过这套接口预览和操作公共文件,实现自己的逻辑。
有兴趣的开发者,可以预览我们的[源码仓](https://gitee.com/openharmony/filemanagement_user_file_service),提出您宝贵的意见。
基于FAF进行文件操作的全流程,包含以下几个元素,如图所示:
**图1 公共文件操作层次图**
![](figures/公共文件操作层次图.png)
- **文件访问客户端应用** - 需要访问或操作公共文件的应用。通过拉起文件选择器,用户可以在可视化界面上进行文件操作。
- **文件选择器应用** - 可以让用户访问所有共享数据集的系统应用。通过使用 FAF 的对上接口,完成各种文件操作。
- **文件访问服务端应用** - 系统内支持将数据集进行共享的服务。目前有[UserFileManager](https://gitee.com/openharmony/multimedia_medialibrary_standard)、ExternalFileManager等。前者管理了本地磁盘、分布式设备的数据集,后者管理了SD卡、U盘等多种外置存储设备的数据集。开发者也可以基于 FAF 的服务端配置,共享自己的数据集。
FAF提供的主要功能:
- 可以让用户浏览系统内所有文件服务端应用提供的数据集,而不仅仅是单一应用的数据集。
- 客户端应用不需要获取FAF的使用权限,直接通过选择器应用操作文件。
- 支持访问多个临时挂载的设备,比如外置存储卡、分布式设备等。
## 数据模型
FAF 中数据模型主要通过URI、FileInfo、RootInfo 进行传递。详情参考[fileExtension](../reference/apis/js-apis-fileExtensionInfo.md)。文件访问服务端应用可以通过 FileAccessExtensionAbility API,将自身的数据安全的共享出去。
**图2 公共文件访问框架数据流**
![](figures/公共文件访问框架数据流.png)
注意事项:
- 在FAF中,文件访问客户端和文件访问服务端并不直接交互。只需要具备拉起文件选择器应用的权限即可。
- 文件选择器应用会为用户提供标准的文档访问界面,即使底层的文件访问服务端相互之间差异很大,一致性也不受影响。
\ No newline at end of file
# 文件管理概述
在操作系统中,存在各种各样的数据,按数据结构可分为:
- 结构化数据:能够用统一的数据模型加以描述的数据。常见的是各类数据库数据。在应用开发中,对结构化数据的开发活动隶属于[数据管理模块](database/data-mgmt-overview.md)
- 非结构化数据:指数据结构不规则或不完整,没有预定义的数据结构/模型,不方便用数据库二维逻辑表来表现的数据。常见的是各类文件,如文档、图片、音频、视频等。在应用开发中,对非结构化数据的开发活动隶属于文件管理模块,将在下文展开介绍。
在文件管理模块中,按文件所有者的不同,有如下文件分类模型,其示意图如下面文件分类模型示意图:
- [应用文件](app-file-overview.md):文件所有者为应用,包括应用安装文件、应用资源文件、应用缓存文件等。
- [用户文件](user-file-overview.md):文件所有者为登录到该终端设备的用户,包括用户私有的图片、视频、音频、文档等。
- 系统文件:与应用和用户无关的其他文件,包括公共库、设备文件、系统资源文件等。这类文件不需要开发者进行文件管理,本文不展开介绍。
按文件系统管理的文件存储位置(数据源位置)的不同,有如下文件系统分类模型:
- 本地文件系统:提供本地设备或外置存储设备(如U盘、移动硬盘)的文件访问能力。本地文件系统是最基本的文件系统,本文不展开介绍。
- [分布式文件系统](distributed-fs-overview.md):提供跨设备的文件访问能力。所谓跨设备,指文件不一定存储在本地设备或外置存储设备,而是通过计算机网络与其他分布式设备相连。
**图1** 文件分类模型示意图
![File classification model](figures/file-classification-model.png)
此差异已折叠。
# 向应用沙箱推送文件
开发者在应用开发调试时,可能需要向应用沙箱下推送一些文件以期望在应用内访问或测试,此时有两种方式:
1. 可以通过DevEco Studio向应用安装路径中放入目标文件,详见[应用安装资源访问](../quick-start/resource-categories-and-access.md#资源访问)
2. 在具备设备环境时,可以使用另一种更为灵活的方式,通过hdc工具来向设备中应用沙箱路径推送文件。即本文介绍的内容。
但是hdc shell看到的调试进程下的文件路径与应用视角的应用沙箱路径不同,开发者需要先了解如下路径映射关系。
## 应用沙箱路径和调试进程视角下的真实物理路径
在应用沙箱路径下读写文件,经过映射转换,实际读写的是在hdc进程视角下看到真实物理路径中的应用文件,其对应关系如下表所示。
**表1** 应用沙箱路径与真实物理路径对应关系
| 应用沙箱路径 | 调试进程(hdc)视角下的实际路径 | 说明 |
| -------- | -------- | -------- |
| /data/storage/el1/bundle | /data/app/el1/bundle/public/&lt;PACKAGENAME&gt; | 应用安装包目录 |
| /data/storage/el1/base | /data/app/el1/&lt;USERID&gt;/base/&lt;PACKAGENAME&gt; | 应用el1级别加密数据目录 |
| /data/storage/el2/base | /data/app/el2/&lt;USERID&gt;/base/&lt;PACKAGENAME&gt; | 应用el2级别加密数据目录 |
| /data/storage/el1/database | /data/app/el1/&lt;USERID&gt;/database/&lt;PACKAGENAME&gt; | 应用el1级别加密数据库目录 |
| /data/storage/el2/database | /data/app/el2/&lt;USERID&gt;/database/&lt;PACKAGENAME&gt; | 应用el2级别加密数据库目录 |
| /data/storage/el2/distributedfiles | /mnt/hmdfs/&lt;USERID&gt;/account/merge_view/data/&lt;PACKAGENAME&gt; | 应用el2加密级别有帐号分布式数据融合目录 |
## 开发示例
以应用包com.ohos.example为例,如果是在example的应用沙箱路径“/data/storage/el1/bundle”下读写文件,从上表可知,对应的真实物理路径为“/data/app/el1/bundle/public/&lt;PACKAGENAME&gt;”,即“/data/app/el1/bundle/public/com.ohos.example”。
推送命令示例如下:
```
hdc file send ${待推送文件的本地路径} /data/app/el1/bundle/public/com.ohos.example/
```
## 切换应用沙箱视角
在调试过程中,如果权限不对或文件不存在,开发者需要从调试进程视角切换为应用视角,以便直观分析权限及文件目录问题。视角切换命令如下:
```
hdc shell // 进入shell
ps -ef|grep [hapName] // 通过ps命令找到对应应用的pid
nsenter -t [hapPid] -m /bin/sh // 通过上一步找到的应用pid进入对应应用的沙箱环境中
```
执行完成后,即切换到了应用视角,该视角下的目录路径为应用沙箱路径,可以去排查沙箱路径相关问题。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册