HarmonyOS 新手入门:ArkData 键值型数据库,比 Preferences 更像一个小仓库

举报
蓝瘦的蜕变 发表于 2026/06/23 18:57:09 2026/06/23
【摘要】 上一篇写了 Preferences,它适合存昵称、主题、字号这类小配置。 这篇继续看 ArkData 里的键值型数据库,也就是 distributedKVStore。它同样是 keyvalue 思路,但比 Preferences 更适合放业务数据,比如草稿、缓存、离线任务状态这类“有一点量,但又不想上关系型数据库”的数

ArkData 键值型数据库:比 Preferences 更像一个小仓库

上一篇写了 Preferences,它适合存昵称、主题、字号这类小配置。

这篇继续看 ArkData 里的键值型数据库,也就是 distributedKVStore。它同样是 key-value 思路,但比 Preferences 更适合放业务数据,比如草稿、缓存、离线任务状态这类“有一点量,但又不想上关系型数据库”的数据。

arkdata-kv-store-cover

官方文档可以先收着:

先分清它和 Preferences

Preferences 更像设置项文件。

KVStore 更像一个本地 key-value 数据库。

我一般会这么区分:

  • App 设置、用户偏好:用 Preferences
  • 草稿箱、搜索历史、临时缓存、离线状态:可以考虑 KVStore
  • 复杂列表、多条件查询、多表关系:别硬塞,直接看关系型数据库

新手最容易混的就是:看到它们都能 put/get,就觉得差不多。其实场景不一样,Preferences 更轻,KVStore 更偏业务数据存储。

核心流程

键值型数据库比 Preferences 多一步:先创建 KVManager,再拿具体的 KVStore

流程大概是这样:

  1. createKVManager:创建管理器。
  2. getKVStore:打开或创建一个键值数据库。
  3. put / get / delete:写入、读取、删除数据。

最小初始化代码:

const context = getContext(this) as common.UIAbilityContext;

const kvManager = distributedKVStore.createKVManager({
  context: context,
  bundleName: context.abilityInfo.bundleName
});

const options: distributedKVStore.Options = {
  createIfMissing: true,
  encrypt: false,
  backup: false,
  autoSync: false,
  kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
  securityLevel: distributedKVStore.SecurityLevel.S1
};

const store = await kvManager.getKVStore<distributedKVStore.SingleKVStore>(
  'demo_note_store',
  options
);

这里先用 SINGLE_VERSION,新手入门更好理解。等后面真的要做跨设备协同,再去看 DEVICE_COLLABORATION

读写数据

写入一条草稿:

await store.put('draft_title', '我的第一条草稿');
await store.put('draft_content', '今天先把 KVStore 跑通。');

读取:

const title = await store.get('draft_title') as string;
const content = await store.get('draft_content') as string;

删除:

await store.delete('draft_title');
await store.delete('draft_content');

Preferences 不一样,这里没有 flush() 这一步。KVStoreput 本身就是数据库写入操作。

适合做什么 Demo

如果你要给新手讲 KVStore,我建议别一上来就讲分布式同步。

先做一个“草稿箱”最容易理解:

  • 输入标题
  • 输入内容
  • 点击保存,写入 KVStore
  • 点击读取,打开半模态面板展示数据库里保存的 key-value 信息
  • 重新进入页面,自动读出上次保存的草稿
  • 点击清空,删除对应 key

这个场景比“保存主题模式”更像业务数据,也更能体现 KVStore 和 Preferences 的区别。

小坑提醒

storeId 建议只用字母、数字、下划线,比如 demo_note_store

securityLevel 必填,入门 Demo 可以先用 S1。如果存的是敏感数据,就不要随便照抄这个级别,要按数据敏感程度重新选。

get 读取不存在的 key 时要做好异常处理。新手写 Demo 时可以先用 try/catch 包起来,不然页面上看起来像是“没反应”。

评论区聊聊

你们会把什么数据放进键值型数据库?

草稿、搜索历史、离线缓存,还是一些接口结果?如果你在真实项目里用过 KVStore,也可以聊聊你是怎么设计 key 的。这个点挺实用,后续写 Demo 时可以直接拿真实场景来展开。

完整页面代码

ArkDataKVStoreDemo.ets

import { common } from '@kit.AbilityKit';
import { distributedKVStore } from '@kit.ArkData';
import { promptAction, router } from '@kit.ArkUI';

const STORE_ID: string = 'demo_note_store';
const KEY_TITLE: string = 'draft_title';
const KEY_CONTENT: string = 'draft_content';

class SavedInfoItem {
  label: string = '';
  value: string = '';

  constructor(label: string, value: string) {
    this.label = label;
    this.value = value;
  }
}

@Entry
@Component
struct ArkDataKVStoreDemo {
  @State title: string = '';
  @State content: string = '';
  @State tips: string = '启动后自动读取草稿';
  @State showResultSheet: boolean = false;
  @State savedItems: SavedInfoItem[] = [];

  private store: distributedKVStore.SingleKVStore | undefined = undefined;

  aboutToAppear(): void {
    this.loadDraft();
  }

  private async getStore(): Promise<distributedKVStore.SingleKVStore> {
    if (this.store !== undefined) {
      return this.store;
    }

    const context = getContext(this) as common.UIAbilityContext;
    const kvManager = distributedKVStore.createKVManager({
      context: context,
      bundleName: context.abilityInfo.bundleName
    });

    const options: distributedKVStore.Options = {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: false,
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
      securityLevel: distributedKVStore.SecurityLevel.S1
    };

    this.store = await kvManager.getKVStore<distributedKVStore.SingleKVStore>(
      STORE_ID,
      options
    );
    return this.store;
  }

  private async readStringValue(store: distributedKVStore.SingleKVStore, key: string): Promise<string> {
    try {
      return await store.get(key) as string;
    } catch (err) {
      return '';
    }
  }

  private updateSavedItems(): void {
    this.savedItems = [
      new SavedInfoItem('数据库 Store ID', STORE_ID),
      new SavedInfoItem('标题 Key', KEY_TITLE),
      new SavedInfoItem('标题 Value', this.title.length > 0 ? this.title : '未保存'),
      new SavedInfoItem('内容 Key', KEY_CONTENT),
      new SavedInfoItem('内容 Value', this.content.length > 0 ? this.content : '未保存')
    ];
  }

  private async loadDraft(): Promise<void> {
    const store = await this.getStore();
    this.title = await this.readStringValue(store, KEY_TITLE);
    this.content = await this.readStringValue(store, KEY_CONTENT);
    this.updateSavedItems();
    this.tips = this.title.length > 0 || this.content.length > 0 ?
      '读取成功:这是上次保存的草稿' :
      '当前还没有保存草稿';
  }

  private async showSavedInfo(): Promise<void> {
    await this.loadDraft();
    this.showResultSheet = true;
  }

  private async saveDraft(): Promise<void> {
    try {
      const store = await this.getStore();
      const title = this.title.length > 0 ? this.title : '未命名草稿';
      await store.put(KEY_TITLE, title);
      await store.put(KEY_CONTENT, this.content);
      this.title = title;
      this.updateSavedItems();
      this.tips = '保存成功:KVStore 写入后不需要 flush';
      promptAction.showToast({ message: '草稿已保存' });
    } catch (err) {
      this.tips = '保存失败,请查看日志';
      promptAction.showToast({ message: '保存失败' });
      console.error('KVStore save failed');
    }
  }

  private async deleteKey(store: distributedKVStore.SingleKVStore, key: string): Promise<void> {
    try {
      await store.delete(key);
    } catch (err) {
      console.info(`KVStore key not found: ${key}`);
    }
  }

  private async clearDraft(): Promise<void> {
    try {
      const store = await this.getStore();
      await this.deleteKey(store, KEY_TITLE);
      await this.deleteKey(store, KEY_CONTENT);
      this.title = '';
      this.content = '';
      this.updateSavedItems();
      this.tips = '已清空草稿';
      promptAction.showToast({ message: '草稿已清空' });
    } catch (err) {
      this.tips = '清空失败,请查看日志';
      promptAction.showToast({ message: '清空失败' });
      console.error('KVStore clear failed');
    }
  }

  @Builder
  private savedInfoSheet() {
    Column({ space: 14 }) {
      Text('数据库读取结果')
        .width('100%')
        .fontSize(22)
        .fontWeight(700)
        .fontColor('#0F172A')

      Text('下面列表只展示当前 Demo 已保存到 KVStore 的数据库信息,不做 LazyForEach 懒加载渲染处理。')
        .width('100%')
        .fontSize(13)
        .fontColor('#64748B')

      Column({ space: 8 }) {
        ForEach(this.savedItems, (item: SavedInfoItem) => {
          Column({ space: 4 }) {
            Text(item.label)
              .fontSize(12)
              .fontColor('#64748B')
            Text(item.value)
              .fontSize(16)
              .fontColor('#0F172A')
          }
          .width('100%')
          .padding(12)
          .backgroundColor('#F8FAFC')
          .borderRadius(8)
        }, (item: SavedInfoItem) => item.label)
      }
      .width('100%')

      Button('关闭')
        .width('100%')
        .height(44)
        .fontSize(16)
        .backgroundColor('#0EA5E9')
        .onClick(() => {
          this.showResultSheet = false;
        })
    }
    .width('100%')
    .padding({ left: 20, right: 20, top: 12, bottom: 20 })
  }

  build() {
    Scroll() {
      Column({ space: 18 }) {
        Row() {
          Text('返回')
            .fontSize(14)
            .fontColor('#0EA5E9')
            .onClick(() => {
              router.back();
            })

          Blank()
        }
        .width('100%')

        Text('ArkData KVStore')
          .width('100%')
          .fontSize(28)
          .fontWeight(700)
          .fontColor('#182431')

        Text('用键值型数据库保存一份本地草稿。')
          .width('100%')
          .fontSize(14)
          .fontColor('#56616F')

        Column({ space: 12 }) {
          Text('草稿标题')
            .fontSize(14)
            .fontColor('#334155')

          TextInput({ placeholder: '输入标题', text: this.title })
            .height(44)
            .fontSize(16)
            .backgroundColor('#F1F5F9')
            .fontColor('#0F172A')
            .onChange((value: string) => {
              this.title = value;
            })
        }
        .width('100%')

        Column({ space: 12 }) {
          Text('草稿内容')
            .fontSize(14)
            .fontColor('#334155')

          TextArea({ placeholder: '输入草稿内容', text: this.content })
            .height(140)
            .fontSize(16)
            .backgroundColor('#F1F5F9')
            .fontColor('#0F172A')
            .onChange((value: string) => {
              this.content = value;
            })
        }
        .width('100%')

        Text(this.title.length > 0 ? this.title : '未命名草稿')
          .width('100%')
          .padding(16)
          .fontSize(18)
          .fontWeight(700)
          .fontColor('#0F172A')
          .backgroundColor('#E0F2FE')
          .borderRadius(8)

        Text(this.content.length > 0 ? this.content : '这里会预览草稿内容')
          .width('100%')
          .padding(16)
          .fontSize(15)
          .fontColor('#334155')
          .backgroundColor('#F8FAFC')
          .borderRadius(8)

        Row({ space: 12 }) {
          Button('保存')
            .layoutWeight(1)
            .height(44)
            .fontSize(16)
            .backgroundColor('#0EA5E9')
            .onClick(() => {
              this.saveDraft();
            })

          Button('读取')
            .layoutWeight(1)
            .height(44)
            .fontSize(16)
            .fontColor('#0F172A')
            .backgroundColor('#BAE6FD')
            .onClick(() => {
              this.showSavedInfo();
            })

          Button('清空')
            .layoutWeight(1)
            .height(44)
            .fontSize(16)
            .fontColor('#0F172A')
            .backgroundColor('#CBD5E1')
            .onClick(() => {
              this.clearDraft();
            })
        }
        .width('100%')

        Text(this.tips)
          .width('100%')
          .fontSize(13)
          .fontColor('#64748B')
      }
      .width('100%')
      .padding(24)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
    .bindSheet(this.showResultSheet, this.savedInfoSheet(), {
      height: SheetSize.MEDIUM,
      dragBar: true,
      showClose: true,
      onDisappear: () => {
        this.showResultSheet = false;
      }
    })
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。