1、概述
1.1、功能简介
数据管理为开发者提供数据存储、数据管理能力。
它分为两个部分:
- **数据存储:**提供通用数据持久化能力,根据数据特点,分为用户首选项、键值型数据库和关系型数据库。
- **数据管理:**提供高效的数据管理能力,包括权限管理、数据备分恢复、数据共享等
注:应用创建的数据库,都保存到应用少盒,当应用卸载时,数据库也会自动删除。
1.2、运作机制
数据管理模块包括用户首选项、键值型数据管理、关系型数据管理、分布式数据对象和跨应用数据管理。
Interface接口层提供标准JS API接口
Frameworks&System service层负责实现部件数据存储功能
另外还有一些SQLite和其它子系统的依赖
- **用户首选项:**Preferences, 提供轻量级配置数据持久化能力,支持订阅数据变化的通知能力。不支持分布式同步,常常用来保存应用配置信息、用户偏好设置等
- **键值型数据管理:**KV-Store,提供了键值型数据库的读写、加密、手动备份能力。暂不支持分布式功能。
- **关系型数据管理:**RelationalStore,提供了关系型数据库的增删改查、加密、手动备份能力。暂不支持分布式功能。
- **分布式数据对象:**DataObject,独立提供地象型结构数据的分布式能力,暂不支持分布式功能。
- **跨应用数据管理:**DataShare,提供了向其他应用共享以及管理其数据的方法。仅系统应用可用。
2、应用数据持久化概述
应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。
HarmonyOS标准系统支持典型的存储数据形态有:用户首选项、键值型数据库、关系型数据库
3、用户首选项实现数据持久化
用户首选项为应用提供key-value键值型的数据处理能力,支持应用持久化轻量级数据,并对其进行修改和查询。
Preferences不适合存放过多数据,适用的场景一般为应用保存用户个性化的配置。
3.1、运作机制
用户程序通过JS接口调用用户首选项读写对应的数据文件。开发者可以把用户首选项持久化文件的内容加载到Preferences实例,每个文件唯一对应到一个Preferences实例,系统会通过静态容器把这个实例存储在内存中,直到主动从内存中移除这个实例或删除这个文件。
3.2、使用约束
1、key键为string类型,要求非空且长度不超过80个字节
2、value值为string类型时可以为空,不为空时长度不超过8192个字节
3、内存会随着存储数据量的增大而增大,所以存储的数据应该是轻量级的,建议存储的数据不要超过1万条
3.3、相关接口说明
接口大部分都是异步接口,异步接口都有callback和Promise两种返回形式。以下为callback为例说明
|----------------------------------------------------------------------------------------|----------------------------------------------|
| 接口名称 | 描述 |
| getPreferences(context:Context,name:string,callback:AsyncCallback<Preferences>):void | 获取Preferences 实例 |
| put(key:string,value:valueType,callback:AsyncCallback<void>):void | 把数据写入Preferences实例,可以通过flush把实例进行持 久化 |
| has(key:string,callback:AsyncCallback<boolean>):void | 检查实例中是否包含指定的key的存储键值对,给定的key不可以为空 |
| get(key:string,defValue:valueType,callback:AsyncCallback<valueType>):void | 获取指定键对应的值,如果值为null或者非默认值类型,返回默认数据defValue |
| delete(key:string,callback:AsyncCallback<void>):void | 从实例中删除指定key的存储键值对 |
| flush(callback:AsyncCallback<void>):void | 把当前实例中的数据异步存储到用户首选项持久化文件中 |
| on(type:'change',callback?:Callback<{key:string}>):void | 订阅数据变更,订阅的key的值发生变化,在执行flush方法后,触发callback回调 |
| off(type:'change',callback?:Callback<{key:string}>):void | 取消订阅数据变更 |
| deletePreferences(context:Context,name:string,callback:AsyncCallback<void>):void | 从内存中移除指定实例,如果这个实例有对应的持久化文件则同时会把持外化文件删除 |
3.4、开发步骤
1、导入用户首选项模块
TypeScript
import dataPreferences from '@ohos.data.preferences';
2、获取Preferences实例,读取指定文件,把数据加载到Preferences实例,用于数据操作
TypeScript
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import dataPreferences from '@ohos.data.preferences';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
}
onDestroy() {
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
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}`);
}
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
}
onForeground() {
// Ability has brought to foreground
}
onBackground() {
// Ability has back to background
}
}
在onWindowStageCreate方法中进行读取
3、写入数据
使用put()方法保存数据到缓存的Preferences实例中,在写入数据后如果有必要可以使用flush()方法把Preferences实例的数据存储到持久化文件。
注意:如果键此时已存在则会修改值,如果需要是在键不存在时新增键值对,则需要使用has()进行检查
4、读取数据
使用get()方法获取数据,如果值为null或者非默认值类型,则返回默认数据。
5、删除数据
使用delete()方法删除指定的键值对
6、据的持久化
应用存入数据到Preferences后,可以使用flush()方法实现数据持久化
7、订阅数据更新
使用on(),订阅key值发生变化,flush()执行时,会回调其中的回调方法
8、删除指定文件
使用deletePreferences()方法从内存中移除指定文件对应的Preferences实例,包括内存中的数据。若该Preference存在对应的持久化文件,则同时删除该持久化文件,包括指定文件及其备份文件、损坏文件。
TypeScript
// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
import dataPreferences from '@ohos.data.preferences';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
}
onDestroy() {
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
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.')
// 进行相关的数据操作
globalThis.dataPreferences = preferences;
})
} catch (err) {
console.error(`Failed to get preferences. Code:${err.code},Message:${err.message}`);
}
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
}
onForeground() {
// Ability has brought to foreground
}
onBackground() {
// Ability has back to background
}
}
TypeScript
// index.ets
import dataPreferences from '@ohos.data.preferences';
import Prompt from '@system.prompt';
import common from '@ohos.app.ability.common';
@Entry
@Component
struct Index {
@State message: string = '';
@State message1: string = '';
dpf:dataPreferences.Preferences = globalThis.dataPreferences;
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.margin({bottom:10})
Text(this.message1)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.margin({bottom:10})
Button('put')
.margin({bottom:15})
.onClick(() => {
try{
this.dpf.has('startup', (err,val) => {
if(err) {
console.error(`Failed to check data. Code:${err.code}, message:${err.message}`);
return;
}
if (val) {
console.info('startup is exists!!')
} else {
try {
this.dpf.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 put data. Code:${err.code}, message:${err.message}`);
}
})
Button('get')
.margin({bottom:15})
.onClick(() => {
try{
this.dpf.get('startup','default',(err,val) => {
if(err) {
console.error(`Failed to get data. Code:${err.code}, message:${err.message}`);
}
console.info(`succeeded in getting value of 'startup'. val:${val} `);
this.message = val.toString();
})
}catch (err) {
console.error(`Failed to get data. Code:${err.code}, message:${err.message}`);
}
})
Button('delete')
.margin({bottom:15})
.onClick(() => {
try{
this.dpf.delete('startup', (err) => {
if(err) {
console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
return;
}
console.info('succeeded in deleting the data')
})
} catch (err) {
console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
}
})
Button('flush')
.margin({bottom:15})
.onClick(() => {
try{
this.dpf.flush((err) => {
console.error(`Failed to flush data. Code:${err.code}, message:${err.message}`);
return;
})
Prompt.showToast({
message: '数据持久化成功!',
})
} catch (err) {
console.error(`Failed to flush data. Code:${err.code}, message:${err.message}`);
}
})
Button('订阅')
.margin({bottom:15})
.onClick(() => {
this.dpf.on('change',(key) => {
console.info(key + '数据发生变化')
})
})
Button('更新')
.margin({bottom:15})
.onClick(() => {
try{
this.dpf.put('startup','manual',(err) => {
if(err) {
console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
return;
}
})
} catch (err){
console.error(`Failed to put data. Code:${err.code}, message:${err.message}`);
}
})
Button('deletePreferences')
.onClick(() => {
try{
let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
dataPreferences.getPreferences(context,'mystore', (err, val) => {
if(err) {
console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`successed to delete preferences.`);
})
} catch (err) {
console.error(`Failed to delete preferences. Code:${err.code}, message:${err.message}`);
}
})
}
.width('100%')
}
.height('100%')
}
}
4、键值型数据库实现数据持久化
4.1、使用场景介绍
键值型数据库存储键值对形式的数据,一般用来存储的数据没有复杂的关系模型。
4.2、使用约束
1、设备协同数据库,针对每条记录,key的长度小于等于896Byte,value小于4MB
2、单版本数据库,针对每条记录,key的长度小于等于1KB,value小于4MB
3、每个应用程序最多支持同时打开16个键值型分布式数据库
4、键值型数据库事件回调方法不允许进行阻塞操作
4.3、相关接口说明
接口大部分是异步操作,异步接口都有callback和Promise两种形式
|-----------------------------------------------------------------------------------------------|----------------------------------------|
| 接口名称 | 描述 |
| createKVManager(config: KVManagerConfig): KVManager | 创建一个KVManager对象实例,用于管数据库对象 |
| getKVStore<T>(storeId:string,options:Options,callback:AsyncCallback<T>):void | 指定Options和storeId,创建并得到指定类型的KVStore数据库 |
| put(key:string,value:Uint8Array|string|number|boolean,callback:AsyncCallback<void>):void | 添加指定类型的键值对到数据库 |
| get(key:string,callback:AsyncCallback<Uint8Array|string|number|boolean>):void | 获取指定键对应的值 |
| delete(key:string,callback:AsyncCallback<void>):void | 从数据库中删除指定键值数据 |
4.4、开发步骤
1、获取一个KVManager实例
在EntryAbility.ts中onCreate()或onWindowStageCreate()中创建。
TypeScript
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
// KVManagerConfig
let context = this.context;
const KvManagerConfig = {
context: context,
bundleName: 'com.xiaoxie'
};
try{
let kvManager = distributedKVStore.createKVManager(KvManagerConfig);
console.info('Succeeded in create KVManager.')
globalThis.kvManager = kvManager;
} catch (err){
console.error(`Failed to create KVManager, Code:${err.code},Message:${err.Message}`);
}
windowStage.loadContent('pages/KVStore', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
});
}
2、创建并获取键值数据库
3、调用put()方法向键值数据库中插入数据,当我们据时key值存在则会修改其值,否则新增一条数据
4、调用get()方法获取指定键值
5、调用delete()方法删除指定键值的数据
TypeScript
// KVStore.ets
import distributedKVStore from '@ohos.data.distributedKVStore';
import Prompt from '@system.prompt';
const options = {
createIfMissing: true, // 当数据库文件不存在的时候是否创建,默认创建
encrypt: false, // 设置数据库文件是否加密,默认不加密
backup: false, // 设置数据库文件是否备份,默认备份
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION, // 设置要创建数据库的类型,默认为多设备协同
securityLevel: distributedKVStore.SecurityLevel.S2 // 设置数据库的安全级别
};
@Entry
@Component
struct KVStore {
@State message: string = '';
kvManager:distributedKVStore.KVManager = globalThis.kvManager
kvStore:distributedKVStore.SingleKVStore = undefined;
build() {
Row() {
Column() {
Text(this.message)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({bottom:10})
Button('1、创建并获取键值数据库')
.width('50%')
.margin({bottom:10})
.onClick(() => {
try{
this.kvManager.getKVStore('storeId',options,(err,kvStore:distributedKVStore.SingleKVStore) => {
if(err) {
console.error(`Failed to get KVStore, Code:${err.code},Message:${err.message}`);
return;
}
Prompt.showToast({
message: '获取键值数据库成功!',
duration: 1500
})
this.kvStore = kvStore;
});
} catch (e) {
console.error(`Failed to get KVStore, Code:${e.code},Message:${e.message}`);
}
})
Button('2、向键值数据库插入数据')
.width('50%')
.margin({bottom: 10})
.onClick(() => {
try{
if(this.kvStore) {
this.kvStore.put('test_key','test_value',(err) => {
if(err) {
console.error(`Failed to put data, Code:${err.code},Message:${err.message}`);
return;
}
Prompt.showToast({
message: '向键值数据加中插入数据成功!',
duration: 1500
})
})
} else {
Prompt.showToast({
message: '错误:请先获取数据库!',
duration: 1500
})
}
} catch (e) {
console.error(`Failed to put data, Code:${e.code},Message:${e.message}`);
}
})
Button('3、获取指定键的值')
.width('50%')
.margin({bottom:10})
.onClick(() => {
try{
if(this.kvStore) {
this.kvStore.get('test_key',(err,data) => {
if(err) {
this.message = '';
console.error(`Failed to get data, Code:${err.code},Message:${err.message}`);
return;
}
this.message = '';
this.message = data.toString();
})
} else {
Prompt.showToast({
message: '错误:请先获取数据库!',
duration: 1500
})
}
} catch (e) {
console.error(`Failed to get data, Code:${e.code},Message:${e.message}`);
}
})
Button('4、删除指定键值数据')
.width('50%')
.onClick(() => {
try{
if(this.kvStore) {
this.kvStore.delete('test_key',(err) => {
if(err) {
console.error(`Failed to delete data, Code:${err.code},Message:${err.message}`);
return;
}
Prompt.showToast({
message: '删除指定键值数据成功!',
duration: 1500
})
})
} else {
Prompt.showToast({
message: '错误:请先获取数据库!',
duration: 1500
})
}
} catch (e){
console.error(`Failed to delete data, Code:${e.code},Message:${e.message}`);
}
})
}
.width('100%')
}
.height('100%')
}
}