引言

前几篇数据都是存在内存里的——App重启数据就丢了。本篇用 @ohos.data.preferences 实现数据的本地持久化,确保数据在App重启后仍然存在。

先看本次要持久化的数据——新增一笔 66 元交通账单:

在这里插入图片描述


一、持久化方案

新增/编辑/删除

查询

应用启动

从Preferences
加载账单数据

内存中操作BillStore

同步到Preferences

flushSync落盘

直接从内存返回


二、实现持久化 Store

// store/PersistentBillStore.ts
import preferences from '@ohos.data.preferences';
import { BillItem, BillItemInput, BillType, BillCategory } from '../model/BillItem';

export class PersistentBillStore {
  private bills: BillItem[] = [];
  private prefs: preferences.Preferences | null = null;
  private isReady: boolean = false;
  private pendingQueue: Array<() => void> = [];

  // 初始化:加载Preferences
  async init(context: Context): Promise<void> {
    this.prefs = await preferences.getPreferences(context, 'easy_account');
    
    // 从Preferences读取序列化数据
    const jsonStr = this.prefs.getSync('bills_data', '[]') as string;
    try {
      this.bills = JSON.parse(jsonStr);
    } catch {
      this.bills = [];
    }
    
    this.isReady = true;
    // 执行等待队列
    this.pendingQueue.forEach(fn => fn());
    this.pendingQueue = [];
  }

  // 保存到磁盘
  private save(): void {
    if (!this.prefs) return;
    const jsonStr = JSON.stringify(this.bills);
    this.prefs.putSync('bills_data', jsonStr);
    this.prefs.flushSync();
  }

  // 等待初始化完成
  private ensureReady(): Promise<void> {
    if (this.isReady) return Promise.resolve();
    return new Promise(resolve => {
      this.pendingQueue.push(() => resolve());
    });
  }

  // 增删改查
  async getAll(): Promise<BillItem[]> {
    await this.ensureReady();
    return [...this.bills];
  }

  getById(id: string): BillItem | undefined {
    return this.bills.find(b => b.id === id);
  }

  add(bill: BillItemInput): BillItem {
    // ArkTS 不支持对象展开,逐字段构造
    const newBill: BillItem = {
      type: bill.type,
      category: bill.category,
      amount: bill.amount,
      note: bill.note,
      date: bill.date,
      id: Date.now().toString(36) + Math.random().toString(36).substring(2, 7),
      createTime: Date.now()
    };
    this.bills.unshift(newBill);
    this.save();
    return newBill;
  }

  update(id: string, updates: Partial<BillItem>): boolean {
    const idx = this.bills.findIndex(b => b.id === id);
    if (idx === -1) return false;
    // ArkTS 不支持对象展开合并,改为逐字段判断更新
    if (updates.type !== undefined) {
      this.bills[idx].type = updates.type;
    }
    if (updates.category !== undefined) {
      this.bills[idx].category = updates.category;
    }
    if (updates.amount !== undefined) {
      this.bills[idx].amount = updates.amount;
    }
    if (updates.note !== undefined) {
      this.bills[idx].note = updates.note;
    }
    if (updates.date !== undefined) {
      this.bills[idx].date = updates.date;
    }
    this.save();
    return true;
  }

  delete(id: string): boolean {
    const idx = this.bills.findIndex(b => b.id === id);
    if (idx === -1) return false;
    this.bills.splice(idx, 1);
    this.save();
    return true;
  }

  // 数据迁移(版本升级时)
  migrateIfNeeded(): void {
    const version = this.prefs?.getSync('data_version', 1) as number;
    if (version < 2) {
      // v1 → v2 迁移逻辑
      this.prefs?.putSync('data_version', 2);
      this.prefs?.flushSync();
    }
  }
}

// 导出全局单例
export const persistentStore = new PersistentBillStore();

三、在 App 入口初始化

// entryability/EntryAbility.ets
import { persistentStore } from '../store/PersistentBillStore';

export default class EntryAbility extends UIAbility {
  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    // 在 Ability 创建时初始化持久化
    await persistentStore.init(this.context);
    console.log('账单数据加载完成');
  }
}

四、首页加载持久化数据

// pages/HomePage.ets 修改
import { persistentStore } from '../store/PersistentBillStore';

@Entry
@Component
struct HomePage {
  @State bills: BillItem[] = [];
  @State isLoading: boolean = true;

  async aboutToAppear() {
    this.isLoading = true;
    // 从持久化存储加载数据
    this.bills = await persistentStore.getAll();
    this.isLoading = false;
  }

  build() {
    Stack() {
      if (this.isLoading) {
        // 加载中显示骨架屏
        LoadingPlaceholder()
      } else {
        // 正常内容
        this.MainContent()
      }
    }
  }
}

新增成功后首页同步更新——支出从 ¥434 变为 ¥500,新账单出现在列表顶部:

在这里插入图片描述


五、迁移内存数据

如果你之前用内存版 BillStore 已经有数据了,需要一个迁移入口:

// utils/MigrateData.ts
import { billStore } from '../store/BillStore';
import { persistentStore } from '../store/PersistentBillStore';

export async function migrateFromMemory() {
  const memoryBills = billStore.getAll();
  if (memoryBills.length === 0) return;
  
  const existBills = await persistentStore.getAll();
  if (existBills.length > 0) return; // 已经有数据了
  
  // 将内存数据写入持久化
  for (const bill of memoryBills) {
    persistentStore.add({
      type: bill.type,
      category: bill.category,
      amount: bill.amount,
      note: bill.note,
      date: bill.date
    });
  }
  console.log(`已迁移 ${memoryBills.length} 条数据到持久化存储`);
}

六、数据导出验证

调试时可以查看 Preferences 存储的文件:

沙箱路径: /data/storage/el2/base/preferences/easy_account.xml

打开后能看到序列化的 JSON 数据:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<preferences>
  <string name="bills_data">[{"id":"xxx","type":"expense",...}]</string>
  <int name="data_version" value="2"/>
</preferences>

完全关闭 App 后重新启动——66 元交通账单、¥500 支出、¥15000 收入原样恢复,数据真正"存得住"了:

在这里插入图片描述


七、异常处理

// 保存失败时重试
private saveWithRetry(maxRetries = 3): boolean {
  for (let i = 0; i < maxRetries; i++) {
    try {
      this.save();
      return true;
    } catch (e) {
      console.error(`保存失败(第${i + 1}次):`, e.message);
    }
  }
  console.error('保存失败,已达最大重试次数');
  return false;
}

总结

本篇实现了:

  1. Preferences 持久化:增删改自动同步到磁盘
  2. 异步初始化:Ability 创建时加载数据
  3. 版本迁移:支持数据格式升级
  4. 异常处理:保存失败自动重试
  5. 数据验证:可直接查看沙箱 XML 文件

从这篇开始,随手账本的数据就真正"存得住"了。下篇实现数据导出与分享功能。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐