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、导入用户首选项模块
import dataPreferences from '@ohos.data.preferences';
2、获取Preferences实例,读取指定文件,把数据加载到Preferences实例,用于数据操作
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 abilitytry{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存在对应的持久化文件,则同时删除该持久化文件,包括指定文件及其备份文件、损坏文件。
// EntryAbility.tsimport 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 abilitytry{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}
}
// index.etsimport 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()中创建。
onWindowStageCreate(windowStage: window.WindowStage) {// Main window is created, set main page for this ability// KVManagerConfiglet 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()方法删除指定键值的数据
// 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.kvManagerkvStore: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%')}
}