在 Capacitor 中进行键值存储,请使用 @capacitor/preferences 少量小型设置用 Preferences,更大或更多的数据用基于 SQLite 的键值存储,机密信息用 Keychain 或 Keystore。本指南对比了 Capacitor 8 中可用的选项,展示了每种方案的可用 code,并解释了如何在切换时不丢失用户数据。
大多数应用会同时使用其中两种:用 Preferences 或 SQLite 存储应用状态,用安全存储保存认证 token。
选项概览
localStorage / IndexedDB |
@capacitor/preferences |
快速 SQL KeyValueStore |
@capgo/capacitor-data-storage-sqlite |
@capgo/capacitor-native-biometric setData |
|
|---|---|---|---|---|---|
| 基于 | WebView 存储 | UserDefaults / SharedPreferences | SQLite 文件 | SQLite 文件 | Keychain / Keystore |
| 可抵御系统存储清理 | 无保证 | 是 | 是 | 是 | 是 |
| 值类型 | 字符串 (localStorage) | 字符串 | JSON 值、数字、布尔值、 Uint8Array |
字符串 | 字符串 |
| 合适的尺寸范围 | 小 | 小,每个值几 KB | 大值,多键 | 多键 | 非常小(Android 上约 8 KB 以内) |
| 加密 | 否 | 否 | 可选 SQLCipher,密钥来自安全存储 | 可选 SQLCipher,密码在配置中 | 是,硬件支持 |
| 命名空间 | 按来源 | group 选项 |
store 选项 |
数据表 | 键名 |
| 按前缀查询 | 否 | 否 | 列出键 | filtervalues |
否 |
| Web 端 | 是 | localStorage |
SQLite Wasm(OPFS) | IndexedDB | 不适合存储敏感信息 |
为什么不用 localStorage
localStorage 和 IndexedDB 在 Capacitor WebView 中运行,但数据归属于 WebView,而非应用的本地存储。当设备空间不足时,操作系统可能会回收 WebView 的存储空间,且 Capacitor 的官方文档也不建议将其用于存储不可丢失的数据。更改应用的 capacitor.config 也会改变 origin,导致 WebView 指向另一个空的存储空间。
请将它们视为可随时清除的缓存。其他所有数据请使用基于本地存储的选项。
方案 1:用 Capacitor Preferences 存少量设置
@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) });
}
适用场景:主题、语言、onboarding 标记、最后选中的标签页、功能开关。
请勿用于:
- 机密信息。 值以明文形式存储在应用沙箱中,并包含在设备备份里。
- 大体积数据。
UserDefaults以及SharedPreferences会将整个文件加载到内存中。大型 JSON 数据块会拖慢每次读写操作。 - 需要按前缀列出或清除大量键。
keys()会返回全部内容,因此你需要在 JavaScript 中过滤。
Preferences.configure({ group: 'MyApp' }) 更改键值存储的命名空间。默认分组为 CapacitorStorage.
方案 2:使用 SQLite 键值存储
一旦你开始缓存 API 响应、草稿、离线队列或数百条记录,SQLite 文件会是更好的选择。它能处理大值,写入是原子的,并且可以加密。
高性能 SQL KeyValueStore
@capgo/capacitor-fast-sql 包含一个 KeyValueStore 基于其 SQLite 连接的类。它会创建一个 __kv_store 数据表,保留值类型,并支持在一个数据库中使用多个命名 store。
bun add @capgo/capacitor-fast-sql
bunx cap sync
Fast SQL 需要两个原生设置(iOS 的本地网络配置 Info.plist以及 Android 上的 localhost 明文规则)。它们都包含在 如何在 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,一个数据库。 本指南中介绍的 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
之后,当某个键值对数据膨胀到需要查询时(例如所有超过一周的草稿),你可以将其迁移到同一数据库中的正式表里。
加密的键值存储
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。配置方法以及备份恢复后密钥丢失的处理方式,详见 如何在 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 端,它通过 localforage,需单独安装:
bun add localforage
关于其加密机制: openStore({ encrypted: true, mode: 'secret' }) 使用 SQLCipher,并采用来自 capacitor.config 在……下 plugins.CapgoCapacitorDataStorageSqlite.encryptionSecret的密码。该密码打包在您的应用 bundle 中,若未设置,插件将回退到内置默认值。请将其视为防止随意文件检查的手段,而非对抗解包应用者的防护。对于真正的密钥,请使用 Keychain 或 Keystore 中的密钥,如上文 Fast SQL 示例所示。
选择高性能 SQL KeyValueStore 还是 data-storage-sqlite?
- 选择 快速 SQL
KeyValueStore如果你想要无需手动处理 JSON 的类型化值、二进制值、运行时加密密钥,或者已经在表操作中使用 Fast SQL。 - 选择 Data Storage SQLite 如果你需要它的表和过滤功能、JSON 导出和导入,或者 Electron 支持。
方案 3:使用 Keychain 和 Keystore 存储敏感信息
Auth token、refresh token、API 密钥和数据库加密密钥应存放在平台的安全存储中。 @capgo/capacitor-native-biometric (8.6.0 及更高版本)在 iOS Keychain 和 Android Keystore 之上提供了通用的键值 API:
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 数据库。我们的这篇文章: 离线 token 的安全存储 深入探讨 Token 处理。
请注意,iOS 上的 Keychain 条目在应用卸载后仍可能保留。如果应用需要干净初始状态,请在全新安装后的首次启动时清除残留密钥(在 Preferences 中存一个“首次运行完成”标志,该标志会在卸载时被移除;若该标志不存在,就清除 Keychain 条目)。
选择:决策清单
- 它是敏感信息吗? Token、密码、PIN、密钥。请使用 Keychain 或 Keystore 存储。
- 只是几个小设置吗? 请使用 Preferences。
- 是大量条目、大型 JSON、二进制数据,还是需要批量过期和清除的缓存? 请使用 SQLite key-value 存储。
- 你需要对其进行过滤、排序或关联查询吗? 它已不再是 key-value 数据。请创建一张表,参见 SQLite 指南,或使用类型化层,例如 Kysely.
在不同存储方案间迁移现有数据
从 localStorage 从 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;
}
将密钥复制到安全存储后,立即从旧存储中删除。如果将 token 移至 Keychain 后仍保留在 Preferences 中,原有的暴露风险依然存在。
由于此 code 在 JavaScript 中运行,您可以将其与 Capgo live update。SQLite 插件本身是原生的,因此添加它的版本需要商店构建。先发布插件,然后执行迁移 code。
故障排除
Preferences 的值是 null 在 Web 更新后。 源已更改(端口、主机名或协议),因此 localStorage 是一个不同的、空的存储。
快速 SQL KeyValueStore.open 在 iOS 上抛出异常。 本地 HTTP 通道被阻止。请添加 NSAllowsLocalNetworking 改为 Info.plist 并重新构建原生应用。
get 在 data-storage-sqlite 中,你预期得到对象,却返回了字符串。 该插件只存储字符串。 JSON.parse 该值。
在新手机上恢复备份后,Secret 丢失。 Keystore 密钥和仅限设备的 Keychain 项不会迁移到新硬件。请将安全存储视为凭据缓存,重新登录即可重新获取。
在 iOS 上重装后设置丢失,但 token 还在。 预期:Preferences 会随应用一起删除,Keychain 项可能保留。请使用上面的首次运行标志模式。
总结
- Preferences:轻量级、非敏感的设置。
- SQLite 键值存储 (Fast SQL
KeyValueStore或@capgo/capacitor-data-storage-sqlite): 大量或大型值、缓存、草稿,可选加密。 - Keychain 或 Keystore(
@capgo/capacitor-native-biometricsetData):用于存储密钥和加密密钥。
插件参考: Fast SQL 文档, data-storage-sqlite 文档,以及 原生生物识别插件页面.