鸿蒙开发 - 数据持久化 Preferences (内存存储) (封装)

这篇文章介绍鸿蒙中的 Preferences,它是一种轻量级存储方式,数据存储在内存中,用于存储少量的数据。

可以执行 flush() 方法将内存中的数据写入到磁盘文件,保证下次重启后数据可以继续使用,下面会有介绍到

主要特性:

  • 数据存储形式:键值对,键的类型为字符串,值的存储数据类型包括数字型、字符型、布尔型以及这3种类型的数组类型 点击查看
  • 轻量级:Preferences主要用于存储少量的数据,不适合用来存储大量的数据集。所有数据会被加载到内存中,过多的数据可能导致内存占用过高
  • 快速访问:由于数据被缓存在内存中,因此读取速度非常快
  • 同步与异步操作:提供了同步和异步两种方式来处理数据的读写操作

初始化 Preferences 实例

TypeScript 复制代码
import { preferences } from '@kit.ArkData'
import { BusinessError } from '@kit.BasicServicesKit'

@Entry
@Component
struct Index {
  dataPreferences: preferences.Preferences | null = null
  
  aboutToAppear(): void {
    let options: preferences.Options = { name: 'myStore' }
    this.dataPreferences = preferences.getPreferencesSync(getContext(this), options)
  }
}
  • preferences.getPreferencesSync(context: Context, options: Options) 获取 Preferences 实例
  • Options: { name: string } 指定 Preferences 实例的名称

操作数据的方法(以下都是异步写法,下面会有同步写法)

写入数据 put

将数据写入缓存中

示例代码:

TypeScript 复制代码
build() {
  Column() {
    Button('登录')
      .onClick(async () => {
        this.dataPreferences?.put('name', '诡术妖姬', (err: BusinessError) => {
          if (err) {
            console.error(`写入数据失败:code=${err.code},message=${err.message}`)
            return
          }
          console.log('写入数据成功')
        })
      }
  }
}

当我们写入数据的时候,如果不需要关注回调通知,可以忽略掉,如下:

typescript 复制代码
this.dataPreferences?.put('name', '诡术妖姬')

获取数据 get

从缓存中获取键对应的值

get 方法中第一个参数是Key,第二个参数是默认值,如果获取的数据为null或者非默认值类型就会返回默认值

示例代码:

typescript 复制代码
this.dataPreferences?.get('name', '默认值', (err: BusinessError, val: preferences.ValueType) => {
  if (err) {
    console.error(`获取数据失败: code=${err.code}, message=${err.message}`)
    return
  }
  console.log(`获取数据成功: val=${val}`)
})

获取所有数据 getAll

从缓存中获取所有的键值数据

示例代码:

typescript 复制代码
this.dataPreferences?.getAll((err: BusinessError, val: preferences.ValueType) => {
  console.log('获取所有数据:', JSON.stringify(val)) // 获取所有数据: {"name1":"武器","name2":"杰斯","name3":"酒桶"}
})

检查是否存在 has

检查是否存在指定Key的键值数据,返回值是 boolean

示例代码:

typescript 复制代码
this.dataPreferences?.has('name', (err: BusinessError, flag: boolean) => {
  if (err) {
    console.error(`检查失败: code=${err.code}, message=${err.message}`)
    return
  }
  console.log('检查的key值是否存在:', flag) // true
})

this.dataPreferences?.has('name22222', (err: BusinessError, flag: boolean) => {
  if (err) {
    console.error(`检查失败:code=${err.code}, message=${err.message}`)
    return
  }
  console.log('检查的key值是否存在:', flag) // false
})

删除数据 delete

删除指定key的键值数据

示例代码:

typescript 复制代码
this.dataPreferences?.delete('name', (err: BusinessError) => {
  if (err) {
    console.error(`删除失败: code=${err.code}, message=${err.message}`)
    return
  }
  console.log('删除成功')
})

删除所有数据 clear

删除缓存中的所有数据

示例代码:

typescript 复制代码
this.dataPreferences?.clear((err: BusinessError) => {
  if (err) {
    console.error(`删除所有数据失败: code=${err.code}, message=${err.message}`)
  }
  console.log('删除所有数据成功')
})

将缓存数据同步到磁盘文件 flush

作用: flush 主要用于将内存的更改同步到磁盘文件中。当操作了数据(例如添加、更新、删除)后,这些更改首先会保存在内存中。调用
flush 会将这些更改写入到磁盘中,从而确保数据在应用程序重启或设备重启后仍然可用。

示例代码:

typescript 复制代码
this.dataPreferences?.flush((err: BusinessError) => {
  if (err) {
    console.log(`Failed to flush:code=${err.code},message:${err.message}}`)
    return
  }
  console.log('Succeeded in flush')
})

如果不执行 flush() 方法,可能会有以下影响:

  1. 数据丢失风险:如果应用程序在数据同步到持久化存储之前崩溃或被强制终止,那么内存中的更改可能会丢失。这意味着用户的数据可能无法被正确保存。
  2. 数据不一致性:在某些情况下,如果没有及时调用 flush(),可能会导致数据在不同时间点读取时出现不一致的情况。例如,一个进程可能已经更新了数据但尚未同步,而另一个进程读取的仍然是旧数据。
  3. 性能考虑:虽然 flush() 方法可以确保数据持久化,但频繁调用它可能会影响性能。因此,开发者通常会在合适的时间点(如用户明确保存操作或应用程序即将退出时)调用 flush()。 最佳实践

为了确保数据的完整性和一致性,开发者应该遵循以下最佳实践:

  • 在关键数据修改后,及时调用 flush() 方法。
  • 考虑在应用程序的生命周期事件中(如 onPause() 或 onDestroy())调用 flush(),以确保在应用程序退出前数据被正确保存。
  • 避免在性能敏感的操作中频繁调用 flush(),而是根据实际需求选择合适的时间点进行同步。

总之,虽然不调用 flush() 方法在某些情况下可能不会立即导致问题,但为了确保数据的可靠性和持久性,开发者应该养成良好的习惯,在适当的时候调用 flush() 方法。

另外一种异步写法

上面方法示例代码都是用的 callback异步回调 这种方式,除了这种,还有另外一种方式 Promise异步回调,如下:

typescript 复制代码
let promise = this.dataPreferences?.get('name', '默认值',)
promise?.then((val: preferences.ValueType) => {
  console.log(`获取数据成功: val=${val}`)
}).catch((err: BusinessError) => {
  console.error(`获取数据失败: code=${err.code}, message=${err.message}`)
})

如果不愿意使用Promise链式回调,也可以使用async/await

typescript 复制代码
let res = await this.dataPreferences?.get('name', '默认值',)
console.log('获取数据:', res)

同步写法

上面方法除了 flush 没有同步写法,其他方法都有同步写法,比如:

  • get
typescript 复制代码
let value = dataPreferences?.getSync('name', '默认值')
  • put
typescript 复制代码
let value = dataPreferences?.putSync('name', '小鲁班')

监听数据变更

监听所有key的数据变更

监听所有key,其中一个键值发生变化,就会触发回调。(只有在执行flush方法后,才会触发callback回调)

语法: on(type: 'change', callback: Callback<string>): void

示例代码:

TypeScript 复制代码
@Entry
@Component
struct Index {
  aboutToAppear(): void {
    let observer = (key: string) => {
      console.log('发生数据变更的key:', key) // 发生数据变更的key:name,诡术妖姬
    }
    this.dataPreferences.on('change', observer)
  }
  
  build() {
    Column() {
      Button('按钮')
        .onClick(() => {
          this.dataPreferences?.delete('name', (err: BusinessError) => {
            if (err) {
              console.error(`删除失败: code=${err.code}, message=${err.message}`)
              return
            }
            console.log('删除成功')
          })
          this.dataPreferences?.flush()
        })
    }
  }
}

监听指定key的数据变更

可以传入一个数组,指定一部分key进行监听

语法: on(type: 'dataChange', keys: Array<string>, callback: Callback<Record<string, ValueType>>): void

示例代码:

TypeScript 复制代码
let keys = ['name', 'age']
let observer = (data: Record<string, preferences.ValueType>) => {
  for (const keyValue of Object.entries(data)) {
    console.info(`observer : ${keyValue}`)
  }
  console.info("The observer called.") 
}
this.dataPreferences.on('dataChange', keys, observer);

监听进程间的数据变更

监听进程间数据变更,多个进程持有同一个首选项文件时,在任意一个进程(包括本进程)执行flush方法,持久化文件发生变更后,触发callback回调。

语法: on(type: 'multiProcessChange', callback: Callback<string>): void

示例代码:

TypeScript 复制代码
let observer = (key: string) => {
  console.log('发生数据变更的key:', key)
}
this.dataPreferences?.on('multiProcessChange', observer)

取消监听

语法: off(type: 'change', callback?: Callback<string>): void

如果需要取消指定的回调函数,就需要填写callback,不填写则全部取消。

示例代码: dataPreferences.off('change', observer);

multiProcessChangedataChange 同理

Preferences使用的约束限制

  • 首选项无法保证进程并发安全,会有文件损坏和数据丢失的风险,不支持在多进程场景下使用
  • Key键为string类型,要求非空且长度不超过1024个字节
  • 如果Value值为string类型,请使用UTF-8编码格式,可以为空,不为空时长度不超过16 * 1024 * 1024个字节
  • 内存会随着存储数据量的增大而增大,所以存储的数据量应该是轻量级的,建议存储的数据不超过一万条,否则会在内存方面产生较大的开销

封装

做一个简单的封装玩玩,没有经历过多的测试

  • 封装一个 PreferencesUtil 类
TypeScript 复制代码
import { preferences } from '@kit.ArkData'
import { BusinessError } from '@kit.BasicServicesKit'

class PreferenceUtil {
  private _preferences?: preferences.Preferences
  private context?: Context
  private fileName?: string

  constructor(t?: Context, fileName: string = 'myStore') {
    this.context = t || getContext(this)
    this.fileName = fileName
    this.init()
  }

  init() {
    if (this._preferences) {
      this._preferences = preferences.getPreferencesSync(this.context, { name: this.fileName })
    }
  }

  get(key: string): Promise<preferences.ValueType> {
    return new Promise(resolve => {
      this._preferences?.get(key, '', (err: BusinessError, val: preferences.ValueType) => {
        resolve(val)
      })
    })
  }

  getSync(key: string): Promise<preferences.ValueType | undefined> {
    return new Promise(resolve => {
      resolve(this._preferences?.getSync(key, ''))
    })
  }

  put(key: string, value: preferences.ValueType) {
    this._preferences?.put(key, value, (err: BusinessError) => {
      if (err) {
        console.error(`写入数据失败:code=${err.code},message=${err.message}`)
        return
      }
      console.log('写入数据成功')
      this.flush()
    })
  }

  putSync(key: string, value: preferences.ValueType) {
      this._preferences?.putSync(key, value)
      this.flush()
  }

  delete(key: string) {
    this._preferences?.delete(key, (err: BusinessError) => {
      if (err) {
        console.error(`删除失败: code=${err.code}, message=${err.message}`)
        return
      }
      console.log('删除成功')
      this.flush()
    })
  }

  deleteSync(key: string) {
    this._preferences?.deleteSync(key)
    this.flush()
  }

  clear() {
    this._preferences?.clear((err: BusinessError) => {
      if (err) {
        console.error(`清空所有数据失败: code=${err.code}, message=${err.message}`)
      }
      console.log('清空所有数据成功')
      this.flush()
    })
  }

  clearSync() {
    this._preferences?.clearSync()
    this.flush()
  }

  has(key: string): Promise<boolean> {
    return new Promise(resolve => {
      this._preferences?.has(key, (err: BusinessError, flag: boolean) => {
        if (err) {
          console.error(`检查失败: code=${err.code}, message=${err.message}`)
          return
        }
        resolve(flag)
        console.log('检查的key值是否存在:', flag) // true
      })
    })
  }

  hasSync(key: string): Promise<boolean | undefined> {
    return new Promise(resolve => {
      resolve(this._preferences?.hasSync(key))
    })
  }

  flush() {
    this._preferences?.flush((err: BusinessError) => {
      if (err) {
        console.log(`Failed to flush:code=${err.code},message:${err.message}}`)
        return
      }
      console.log('Succeeded in flush')
    })
  }
}

export default PreferenceUtil 
  • 组件内使用
TypeScript 复制代码
import PreferenceUtil from './storage'

@Entry
@Component
struct Index {
  preferences?: PreferencesUtil
  
  async aboutToAppear(): Promise<void>{
    this.preferenceUtil = new PreferenceUtil()
    console.log('Get name', await this.preferenceUtil?.get('name')) 
  }
}

问题

数据不一致问题

点这里查看

最后

如果大家有不理解的地方可以留言,或自行阅读文档 文档地址

相关推荐
冉冉同学1 小时前
【HarmonyOS NEXT】解决微信浏览器无法唤起APP的问题
android·前端·harmonyos
别说我什么都不会2 小时前
【仓颉三方库】 数据库驱动——kv4cj
harmonyos
进击的圆儿3 小时前
鸿蒙应用(医院诊疗系统)开发篇2·Axios网络请求封装全流程解析
华为·harmonyos
鸿蒙布道师3 小时前
鸿蒙NEXT开发文件预览工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师3 小时前
鸿蒙NEXT开发全局上下文管理类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
别说我什么都不会9 小时前
【仓颉三方库】 数据库驱动——redis-sdk
harmonyos
悬空八只脚9 小时前
React-Native开发鸿蒙NEXT-环境配置问题(续)
harmonyos
寒雪谷9 小时前
用户登陆UI
开发语言·javascript·ui·harmonyos·鸿蒙
simple_lau9 小时前
鸿蒙项目如何调起微信功能
harmonyos
simple_lau9 小时前
如何发布HarmonyOS应用
harmonyos·arkts·arkui