跳到主要内容

应用数据持久化案例

使用不同 模块 完成首选项、键值型、关系型持久化。配套源码点击此处查看GitCode仓库,记得给仓库一个star。

1 入口模块 module 拉起指定其他模块

1.1 新建多个模块

分别命名为:preferences、kvStore、relationalStore。

新建module.png

1.2 在入口模块中拉起其他模块

设计entry模块中src/main/ets/pages/Index.ets的UI效果。三个按钮,点击事件中使用UIAbilityContext对象的startAbility方法传入Want对象拉起指定Ability

import { Want } from '@kit.AbilityKit'
import common from '@ohos.app.ability.common'

@Entry
@Component
struct Index {
context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext

//通用的跳转指定UIAbility方法
startTargetAbility(moduleName: string, abilityName: string, info?: string) {
// console.log(`dxin => bundleName:${this.context.applicationInfo.name}`)
this.context.startAbility({
deviceId: '', // 本设备
bundleName: this.context.applicationInfo.name, //也可动态获取
moduleName: moduleName, // 目标 模块名称
abilityName: abilityName, // 该模块下可能有多个 ability
parameters: {
info: info// 参数可以省略
}
})
}

build() {
Column({ space: 30 }) {
Text('三种数据持久化方案').fontSize(30)
Button('用户首选项').fontSize(22).buttonStyle(ButtonStyleMode.TEXTUAL)
.onClick(() => {
this.startTargetAbility('preferences', 'PreferencesAbility', "dxin-preferences")
})
Button('键值型数据库').fontSize(22).buttonStyle(ButtonStyleMode.TEXTUAL)
.onClick(() => {
this.startTargetAbility('kvStore', 'KvStoreAbility')
})
Button('关系型数据库').fontSize(22).buttonStyle(ButtonStyleMode.TEXTUAL)
.onClick(() => {
this.startTargetAbility('relationalStore', 'RelationalStoreAbility')
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
.backgroundColor($r('app.color.theme_color'))

}
}

1.3 运行项目测试拉起效果:

如果你正在尝试跳转到其他模块(如从 entry 跳转到 my_module),需要在 DevEco Studio 中进行额外配置,否则目标模块不会被安装。

  1. 点击 Run > Edit Configurations。

  2. 找到你的运行配置。

  3. 勾选 Deploy Multi Hap ~Packages。

DeployMultiHapPackages

1.4 美化各模块icon和label

在每个模块中添加icon图标,修改label名程,修改module.json5文件

iconLabel.png


2 preferences用户数首选项

2.1 获取Entry中拉起时传递的参数

PreferencesAbility中获取参数,如果获取不到,给出默认值'首选项'。为了在页面中展示该数据,使用**应用全局UI状态存储AppStorageV2。因为AppStorageV2只支持class**类型,否则会抛出运行时报错,从API version 23开始,将返回错误码140103。所以需要设计一个ParamType类。

PreferencesAbility.ets

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 获取主模块传递过来的参数 存储到AppStorageV2中方便页面使用
let abilityWant = want
let info = abilityWant?.parameters?.info.toString() || '首选项'
AppStorageV2.connect(ParamType, 'info', () => new ParamType(info))
}

src/main/ets/model/ParamType.ets

export default class ParamType{
message:string

constructor(message: string) {
this.message = message
}

}

2.2 在Index中展示获取的参数。

切记1.3步骤中的配置。否则你会发现拉起失败。

import ParamType from '../model/ParamType'
import { AppStorageV2 } from '@kit.ArkUI'

@Entry
@Component
struct Index {
info: ParamType = AppStorageV2.connect(ParamType, 'info', () => new ParamType('info is undefined'))!

build() {
Column({ space: 30 }) {
Text(this.info.message).fontColor("#fff50909").fontSize(40)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}

2.3 主题名称切换效果

准备数组THEME_NAMES存储三种主题 ['default','grey','simple']。使用状态变量themeModel取出数组中的默认数据。在布局中,点击bindMenuaction事件进行主题名称的切换。

changeThemeName

如果仅仅是为了完成主题名称的切换,直接使用状态变量字符串即可,但是为了配合后续的主题内容(图片+名程),所以需要设计为数组,方便以下标对应主题内容。

import ParamType from '../model/ParamType'
import { AppStorageV2 } from '@kit.ArkUI'

@Entry
@ComponentV2
struct Index {
info: ParamType = AppStorageV2.connect(ParamType, 'info', () => new ParamType('info is undefined'))!

// 准备一个数组存储不同主题的名程。
THEME_NAMES:string[] = ['default','grey','simple']

// 默认一个主题 从数组中取出
@Local currentThemeName: string = this.THEME_NAMES[0]

// 修改所选主题
changeTheme(index: number) {
// 主题名称
this.currentThemeName = this.THEME_NAMES[0]
}

build() {
Column({ space: 30 }) {
Text(this.info.message).fontColor("#fff50909").fontSize(40)
Row() {
Text("主题:" + this.currentThemeName)
.fontSize(20)
.layoutWeight(5)
.padding({ left: 10 })
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
Image($r('app.media.change'))
.key('changeBtn')
.id('changeBtn')
.height(30)
.layoutWeight(1)
.objectFit(ImageFit.ScaleDown)
.bindMenu([
{
value: 'default', action: () => {
this.changeTheme(0)
}
},
{
value: 'grey',
action: () => {
this.changeTheme(1)
}
},
{
value: 'simple',
action: () => {
this.changeTheme(2)
}
}
])
}
.width('100%')
.height(50)
.backgroundColor('#0D9FFB')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}

2.4 主题内容切换

changThemeContent

准备对应三种主题数据数组。该数组中存放的是主题内容的图标和名称,数据提前存放到src/main/resources/rawfile。所以先设计主题模型ThemeModel

src/main/ets/model/ThemeModel.ets

export interface ThemeModel {
image: string | Resource
name: string;
}

思路跟切换主题名称相同,设计一个状态变量作为默认的主题数组。展示在点击修改主题名称的时候,也修改当前需要展示的主题内容。

此处变量currentSliderVal是后续步骤中设计滑块时所用

  // 三种主题数据
themeDataArr: ThemeModel[][] = [
[
{ image: $rawfile("image/model1/dialer.png"), name: "电话" },
{ image: $rawfile("image/model1/shopping.png"), name: "商城" },
{ image: $rawfile("image/model1/notes.png"), name: "备忘录" },
{ image: $rawfile("image/model1/settings.png"), name: "设置" },
{ image: $rawfile("image/model1/camera.png"), name: "相机" },
{ image: $rawfile("image/model1/gallery.png"), name: "相册" },
{ image: $rawfile("image/model1/music.png"), name: "音乐" },
{ image: $rawfile("image/model1/video.png"), name: "视频" },
],
[
{ image: $rawfile("image/model2/simplicityCall.png"), name: "电话" },
{ image: $rawfile("image/model2/simplicityShop.png"), name: "商城" },
{ image: $rawfile("image/model2/simplicityNotes.png"), name: "备忘录" },
{ image: $rawfile("image/model2/simplicitySetting.png"), name: "设置" },
{ image: $rawfile("image/model2/simplicityCamera.png"), name: "相机" },
{ image: $rawfile("image/model2/simplicityPhotos.png"), name: "相册" },
{ image: $rawfile("image/model2/simplicityMusic.png"), name: "音乐" },
{ image: $rawfile("image/model2/simplicityVideo.png"), name: "视频" },
],
[
{ image: $rawfile("image/model3/pwcall.png"), name: "电话" },
{ image: $rawfile("image/model3/pwshop.png"), name: "商城" },
{ image: $rawfile("image/model3/pwnotes.png"), name: "备忘录" },
{ image: $rawfile("image/model3/pwsetting.png"), name: "设置" },
{ image: $rawfile("image/model3/pwcamera.png"), name: "相机" },
{ image: $rawfile("image/model3/pwphotos.png"), name: "相册" },
{ image: $rawfile("image/model3/pwmusic.png"), name: "音乐" },
{ image: $rawfile("image/model3/pwvideo.png"), name: "视频" },
]
]
// 当前主题数据
@Local currentThemeContent: ThemeModel[] = this.themeDataArr[0]
// 当前滑块的默认值
@Local currentSliderVal: number = 1

使用Grid组件遍历数组得到多份GridItem子组件。Grid组件的columnsTemplate('1fr 1fr 1fr 1fr')属性用来控制行列。

      // 主题内容
Grid() {
ForEach(this.currentThemeContent, (item: ThemeModel) => {
GridItem() {
Column() {
Image(item.image)
.width(70 * this.currentSliderVal)
.aspectRatio(1)
.padding(10)
.objectFit(ImageFit.Fill)
Text(item.name).fontSize(16 * this.currentSliderVal)
}
.width(90 * this.currentSliderVal)
.aspectRatio(1)
}
})
}
.rowsGap(10)
.width('100%')
.columnsGap(10)
.height("30%")
// .layoutWeight(1)
.padding({ top: 20 })
.backgroundColor('#e5e5e5')
.columnsTemplate('1fr 1fr 1fr 1fr')

2.5 滑块布局

SliderUI

      //   滑块布局
Column() {
Row() {
Text(0.8 + "").fontSize(20)
Text(this.currentSliderVal.toFixed(1)).fontSize(30).fontColor("#fff50065")
Text(1.2 + "").fontSize(20)
}
.width("80%")
.height(40)
.justifyContent(FlexAlign.SpaceBetween)

Slider({
min: 0.8,
max: 1.2,
step: 0.1,
value: this.currentSliderVal
})
.width("80%")
.height(40)
.onChange((val) => {
this.currentSliderVal = val
})
}

2.6 设计首选项工具类

因为使用首选项进行数据的增删改查操作的地方不止一处,本案例就包含了对主题和字号的存储。所以封装为工具类提高复用效率。

以下是用户首选项持久化功能的相关接口,更多接口及使用方式请见用户首选项

接口名称描述
getPreferencesSync(context: Context, options: Options): Preferences获取Preferences实例。该接口存在异步接口。
putSync(key: string, value: ValueType): void将数据写入Preferences实例,可通过flushPreferences实例持久化。该接口存在异步接口。
hasSync(key: string): boolean检查Preferences实例是否包含名为给定Key存储键值对,true表示包含,false表示不包含。给定的Key值不能为空。该接口存在异步接口。
getSync(key: string, defValue: ValueType): ValueType获取键对应的值,如果值为null或非默认值类型,将返回默认数据defValue。该接口存在异步接口。
deleteSync(key: string): voidPreferences实例中删除名为给定Key的存储键值对。该接口存在异步接口。
flush(callback: AsyncCallback<void>): void将当前Preferences实例的数据异步存储到用户首选项持久化文件中。
on(type: 'change', callback: Callback<string>): void订阅数据变更,订阅的数据发生变更后,在执行flush方法后,触发callback回调。
off(type: 'change', callback?: Callback<string>): void取消订阅数据变更。
deletePreferences(context: Context, options: Options, callback: AsyncCallback<void>): void从内存中移除指定的Preferences实例。若Preferences实例有对应的持久化文件,则同时删除其持久化文件。

创建工具类src/main/ets/common/PreferencesUtil.ets。其中包含初始化preferences对象,存/取preferences数据的方法。

Preferences

从上图可知,一个应用可以有多个首选项实例对象。我们前文设计的存储字号大小和主题,可以存储到同一个实例对象(持久化文件)中。也可以分门别类存储到不同的实例对象(持久化文件)中。那么会涉及到:用什么容器管理多个对象。重复放入容器时,会自动去重-->考虑使用Map集合。

// 首选项工具栏,提供首选项增上改查功能
import { preferences } from '@kit.ArkData'
import { Context } from '@ohos.arkui.UIContext'

class PreferencesUtil {
// 准备一个map, 用来存多个首选项对象
preMap: Map<string, preferences.Preferences> = new Map()

// 加载 首选项对象
initPreference(context: Context, name: string) {
try {
// 该接口 第二个参数 在next修改为配置项 而非字符串
let pre: preferences.Preferences = preferences.getPreferencesSync(context, { name })
// 存到首选项 map中
this.preMap.set(name, pre)
console.log('PreferencesUtil', `加载Preferences【${name}】成功`)
} catch (e) {
console.log('PreferencesUtil', `加载Preferences【${name}】失败`, JSON.stringify(e))
}
}

// 保存数据
putPreferenceVal(preferenceName: string, key: string, value: preferences.ValueType) {
if (!this.preMap.has(preferenceName)) {
console.log('PreferencesUtil', `加载Preferences【${preferenceName}】尚未初始化`)
return
}
try {
// 从数组中获取该首选项对象
let pref = this.preMap.get(preferenceName)
// 写入数据
pref?.putSync(key, value)
// 刷盘
pref?.flush()
console.log('PreferencesUtil', `保存【${preferenceName}】-【${key}】-【${value}】成功`)
} catch (e) {
console.log('PreferencesUtil', `保存【${preferenceName}】-【${key}】-【${value}】失败`, JSON.stringify(e))
}
}

// 获取数据
getPreferenceVal(preferenceName: string, key: string, defaultVal: preferences.ValueType){
if (!this.preMap.has(preferenceName)) {
console.log('dxin PreferencesUtil', `加载Preferences【${preferenceName}】尚未初始化`)
return
}
try {
let pref = this.preMap.get(preferenceName)
let val = pref?.getSync(key, defaultVal)
console.log('PreferencesUtil', `读取【${preferenceName}】-【${key}】-【${val}】成功`)
return val
}catch (e){
console.log('PreferencesUtil', `读取【${preferenceName}】-【${key}】失败`,JSON.stringify(e))
// try和catch都要有返回
return defaultVal
}
}
}

export default new PreferencesUtil()

以上工具类封装完成后,并不能直接在UI界面调用对应的方法存取数据。因为initPreference方法中需要使用preferences.getPreferencesSync(context, { name })来获取preferences对象。而context在哪里呢。所以在入口类中需要先对该工具类进行初始化。

src/main/ets/preferencesability/PreferencesAbility.ets

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { AppStorageV2, window } from '@kit.ArkUI';
import PreferencesUtil from '../common/PreferencesUtil';
import ParamType from '../model/ParamType';


export default class PreferencesAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
console.log(`dxin => PreferencesAbility onCreate`)
// 获取主模块传递过来的参数 存储到AppStorageV2中方便页面使用
let abilityWant = want
let info = abilityWant?.parameters?.info.toString() || '首选项'
AppStorageV2.connect(ParamType, 'info', () => new ParamType(info))
}


onWindowStageCreate(windowStage: window.WindowStage): void {
// 以下初始化首选项 也可以放在 onCreate 中
// 存储主题的 首选项文件放到 map中
PreferencesUtil.initPreference(this.context, 'theme_preferences')
// 存储字号大小的 首选项文件 放到 map 中
PreferencesUtil.initPreference(this.context, 'fontSize_preferences')
windowStage.loadContent('pages/Index');
}
}

2.7 在UI中使用工具类完成功能

持久化主题

changeTheme方法中修改了主题名称和主题内容,UI动态驱动后,持久化存储当前主题名称

  // 修改所选主题
changeTheme(index: number) {
// 主题名称
this.currentThemeName = this.THEME_NAMES[0]
// 主题数组
this.currentThemeContent = this.themeDataArr[index]
// 切换完主题后:持久化存储 -> 在theme_preferences文件中存储key为themeModel的数据,数据内容为当前被选择的主题模式
PreferencesUtil.putPreferenceVal("theme_preferences", "themeModel", this.THEME_NAMES[index]
)
}

当进入应用时,渲染UI之前,查询持久化文件中的主题名称

  aboutToAppear(): void {
// 页面加载先查看上一次的主题名称
this.currentThemeName = PreferencesUtil.getPreferenceVal('theme_preferences','themeModel', this.THEME_NAMES[0] ) as string
// 根据 主题 获取索引值 方便从主题内容数组中重置对应数据
let index = this.THEME_NAMES.indexOf(this.currentThemeName)
// 初始化当前主题 内容数组
this.currentThemeContent = this.themeDataArr[index]
}

持久化字号

当滑块滑动时,持久化存储当前的值。

Slider({
min: 0.8,
max: 1.2,
step: 0.1,
value: this.currentSliderVal
})
.width("80%")
.height(40)
.onChange((val) => {
this.currentSliderVal = val
// 把当前改变的数据,存储起来 持久化
PreferencesUtil.putPreferenceVal('fontSize_preferences','fontSizeVal' , this.currentSliderVal)
console.log("dxin => 存储好了字号大小:", this.currentSliderVal)
})

当进入应用时,渲染UI之前,查询持久化文件中的字号大小

  aboutToAppear(): void {
// 页面加载先查看上一次的主题名称
// ...
// 页面加载 去查询一下 上次存储的 字号大小数据 数据
this.currentSliderVal = PreferencesUtil.getPreferenceVal('fontSize_preferences', 'fontSizeVal', this.currentSliderVal) as number
}

2.8 常量抽取

至此,使用首选项持久化应用数据的功能已经完成。编码过程中发现一些可以优化的地方。

  1. THEME_NAMES是用来存储三种主题名称的数组,themeDataArr是用来存储三种主题内容数据的数组。
  2. PreferencesAbility.ets中初始化两个对象分别命名为:theme_preferencesfontSize_preferences,这在存取数据时,均需要在第一个参数手动传入,可能会因为手误拼错单词。
  3. 在存取数据时,第二个参数为指定的key名。也可能因手误拼错单词。
  • 按照江湖规矩,封装到常量文件中,哪里使用就在哪里导入,如有修改,只在常量文件中修改。提高代码可维护性。

src/main/ets/common/DxinConstants.ets

import { ThemeModel } from "../model/ThemeModel"

export default class DxinConstants{

// 三种主题的名称
static readonly THEME_NAMES:string[] = ['default','grey','simple']

// 三种主题数据
static readonly themeDataArr: ThemeModel[][] = [
[
{ image: $rawfile("image/model1/dialer.png"), name: "电话" },
{ image: $rawfile("image/model1/shopping.png"), name: "商城" },
{ image: $rawfile("image/model1/notes.png"), name: "备忘录" },
{ image: $rawfile("image/model1/settings.png"), name: "设置" },
{ image: $rawfile("image/model1/camera.png"), name: "相机" },
{ image: $rawfile("image/model1/gallery.png"), name: "相册" },
{ image: $rawfile("image/model1/music.png"), name: "音乐" },
{ image: $rawfile("image/model1/video.png"), name: "视频" },
],
[
{ image: $rawfile("image/model2/simplicityCall.png"), name: "电话" },
{ image: $rawfile("image/model2/simplicityShop.png"), name: "商城" },
{ image: $rawfile("image/model2/simplicityNotes.png"), name: "备忘录" },
{ image: $rawfile("image/model2/simplicitySetting.png"), name: "设置" },
{ image: $rawfile("image/model2/simplicityCamera.png"), name: "相机" },
{ image: $rawfile("image/model2/simplicityPhotos.png"), name: "相册" },
{ image: $rawfile("image/model2/simplicityMusic.png"), name: "音乐" },
{ image: $rawfile("image/model2/simplicityVideo.png"), name: "视频" },
],
[
{ image: $rawfile("image/model3/pwcall.png"), name: "电话" },
{ image: $rawfile("image/model3/pwshop.png"), name: "商城" },
{ image: $rawfile("image/model3/pwnotes.png"), name: "备忘录" },
{ image: $rawfile("image/model3/pwsetting.png"), name: "设置" },
{ image: $rawfile("image/model3/pwcamera.png"), name: "相机" },
{ image: $rawfile("image/model3/pwphotos.png"), name: "相册" },
{ image: $rawfile("image/model3/pwmusic.png"), name: "音乐" },
{ image: $rawfile("image/model3/pwvideo.png"), name: "视频" },
]
]

// 存储主题的首选项的 文件(数据库)名称
static readonly PREFERENCES_THEME:string = "theme_preferences"

// 存储主题的时候 key
static readonly THEME_KEY:string = "themeModel"

// 存储字号大小的 数据库 名称
static readonly PREFERENCES_FontSize:string = "fontSize_preferences"
// 存储字号大小 倍数 的时候 key
static readonly FONTSIZE_KEY:string = "fontSizeVal"
// 字号大小 倍数 默认值
static readonly fontSizeScaleDefault : number= 1
}

3 KVStore键值型

键值型数据库存储键值对形式的数据,当需要存储的数据没有复杂的关系模型,比如存储商品名称及对应价格等,由于数据复杂度低,更容易兼容不同数据库版本和设备类型,因此推荐使用键值型数据库持久化此类数据。此处以用户名密码为案例进行持久化存储

image-20260526150145063

3.1 设计UI布局

@ObservedV2装饰的对象序列化后会为@Trace装饰的属性添加__ob_前缀。所以进行回显时需要特殊处理。本案例采用V1版本简化复杂度。如果使用V2亦可,但是在反序列化时,需自行解析裁剪拼接字符串

import { promptAction } from '@kit.ArkUI';

// 用户模型
// @ObservedV2装饰的对象序列化后会为@Trace装饰的属性添加__ob_前缀。 为避免徒增反序列化的麻烦,采用V1版本
class Userinfo {
username: string
password: string

constructor(username: string, password: string) {
this.username = username
this.password = password
}
}

@Entry
@ComponentV2
struct Index {
@State userinfo: Userinfo = new Userinfo("dxin", "123")

build() {
Column({ space: 30 }) {
Text(`欢迎 ${this.userinfo.username} ,你的密码是 ${this.userinfo.password}`).fontSize(20)
TextInput({ placeholder: "用户名", text: $$this.userinfo.username })
.onFocus(() => {
this.userinfo.username = ""
})
TextInput({ placeholder: "密码", text: $$this.userinfo.password })
.onFocus(() => {
this.userinfo.password = ""
})

Button("KV数据库应用")
.onClick(() => {
// KV数据库存储 注意存储的数据类型不能是对象,只能是 Uint8Array | string | number | boolean
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}

3.2 设计工具类

以下是键值型数据库持久化功能的相关接口,更多接口及使用方式请见分布式键值数据库

接口名称描述
createKVManager(config: KVManagerConfig): KVManager创建一个KVManager对象实例,用于管理数据库对象。
getKVStore&lt;T&gt;(storeId: string, options: Options, callback: AsyncCallback&lt;T&gt;): void指定optionsstoreId,创建并得到指定类型的KVStore数据库。
put(key: string, value: Uint8Array | string | number | boolean, callback: AsyncCallback&lt;void&gt;): void添加指定类型的键值对到数据库。
get(key: string, callback: AsyncCallback&lt;boolean | string | number | Uint8Array&gt;): void获取指定键的值。
delete(key: string, callback: AsyncCallback&lt;void&gt;): void从数据库中删除指定键值的数据。
closeKVStore(appId: string, storeId: string, callback: AsyncCallback&lt;void&gt;): void通过storeId的值关闭指定的分布式键值数据库。
deleteKVStore(appId: string, storeId: string, callback: AsyncCallback&lt;void&gt;): void通过storeId的值删除指定的分布式键值数据库。

src/main/ets/common/DxinKvStoreUtil.ets

import { Context } from '@kit.AbilityKit';
import { distributedKVStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { UIContext } from '@ohos.arkui.UIContext';

class DxinKvStoreUtil {
/*
* KV数据库可以设计成 map 用不同的kv存储不同的数据 注意每个应用同时打开KV数据库 数量最多 16 个
* 此处 为demo案例,未设计为 map。如果设计为map的用法,参考如下网址的首选项工具类
* https://juejin.cn/post/7451824088924553231
* */
kvStore: distributedKVStore.SingleKVStore | undefined = undefined;

// 1. 初始化 kvstore
async kvStoreInit(kvStoreName: string, context: Context) {
let kvManger: distributedKVStore.KVManager | undefined = undefined

const kvMangerConfig: distributedKVStore.KVManagerConfig = {
context,
// 这种获取UIContext对象的方式已经废弃
// bundleName: getContext(context).applicationInfo.name
// 返回当前组件所在Ability的Context身上可以取出bundleName
bundleName:context.applicationInfo.name as string
}
kvManger = distributedKVStore.createKVManager(kvMangerConfig)
const options: distributedKVStore.Options = {
createIfMissing: true,
encrypt: false,
backup: false,
autoSync: false,
// kvStoreType不填时,默认创建多设备协同数据库
kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
// 多设备协同数据库:kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
securityLevel: distributedKVStore.SecurityLevel.S1
};

let store = await kvManger.getKVStore<distributedKVStore.SingleKVStore>(kvStoreName, options)
if (store !== undefined) {
this.kvStore = store
console.info(`Dxin => 获取名字叫${kvStoreName}的 KVStore 成功.`);
} else {
console.info(`Dxin => 获取名字叫${kvStoreName}的 KVStore 失败@@.`);
}
}

// 2.put 方法,注意put的数据有类型要求
async putData(keyName: string, valueData: Uint8Array | string | number | boolean) {
try {
if (this.kvStore != undefined) {
this.kvStore.put(keyName, valueData)
.then(() => {
console.info(`Dxin => 向${keyName} 去 put 时成功了`);
})
.catch((err: BusinessError) => {
console.error(`Dxin => 向 ${keyName} 去 put 时失败了. Code:${err.code},message:${err.message}`);
})
}
} catch (e) {
let error = e as BusinessError;
console.error(`Dxin => 向${keyName} 去 put 时发生了未知错误. Code:${error.code},message:${error.message}`);
}
}

//3. 使用 kvStore 获取具体的数据
async getData(keyName: string) {
if (this.kvStore === undefined) {
console.error(`Dxin => 从${keyName} 去 get 时发现,这个kv数据库都不存在`);
return
}
let data = await this.kvStore.get(keyName)
console.log(`Dxin => 从${keyName}成功获取数据. Data:${data}`)
return data
}

//4. 删除 指定键名对应的数据
deleteData(keyName: string) {
if (this.kvStore === undefined) {
console.error(`Dxin => 从${keyName} 去 删除数据 时发现,kv数据库都不存在`);
return
}
this.kvStore.delete(keyName)
.then(() => {
console.info('Dxin => 成功地从${keyName}中删除数据');
})
.catch((err: BusinessError) => {
console.error(`Dxin => 失败地从${keyName}中删除数据. Code:${err.code},message:${err.message}`);

})
}
}

export default new DxinKvStoreUtil()

3.3 初始化工具类对象

src/main/ets/kvstoreability/KvStoreAbility.ets

  async onWindowStageCreate(windowStage: window.WindowStage) {
// 此处调用方法一定记得 await 等待初始化kv数据库成功再去显示UI 如果异步显示UI 会导致KV 还未初始化完呢
// 经过测试,该方法放在 onWindowStageCreate 可行
// 经过测试,该方法放在 onCreate 不可行 执行时机不同,生命周期只是先后顺序执行,但是onCreate中如果有异步代码,并非等异步结束后再onWindowStageCreate
await DxinKvStoreUtil.kvStoreInit('DxinKvStore', this.context)
console.log("Dxin => 入口类初始化了KV数据库:", 'DxinKvStore')
windowStage.loadContent('pages/Index');
}

3.4 在UI中使用工具类持久化数据

按钮点击事件中持久化存储

Button("KV数据库应用")
.onClick(() => {
// KV数据库存储 注意存储的数据类型不能是对象,只能是 Uint8Array | string | number | boolean
let userinfoStr = JSON.stringify(this.userinfo)
try {
// 设计一个键名userinfo
DxinKvStoreUtil.putData("userinfo", userinfoStr)
promptAction.openToast({message:'KV存储成功。OK'})
} catch (e) {
promptAction.openToast({message:'KV存储失败!!!ERR'})
}
})

加载页面前获取存储过的数据

 async aboutToAppear(): Promise<void> {
// KV数据库获取
let userinfoStr: string = await DxinKvStoreUtil.getData('userinfo') as string
if (userinfoStr) {
let currentUserinfo = JSON.parse(userinfoStr) as Userinfo
this.userinfo = currentUserinfo
} else {
this.userinfo = new Userinfo("ddd", "666")
}
}

4 relationalStore关系型

使用关系型数据库模拟简单图书管理系统的增删改查效果

image-20260526162001114

不想整了。看客可根据源码分析学习。

加载中...