跳过主要内容

Capacitor: Key-Value Storage - Preferences vs SQLite

比较 Capacitor: Preferences、SQLite key-value 存储和 Keychain 存储。了解哪一个适合设置、缓存和机密信息。

文章来源

马丁·多纳迪厄

作者

瓦莱里娅

审阅者

乔丹

编辑

Capacitor: Preferences 与 SQLite 存储

对于 Capacitor 中的 key-value 存储,建议使用 @capacitor/preferences for a handful of small settings, a SQLite-backed key-value store for anything larger or more numerous, and the Keychain or Keystore for secrets. This guide compares the options available on Capacitor 8, shows working code for each, and explains how to move from one to another without losing user data.

大多数应用程序最终会同时使用两个这些选项:Preferences 或 SQLite 来存储应用程序状态,和安全存储来存储认证令牌。

快速查看选项

localStorage / IndexedDB @capacitor/preferences 快速 SQL KeyValueStore @capgo/capacitor-data-storage-sqlite @capgo/capacitor-native-biometric setData
由 WebView 存储 UserDefaults / SharedPreferences SQLite 文件 SQLite 文件 密钥链 / Keystore
存活 OS 存储清理 不保证 是 Yes Yes Yes
值类型 字符串(localStorage) 字符串 JSON值、数字、布尔值 Uint8Array 字符串 字符串
合适的大小范围 小 小,几KB每个值 __CAPGO_KEEP_0__ __CAPGO_KEEP_0__ __CAPGO_KEEP_0__
加密 否 否 可选SQLCipher,使用安全存储的密钥 可选SQLCipher,配置文件中的口令 是,硬件支持
命名空间 每个源 group 选项 store 选项 表格 键名
前缀查询 否 否 列出键 filtervalues 否
Web 是 localStorage SQLite Wasm (OPFS) IndexedDB 不用于保密

为何不使用localStorage

localStorage 和 IndexedDB 在 Capacitor WebView 内部工作,但数据属于 WebView,而不是你的 app 的本机存储。操作系统可以在设备空间不足时回收 WebView 存储,且 Capacitor 的文档建议不要依赖它来存储无法丢失的数据。改变 app 的主机名或方案在 capacitor.config 也会改变起源,这会将 WebView 指向一个不同的、空的存储。

将它们用作可能消失的缓存。对于其他所有内容,请使用一个原生支持的选项。

选项 1: Capacitor Preferences for 小型设置

@capacitor/preferences 是轻量级键值数据的官方插件。它在 iOS 上写入 UserDefaults,在 Android 上写入 SharedPreferences,在 web 上写入 localStorage.

bun add @capacitor/preferences
bunx cap sync
import { Preferences } from '@capacitor/preferences';

interface Settings {
  theme: 'light' | 'dark' | 'system';
  fontScale: number;
}

const DEFAULTS: Settings = { theme: 'system', fontScale: 1 };

export async function loadSettings(): Promise<Settings> {
  const { value } = await Preferences.get({ key: 'settings' });
  return value ? { ...DEFAULTS, ...JSON.parse(value) } : DEFAULTS;
}

export async function saveSettings(settings: Settings): Promise<void> {
  await Preferences.set({ key: 'settings', value: JSON.stringify(settings) });
}

使用它来存储:主题、语言、引导标志、最后选中的标签、功能开关。

不要使用它来存储:

  • 机密信息。 值存储在应用沙盒内的文本中,并包含在设备备份中。
  • 大值。 UserDefaults 和 SharedPreferences 将整个文件加载到内存中。 大型 JSON blob 会减慢每次读取和写入。
  • 您需要通过前缀来清除或列出许多键。 keys() 返回所有内容,因此在 JavaScript 中进行过滤。

Preferences.configure({ group: 'MyApp' }) 更改键存储的命名空间。 默认组是 CapacitorStorage.

选项 2:SQLite键值存储

一旦您存储了缓存的API响应、草稿、离线队列或数百个条目,SQLite文件就是更好的选择。 它处理大值,写入是原子的,您可以对其进行加密。

快速SQL键值存储

@capgo/capacitor-fast-sql 包括一个 KeyValueStore 在其 SQLite 连接上创建一个 __kv_store 表格,保留值类型,并支持一个数据库中多个命名存储。

bun add @capgo/capacitor-fast-sql
bunx cap sync

Fast SQL 需要两个本机设置(iOS 本地网络在 Info.plist,一个本地主机清文规则在 Android)。它们在 如何在一个 Capacitor 应用中使用 SQLite.

import { KeyValueStore } from '@capgo/capacitor-fast-sql';

const cache = await KeyValueStore.open({ database: 'app', store: 'api-cache' });

// Objects, arrays, numbers, booleans, null and Uint8Array are stored as-is
await cache.set('user:42', { id: 42, name: 'Ada', roles: ['admin'] });
await cache.set('lastSync', Date.now());

const user = (await cache.get('user:42')) as { id: number; name: string } | null;
const exists = await cache.has('lastSync');
const allKeys = await cache.keys();

await cache.remove('user:42');
await cache.clear(); // only clears the 'api-cache' store
await cache.close();

API 总结: open(options), fromConnection(connection, store), set, get (返回 null 当缺失时), has, remove, clear, keys, close.

两个值得了解的模式:

多个存储,一个数据库。 一个 store 选项命名空间键, settings, drafts 所以, api-cache 可以独立清除并并存。

键值对紧邻真实表格。 如果您的应用程序已经使用Fast SQL进行关系数据,则可以重用连接,而不是打开第二个连接:

import { FastSQL, KeyValueStore } from '@capgo/capacitor-fast-sql';

const db = await FastSQL.connect({ database: 'app' });
const drafts = await KeyValueStore.fromConnection(db, 'drafts');
// You own `db`: drafts.close() does not disconnect it

稍后,当一个键值对的blob增长到您想要查询的内容(例如,所有一周前的草稿)时,您可以将其移动到同一数据库中的一个合适表格。

加密键值存储

KeyValueStore.open 接受相同的选项 FastSQL.connect,包括 encrypted 和 encryptionKey 在iOS和Android上:

import { KeyValueStore } from '@capgo/capacitor-fast-sql';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';

async function getKey(): Promise<string> {
  const { isSaved } = await NativeBiometric.isDataSaved({ key: 'kv.key' });
  if (isSaved) return (await NativeBiometric.getData({ key: 'kv.key' })).value;
  const bytes = crypto.getRandomValues(new Uint8Array(32));
  const key = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
  await NativeBiometric.setData({ key: 'kv.key', value: key });
  return key;
}

const secureStore = await KeyValueStore.open({
  database: 'secure-kv',
  encrypted: true,
  encryptionKey: await getKey(),
});

这需要在原生构建中使用SQLCipher。设置,plus当密钥丢失后如何处理备份恢复的内容,在 如何在Capacitor中加密SQLite数据库.

在capgo/capacitor-data-storage-sqlite中

@capgo/capacitor-data-storage-sqlite 是 SQLite (最初由 Jean Pierre Quéau 开发,现由 Capgo 维护) 的一个专门的键值插件。 值为字符串,且添加了几个额外功能:每个存储多个表格、前缀和后缀过滤、以及 JSON 导入和导出。

bun add @capgo/capacitor-data-storage-sqlite
bunx cap sync
import { CapgoCapacitorDataStorageSqlite as Store } from '@capgo/capacitor-data-storage-sqlite';

await Store.openStore({ database: 'appStore', table: 'settings' });

await Store.set({ key: 'locale', value: 'fr' });
await Store.set({ key: 'profile', value: JSON.stringify({ name: 'Ada' }) });

const { value } = await Store.get({ key: 'locale' });
const { result: hasProfile } = await Store.iskey({ key: 'profile' });
const { keys } = await Store.keys();

// Switch table inside the same store
await Store.setTable({ table: 'drafts' });
const { values } = await Store.filtervalues({ filter: 'note%' }); // values whose key starts with "note"

await Store.remove({ key: 'note-1' });
await Store.clear(); // clears the current table

filtervalues 将过滤器应用为 SQL LIKE 模式在键上(note% 前缀时, %note 后缀时,包含时为普通文本)。模式被插入到 SQL 字符串中,因此只传递您控制的过滤器,永远不要传递原始用户输入。

在 Web 上,它使用 IndexedDB 通过 localforage,您需要单独安装:

bun add localforage

关于其加密: openStore({ encrypted: true, mode: 'secret' }) 使用 SQLCipher 与一个从 capacitor.config under plugins.CapgoCapacitorDataStorageSqlite.encryptionSecretFast SQL KeyValueStore 或 data-storage-sqlite?

快速 SQLKeyValueStore 或数据存储sqlite?

  • 选择 快速 SQL KeyValueStore 如果您想要类型的值而不需要手动 JSON、二进制值、运行时加密密钥或已经使用快速 SQL 表格
  • 选择 数据存储- SQLite 如果您需要其表格和过滤功能、JSON 导出和导入或 Electron 支持

选项 3: 密钥链和密钥库

认证令牌、刷新令牌、API 密钥和数据库加密密钥应存储在平台安全存储中 @capgo/capacitor-native-biometric (8.6.0 和更高版本) expose 一个通用的键值对 API 在 iOS 密钥链和 Android 密钥库之上

bun add @capgo/capacitor-native-biometric
bunx cap sync
import { AccessControl, NativeBiometric } from '@capgo/capacitor-native-biometric';

// Silent read and write
await NativeBiometric.setData({ key: 'session.refreshToken', value: token });
const { value: refreshToken } = await NativeBiometric.getData({ key: 'session.refreshToken' });

// Require Face ID / Touch ID / fingerprint to read
await NativeBiometric.setData({
  key: 'wallet.pin',
  value: pin,
  accessControl: AccessControl.BIOMETRY_ANY,
});
const { value: secretPin } = await NativeBiometric.getSecureData({
  key: 'wallet.pin',
  reason: 'Confirm it is you',
});

await NativeBiometric.deleteData({ key: 'session.refreshToken' });

保持值小。该插件文档指出 Android Keystore 支持加密最佳在约 8 KB 每个值。如果您需要保护大量数据,请在此存储一个随机密钥并使用它来加密 SQLite 数据库。我们的文章 安全存储离线令牌 更深入地讨论令牌处理

iOS 中的 Keychain 项可能会在卸载应用后仍然存在。第一次启动后,清除陈旧的密钥,如果您的应用期望一个干净的白板(在偏好设置中存储一个“首次启动完成”标志,卸载时会删除它,并在它丢失时清除 Keychain 项)。

选择:决策列表

  1. 是秘密吗? 令牌、密码、PIN、密钥。使用 Keychain 或 Keystore 存储。
  2. 是几个小设置吗? 使用偏好设置。
  3. 是许多条目、较大的 JSON、二进制数据或您希望以组方式过期和清除的缓存吗? 使用 SQLite 键值存储。
  4. 您需要过滤、排序或连接它吗? 这不是键值数据了。创建一个表格,参见 SQLite 指南,或使用一个类型化的层,如 Kysely.

将现有数据从一个存储迁移到另一个存储

从 localStorage 将从"Preferences"或"Preferences"迁移到SQLite存储是启动时一次性复制。 在应用程序读取数据之前完成此操作,并记录它已运行。

import { Preferences } from '@capacitor/preferences';
import { KeyValueStore, type KeyValueValue } from '@capgo/capacitor-fast-sql';

export async function migrateToSqliteStore(): Promise<KeyValueStore> {
  const kv = await KeyValueStore.open({ database: 'app', store: 'settings' });
  if (await kv.has('__migrated_v1')) return kv;

  const { keys } = await Preferences.keys();
  for (const key of keys) {
    const { value } = await Preferences.get({ key });
    if (value === null) continue;
    let parsed: KeyValueValue = value;
    try {
      parsed = JSON.parse(value) as KeyValueValue;
    } catch {
      // keep plain strings as strings
    }
    await kv.set(key, parsed);
  }

  // Also copy from localStorage if older versions used it
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    if (key && !(await kv.has(key))) {
      await kv.set(key, localStorage.getItem(key));
    }
  }

  await kv.set('__migrated_v1', true);
  // Remove the old copies only after a release or two, once you are confident
  return kv;
}

尽快从旧存储中删除机密信息,因为它们已被安全存储。 在将令牌从"Preferences"迁移到"Keychain"后,仍然会暴露原始令牌。

因为这个code是运行在JavaScript中的,你可以将它与一个 Capgo live update. The SQLite plugin itself is native, so the release that adds it needs a store build. Ship the plugin first, then the migration code.

首选项值

在Web上更新后 null 源地址已更改(端口、主机名或方案),所以 源地址已更改(端口、主机名或方案),所以 localStorage 是一个不同的、空的存储器。

快速 SQL KeyValueStore.open 在 iOS 上抛出异常。 本地 HTTP 通道被阻塞。添加 NSAllowsLocalNetworking 到 Info.plist 重新构建原生应用。

get 返回一个字符串,而您期望在 data-storage-sqlite 中得到一个对象。 该插件仅存储字符串。 JSON.parse 存储

值 在恢复备份到新手机后丢失的密钥。

密钥库密钥和仅在设备上可用的 Keychain 项不会传输到新硬件。将安全存储视为可以重新获取的凭证缓存。您可以通过再次登录来重新获取它们。 预期:在卸载应用程序时,首选项会被删除,Keychain 项可以保留。使用上述的首次运行标志模式。

概要

  • 首选项:小的、非敏感的设置。
  • SQLite 键值存储(快速 SQL) KeyValueStore 或 @capgo/capacitor-data-storage-sqliteSQLite 键值存储(快速 SQL)
  • 或@capgo/capacitor-native-biometric setDataSQLite 键值存储(快速 SQL)

或 SQLite 键值存储(快速 SQL), 或和 原生生物识别插件页面.

实时更新 Capacitor 应用

当 web 层 bug 活跃时,通过 Capgo 直接将修复推送给用户,而不是等待几天的应用商店审批。用户在后台接收更新,而原生变化仍然在正常审批路径中。

最新博客

Capgo 为您提供创建真正专业的移动应用所需的最佳见解。