HarmonyOS应用数据持久化:Preferences用户首选项全攻略(实战场景 + 封装 + 面试对比)

58 阅读10分钟

✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃

在鸿蒙应用开发中,数据持久化是核心需求之一 —— 无论是用户配置、业务数据还是登录状态,都需要通过合适的方式保存到设备中,避免应用重启后数据丢失。而用户首选项(Preferences) 作为最常用、最轻量化的持久化方案,是每个鸿蒙开发者必须掌握的技能。本文将以 Preferences 为核心,详解其用法、实战封装,并对比其他持久化方案,帮你彻底搞定轻量数据存储难题。​

一、鸿蒙 3 种数据持久化方案速览(聚焦 Preferences 定位)​

鸿蒙提供 3 种主流持久化方式,其中 Preferences 主打 “轻量、快速、易用”,先通过表格明确其定位:

持久化方案​核心特性​适用场景​数据规模限制​
用户首选项(Preferences)(核心)​键值对存储、全量加载到内存、访问速度快​应用配置(字体大小、夜间模式)、用户偏好​单条数据≤16MB,总数据≤50MB,建议≤1 万条​
键值型数据库(KV-Store)​非关系型、分布式友好、跨设备兼容​无复杂关系的业务数据(如收藏列表)​无明确上限,适合中少量数据​
关系型数据库(RelationalStore)​支持 SQL、行列表存储、事务处理​复杂关系数据(如订单、用户 - 商品关联)​适合大量结构化数据​

核心选型原则:​

  • 简单配置 / 小数据 → 优先用户首选项(Preferences)​
  • 无关系业务数据 / 跨设备场景 → KV-Store​
  • 复杂关联数据 / 需 SQL 查询 → 关系型数据库

二、用户首选项(Preferences)核心详解​

作为鸿蒙轻量数据持久化的首选,Preferences 以文本形式存储键值对,应用启动时全量加载到内存,读写效率极高,但不适合存储大量数据(内存占用会随数据量增长而增大)。​

❶ 适用场景快速判断​

适用存储场景(如下图中 1、2、3、5、6、10、11) 针对于小白开发者来说可以推敲下为什么这些场景要用Preferences来存储而不是api接口直接获取展示🤔。

基础较为薄弱的小伙伴也可以私信 有问必答🍃

编辑

快速判断标准:​

  • 单条数据(原始类型 / 单个对象)→ 直接用 Preferences​
  • 多条对象 / 复杂结构数据 → 切换关系型数据库

❷ 核心依赖:上下文(context)​

操作 Preferences 必须先获取context(上下文环境),其核心作用:​

  • 提供应用唯一标识、临时目录等关键信息​
  • 支持应用生命周期相关操作(如协议页不同意则退出应用)
import { Dialog } from '../components/Dialog'
import { router } from '@kit.ArkUI'
import { common } from '@kit.AbilityKit'

@Entry
@Component
struct Welcome {

  private privacyPolicyDialog = new Dialog(this.getUIContext(), 'privacyPolicy', {
    ok: () => {
      router.replaceUrl({
        url:'pages/Index'
      })
      this.privacyPolicyDialog.close()
    },
    cancel: () => {
      let context = getContext(this) as common.UIAbilityContext;
      context.terminateSelf()
      this.privacyPolicyDialog.close()
    }
  })


  aboutToAppear() {
    this.privacyPolicyDialog.open()
  }


  build() {
    Column() {
    }
  }
}

上述是伪代码,主要让大家理解下context场景 (ps. context是鸿蒙非常重要的一个知识点 这里简单概括  不是很清楚也可以ai以下)

❸ 基础开发 3 步走(快速上手)​

  1. 导入核心模块​
import { preferences } from '@kit.ArkData';

2. 获取 Preferences 实例​

通过上下文创建实例,name为存储到磁盘的文件名:​

// 简洁写法
const dataPreferences = preferences.getPreferencesSync(this.context, { name: 'myStore' });

3. 核心 API 使用(存 / 读 / 删)​

操作​API 语法​说明​
存储​putSync(键, 值) + flush()​putSync写入内存,flush()同步到磁盘(实现持久化)​
读取​getSync(键, 默认值)​从内存中读取,应用重启后会从磁盘加载到内存​
删除​deleteSync(键)​从内存中删除,需配合flush()同步到磁盘​

​⚠️ 关键提醒(3 遍强调):​

Preferences 数据持久化功能必须在模拟器或真机上运行,预览模式无法生效!​

Preferences 数据持久化功能必须在模拟器或真机上运行,预览模式无法生效!​

Preferences 数据持久化功能必须在模拟器或真机上运行,预览模式无法生效!​

磁盘文件路径:/data/app/el2/100/base/应用唯一标识/haps/entry/preferences/文件名(可通过 Device File Browser 查看)​

❹ 基础使用示例(入门学习用)​

  1. 初始化 Preferences(entryability 中)​应用启动时初始化,避免重复创建实例:​
import { preferences } from '@kit.ArkData';
import { UIAbility } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage) {
    // 初始化 Preferences 实例
    const dataPreferences = preferences.getPreferencesSync(this.context, { name: 'myStore' });
    // 存入AppStorage(仅学习用,实战不推荐)
    AppStorage.setOrCreate('dataPreferences', dataPreferences);
    // ...其他初始化逻辑
  }
}

2. 页面中读写数据​

import { preferences } from '@kit.ArkData';

@Entry
@Component
struct Index {
  // 获取 Preferences 实例
  private dataPreferences: preferences.Preferences = AppStorage.get('dataPreferences') as preferences.Preferences;

  build() {
    Column({ space: 10 })
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center) {
        Button('存储数据').onClick(() => {
          this.dataPreferences.putSync('userPrivacyKey', true); // 写入内存
          this.dataPreferences.flush(); // 同步到磁盘
        });

        Button('读取数据').onClick(() => {
          const privacyStatus = this.dataPreferences.getSync('userPrivacyKey', false);
          console.log('隐私协议状态:', privacyStatus);
        });

        Button('删除数据').onClick(() => {
          this.dataPreferences.deleteSync('userPrivacyKey');
          this.dataPreferences.flush(); // 同步删除结果
        });
      }
}

❺ 实战封装:PreferencesUtil 工具类(企业级用法)​

入门示例仅用于学习,实战中需封装工具类,统一管理 Preferences 实例、避免重复代码。​

a. 封装步骤分析​

  1. 定义工具类(大驼峰命名,后缀 Util)​
  2. 封装核心方法(初始化、存、读、同步磁盘)​
  3. 处理实例唯一性(避免重复创建)​
  4. 导出单例对象(小驼峰命名,方便导入)​

b. 工具类代码(utils/PreferencesUtil.ets)

工作推荐api改成异步 这里为了减少代码用的同步 

import { common } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';

class PreferencesUtil {
  // 私有实例,避免外部修改
  private dataPreferences: preferences.Preferences | null = null;

  /**
   * 初始化 Preferences(在entryability中调用)
   * @param context 上下文环境
   */
  loadPreferences(context: common.UIAbilityContext) {
    if (!this.dataPreferences) {
      this.dataPreferences = preferences.getPreferencesSync(context, { name: 'myStore' });
    }
  }

  /**
   * 存储数据到内存(需配合flush同步到磁盘)
   * @param key 键
   * @param value 值(支持原始类型/单个对象)
   */
  put(key: string, value: preferences.ValueType) {
    this.dataPreferences?.putSync(key, value);
  }

  /**
   * 从内存读取数据
   * @param key 键
   * @param defaultValue 默认值
   * @returns 存储的值或默认值
   */
  get(key: string, defaultValue: preferences.ValueType) {
    return this.dataPreferences?.getSync(key, defaultValue) ?? defaultValue;
  }

  /**
   * 存储并同步到磁盘(推荐优先使用)
   * @param key 键
   * @param value 值
   */
  flush(key: string, value: preferences.ValueType) {
    this.put(key, value);
    this.dataPreferences?.flushSync(); // 同步到磁盘,确保持久化
  }
}

// 导出单例对象(全局唯一)
export const preferencesUtil = new PreferencesUtil();

c. 实战使用步骤​

(1)初始化工具类(entryability 中)

import { preferencesUtil } from '../utils/PreferencesUtil';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage) {
    // 初始化 Preferences(仅需调用一次)
    preferencesUtil.loadPreferences(this.context);
    // ...其他逻辑
  }
}

(2)页面中使用

import { preferencesUtil } from '../utils/PreferencesUtil';

@Entry
@Component
struct Index {
  build() {
    Column({ space: 10 })
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center) {
        Button('存储用户信息').onClick(() => {
          preferencesUtil.flush('userName', '神龙教主');
          preferencesUtil.flush('isLogin', true);
        });

        Button('读取用户信息').onClick(() => {
          const userName = preferencesUtil.get('userName', '匿名用户');
          const isLogin = preferencesUtil.get('isLogin', false);
          console.log(`用户名:${userName},登录状态:${isLogin}`);
        });
      }
}

三、企业级面试题:HarmonyOS应用数据持久化周边

 数据持久化用过哪些? 🐝🐝

用户首选项(Preferences):通过用户保存应用的配置信息,用户的个性化设置(字体大小,是否开启夜间模式)、用户登录信息、手动选择的城市、下拉上次更新日期、首页统计数据缓存、搜索历史、个人中心数据统计、个人中心最近学习缓存等等
键值型数据接口(KV-Store):非关系数据库,一般用的少,可以用于同应用跨设备数据同步,数据库备份与恢复。
关系型数据库(RelationalStore): 关系型数据库,可用来存储离线数据,例如离线收藏、使用搜索记录、视频缓存到本地列表等大量数据库缓存、首页列表数据缓存、下载、收藏、最近缓存。

首选项如何实现持久化 🐝🐝

a.utils目录下创建PreferenceUtil.ets工具
	- this.dataPreferences = preferences.getPreferencesSync(context, {name: 'myStore'})
	- this.dataPreferences?.putSync(key, value)
	- this.dataPreferences?.flushSync() 
	- this.dataPreferences?.getSync()
	
b.entryability中初始化PreferenceUtil实例

c.import {preferencesUtil} from '../utils/PreferenceUtil'  
preferencesUtil.get()
preferencesUtil.put()
preferencesUtil.set()

首选项和PersistentStorage区别

1.PersistentStorage是同步写入磁盘;Preferences是全量加载进内存。
2.PersistentStorage的持久化变量最好是小于2kb的数据;Preferences存储的数据不超过一万条 累计不超过50M,Key键为string类型,要求非空且长度不超过80个字节,如果Value值为string类型,长度不超过8192个字节。
3.PersistentStorage只能在UI页面内使用。
4.PersistentStorage不要大量的数据持久化,因为PersistentStorage写入磁盘的操作是同步的,大量的数据本地化读写会同步在UI线程中执行,影响UI渲染性能。如果开发者需要存储大量的数据,建议使用数据库api。
5.PersistentStorage存储AppStorage属性UI状态,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同;Preferences一般为应用保存用户的个性化设置(字体大小,是否开启夜间模式)等

PersistentStorage进行数据持久化,和preferences用户首选项使用场景? 

PersistentStorage适合存小于2Kb的数据,  
preferences 适合存用户的个性化设置(字体大小,是否开启夜间模式)、用户登录信息等数据,但是不要超多一万条神龙教主 累计不超过50M。

非UI页面使用用户首选项时context如何获取

可以在UIAbility里通过AppStorageLocalStorage里存储context,然后在非ArkUI页面里使用。

this.context

getContext(this)

通过用户首选项实现数据持久化之后,如果App更新版本,之前首选项的数据是否会保留

持久化之后的数据是存在文件中的,App更新版本之后是存在的。

关系型数据库(RelationalStore)如何实现

a.utils目录下创建RdbUtil.ets工具
			1. loadRdbStore   relationalStore.getRdbStore、 store.executeSql(tableSql)、保存起来
			2. 调用insert/delete/query实现增删查功能


b.entryability中初始化RdbUtil实例

c.import {xxxTable} from '../utils/RdbUtil'  
xxxTable.get()
xxxTable.put()
xxxTable.set()

键值型数据接口(KV-Store)如何实现

a.utils目录下创建KVStoreUtil.ets工具
	import { distributedKVStore } from "@kit.ArkData";
    import { common } from "@kit.AbilityKit";

    export type KVValueType =  Uint8Array | string | number | boolean

    class KVStoreUtil {

      private kvStore: distributedKVStore.SingleKVStore | undefined = undefined;

      // 创建商店对象
      loadKVStore(context:common.UIAbilityContext) {}

      // 操作商店对象
      put(key:string, value: KVValueType) {   }
      get(key:string) { }
      delete(key:string)  {}
    }

    export const kvStoreUtil = new KVStoreUtil()
	
b.entryability中初始化KVStoreUtil实例

c.import {kvStoreUtil} from '../utils/KVStoreUtil'  
kvStoreUtil.get()
kvStoreUtil.put()
kvStoreUtil.set()

跨模块、跨进程时如何保证正常读取首选项中数据?

跨模块:

由于context不同,获取到的是不同的首选项实例,因此会导致在跨模块、多页面等场景下取不到数据。此时可以考虑通过单例类在EntryAbility中存一个全局的context,或者使用应用级context。

跨进程:

不同进程一般只有在同沙箱的场景才能访问到一个preference文件,多进程可以通过dataGroupId在多个进程间共用一个preference文件。

首选项存储如果多个任务同时写入、并发的情况能保证数据的准确性吗

首选项无法保证进程并发安全,会有文件损坏和数据丢失的风险,不支持在多进程场景下使用。

对于多线程操作首选项和数据库是不是线程安全的?还是每一个线程独立的

是线程安全的。

用户首选项是线程安全的吗

首选项是线程安全的,所以多线程访问可以保证数据一致性,但只支持同进程,不支持多进程。

不依赖接口 用手机存储 怎么存储

自己通过文件管理直接写数据然后获取出来

let filePath = pathDir + "/test.txt";
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
let str: string = "hello, world";
let writeLen = fs.writeSync(file.fd, str);
console.info("write data to file succeed and size is:" + writeLen);
fs.closeSync(file);

鸿蒙开发者班级

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃

    ^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤