HarmonyOS开发(九):数据管理

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%')
  }
}

5、关系型数据库实现数据持久化

相关推荐
ClkLog-开源埋点用户分析9 分钟前
ClkLog企业版(CDP)预售开启,更有鸿蒙SDK前来助力
华为·开源·开源软件·harmonyos
mg66812 分钟前
鸿蒙系统的优势 开发 环境搭建 开发小示例
华为·harmonyos
模拟IC攻城狮37 分钟前
华为海思招聘-芯片与器件设计工程师-模拟芯片方向- 机试题-真题套题题目——共8套(每套四十题)
嵌入式硬件·华为·硬件架构·芯片
lqj_本人40 分钟前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人42 分钟前
使用 Flutter 绘制一个棋盘
harmonyos
TangKenny1 小时前
计算网络信号
java·算法·华为
23zhgjx-NanKon2 小时前
华为eNSP:QinQ
网络·安全·华为
23zhgjx-NanKon2 小时前
华为eNSP:mux-vlan
网络·安全·华为
Leo.yuan3 小时前
39页PDF | 华为数据架构建设交流材料(限免下载)
数据结构·华为
lqj_本人4 小时前
Flutter&鸿蒙next 状态管理框架对比分析
flutter·华为·harmonyos