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

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

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

主要特性:

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

初始化 Preferences 实例

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

将数据写入缓存中

示例代码:

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

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

this.dataPreferences?.put('name', '诡术妖姬')

获取数据 get

从缓存中获取键对应的值

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

示例代码:

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

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

示例代码:

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

检查是否存在 has

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

示例代码:

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的键值数据

示例代码:

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

删除所有数据 clear

删除缓存中的所有数据

示例代码:

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

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

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

示例代码:

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异步回调,如下:

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

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

同步写法

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

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

监听数据变更

监听所有key的数据变更

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

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

示例代码:

@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

示例代码:

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

示例代码:

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 类
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 
  • 组件内使用
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')) 
  }
}

问题

数据不一致问题

点这里查看

最后

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

  • 初始化 Preferences 实例
  • 操作数据的方法(以下都是异步写法,下面会有同步写法)
  • 写入数据 put
  • 获取数据 get
  • 获取所有数据 getAll
  • 检查是否存在 has
  • 删除数据 delete
  • 删除所有数据 clear
  • 将缓存数据同步到磁盘文件 flush
  • 另外一种异步写法
  • 同步写法
  • 监听数据变更
  • 监听所有key的数据变更
  • 监听指定key的数据变更
  • 监听进程间的数据变更
  • 取消监听
  • Preferences使用的约束限制
  • 封装
  • 问题
  • 数据不一致问题
  • 最后
posted @   时光凉忆  阅读(166)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
点击右上角即可分享
微信分享提示