shared_preferences 演进全史:从 0.1.0 到 2.5.5 的 API 架构、平台联邦化与迁移实践
2026/9/19 10:19:25 网站建设 项目流程

shared_preferences 演进全史:从 0.1.0 到 2.5.5 的 API 架构、平台联邦化与迁移实践

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

本篇文章以 shared_preferences 的 CHANGELOG 为时间轴主线,系统梳理 Flutter 团队官方维护的shared_preferences插件从 2017 年开源初版到 2.5.5 的完整演进历程。通过对照当前仓库中的真实源码与配置文件,读者可以理解其从单体插件到联邦化(federated)架构的转变、SharedPreferences旧 API 与SharedPreferencesAsync/SharedPreferencesWithCache新 API 的取舍逻辑、setPrefixallowList过滤机制、Android 安全更新与存储后端切换,以及如何借助迁移工具与 DevTools 扩展平滑升级存量代码。

一、插件定位:为“简单键值对”而生的官方持久化方案

shared_preferences是 Flutter 官方维护的持久化插件,负责在移动端与桌面端封装平台原生的简单键值存储。根据其 pubspec.yaml 的描述:

Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android.

在 Android 上底层是SharedPreferences(新版 API 默认改为 DataStore Preferences),在 iOS/macOS 上是NSUserDefaults,Web 上是LocalStorage,Linux 上是XDG_DATA_HOME目录,Windows 上是 roaming AppData 目录。支持的数据类型固定为五种:intdoubleboolStringList<String>

一个重要的使用前提写在其 README.md 中:数据是异步落盘的,写入方法返回后不保证已经持久化到磁盘,因此该插件不得用于存储关键数据。这一点从 CHANGELOG 中0.5.2+1的“.commit()调用在 Android 上改为异步后台任务执行”也能得到印证——写盘始终是异步、尽力而为的。

二、CHANGELOG 揭示的核心演进主线

这份 CHANGELOG 从0.1.0(Initial Open Source release)一直记录到2.5.5,其演进主线可以归纳为四条:API 现代化、架构联邦化、平台支持扩展、稳定性与安全加固。下面逐条展开。

2.1 早期 API 打磨(0.1.0 – 0.5.x):从“能用”到“好用”

早期版本(0.1.0 到 0.5.x)主要是功能补齐与 Android 工具链适配:

  • 0.2.0:升级到新的插件注册机制(plugin registration),从 Flutter 1.x 早期的手动注册走向自动注册。
  • 0.2.1setInt支持任意长度整数。
  • 0.2.2破坏性变更setStringSetAPI 改为setStringList,并支持有序存储。
  • 0.2.4:新增setMockInitialValues,为单元测试提供注入 mock 数据的能力;该能力在0.5.3+3中进一步支持重复调用并自动reload()单例。
  • 0.2.5:修复设置 null 值导致崩溃的问题——现在 set 一个 null 值会触发 key 被移除;同时新增remove()方法。
  • 0.3.2:新增get泛型 getter,可读取任意类型的值。
  • 0.4.1:新增getKeys()方法,返回存储中所有 key。
  • 0.5.2:新增containsKey()方法。
  • 0.5.3:新增reload()方法,用于重新从平台读取最新数据——这是解决“原生代码修改了底层存储而 Dart 侧缓存未同步”的关键手段。
  • 0.5.1:Android 上 double 改为以字符串形式存储(避免精度问题)。

这些能力在今天的 shared_preferences_legacy.dart 中依然可见:getKeys()containsKey()reload()setMockInitialValues()都是SharedPreferences类的核心成员。其中getStringList2.3.1还修复过一个List<Object?>强转异常,2.2.1修复过单例初始化竞态条件——这类细节正是“持久化插件”最容易踩坑的地方。

2.2 架构转型:从单体插件到联邦化(Federated)插件(2.0.x 时代)

2.0.x系列是本插件最重要的架构分水岭:

  • 2.0.9:Android 与 iOS 实现被拆分到独立的联邦化包(federated packages)。
  • 2.0.16:iOS/macOS 切换到新的shared_preferences_foundation实现包。
  • 2.0.10:移除 Windows/Linux 实现的旧式手动注册。

联邦化之后,主包shared_preferences只剩 API 层,平台实现由独立包提供。这一点可以从当前 pubspec.yaml 的flutter.plugin.platforms配置一览无余:

flutter: plugin: platforms: android: default_package: shared_preferences_android ios: default_package: shared_preferences_foundation linux: default_package: shared_preferences_linux macos: default_package: shared_preferences_foundation web: default_package: shared_preferences_web windows: default_package: shared_preferences_windows

六个平台(Android、iOS、Linux、macOS、Web、Windows)各自对应独立的 endorsed(官方背书)实现包,Dart 侧通过shared_preferences_platform_interface抽象层解耦。主包 lib/shared_preferences.dart 仅做三件事:导出SharedPreferencesOptions类型、导出 async 新 API、导出 legacy 旧 API。

2.3 破坏性变更:Null Safety 迁移(2.0.0)

2.0.0完成空安全(null-safety)迁移,并带有一个重要的破坏性变更:

Setters no longer accept null to mean removing values. If you were previously usingset*(key, null)for removing, useremove(key)instead.

也就是说,2.0.0 之后不能再通过setString(key, null)来删除值,必须显式调用remove(key)。这一点在今天的源码中仍然强制:_setValue方法第一行就是ArgumentError.checkNotNull(value, 'value')(见 shared_preferences_legacy.dart)。

同期的其他稳定性修复包括:2.0.2修复方法通道调用时重复创建线程池的问题、2.0.3修复 Android 上重复创建 Handler 的问题、2.0.4修复 Android 同时写入的回归问题——这些都属于典型的“高频调用 + 平台通道”性能与并发陷阱。

2.4 新 API 时代:SharedPreferencesAsync 与 SharedPreferencesWithCache(2.3.0+)

2.3.0是本插件在功能层面最重要的一次发布,新增了两套全新 API:

  • SharedPreferencesAsync:无本地缓存,所有读写都是异步的平台调用,始终返回平台上的最新数据。
  • SharedPreferencesWithCache:带内存缓存,初始化时一次性加载数据,之后 getter 全部同步执行,适合对读性能敏感的场景。

从源码可以清晰看到两者设计意图的差异。SharedPreferencesAsync(见 shared_preferences_async.dart)所有方法返回Future,直接委托给SharedPreferencesAsyncPlatform;而SharedPreferencesWithCache内部持有一个Map<String, Object?> _cache,创建时通过reloadCache()从平台拉取数据,getter 直接读缓存(同步),setter 则“先写缓存、再异步落盘”。

缓存方案的核心风险在 README 中被明确列出(README.md):

  • 多 isolate 使用:每个 isolate 有各自的单例与缓存;
  • 多引擎实例(包括firebase_messaging等插件创建的后台 context);
  • 通过原生代码直接修改底层存储。

这些场景下缓存可能与实际存储不一致。解决方案是:读之前调用reloadCache()/reload();如果绝大多数读取都需要 reload,则直接改用SharedPreferencesAsync

2.4.1 allowList:把过滤权交给开发者

新 API 引入了allowList(白名单)机制。SharedPreferencesAsyncgetKeysgetAllclear都接受可选allowList参数;SharedPreferencesWithCacheOptions则在创建时通过allowList限定可缓存、可读写的 key 集合。

源码注释给出了明确语义(见 shared_preferences_async.dart):

  • nullallowList:不过滤,缓存所有条目;
  • 空 allowList:禁止一切缓存、读取与写入;
  • 强烈建议设置 allowList,以避免读取和缓存到非预期数据。

clear()方法特别危险:SharedPreferencesAsync.clear()在不带allowList时会清掉平台上所有偏好项,包括原生代码或其他包写入的数据,因此官方源码注释明确建议调用时务必提供allowList

SharedPreferencesWithCache还实现了“超出白名单即抛异常”的守卫逻辑:containsKeyget、各类 getter/setter 都会先调用_isValidKey检查,key 不在allowList中则抛出ArgumentError(见 shared_preferences_async.dart)。

2.5 前缀机制:setPrefix 与 allowList 的组合(2.1.0 – 2.4.0)

2.1.0新增setPrefix2.2.0为其增加allowList选项,2.4.0又补充了“更新前缀后 allowList 处理”的说明注释,2.3.3澄清了 README 中前缀处理的范围。

默认情况下,SharedPreferences只读写以flutter.开头的 key,这个前缀由插件内部自动处理,开发者无需手动拼前缀(见 shared_preferences_legacy.dart)。

setPrefix的使用要点(源码注释 + README 双重确认):

  • 必须在getInstance()之前调用,调用getInstance()之后再调setPrefix会抛StateError
  • 前缀设为''可访问所有非 Flutter 写入的偏好(常用于从原生 App 迁移到 Flutter 的场景);
  • 前缀设为''后可能读到类型不兼容的值导致初始化失败,此时应配合allowList只保留受支持类型的 key;
  • 若要从旧前缀迁移到新前缀,需要手动改写现有偏好数据,setPrefix本身不做迁移;
  • 使用 allowList 时,allowList 中的 key 必须包含前缀本身。

从实现看,_getSharedPreferencesMap()_prefixHasBeenChanged时会走getAllWithParameters(携带PreferencesFilter(prefix, allowList)),随后把带前缀的 key 剥离出来返回(见 shared_preferences_legacy.dart)。如果底层实现不支持setPrefix,还会抛出带有明确提示的UnimplementedError

2.6 迁移工具:legacy → async 的官方通道(2.4.0)

2.4.0新增官方迁移工具,帮助开发者从旧SharedPreferences平滑迁移到SharedPreferencesAsync。该工具位于 legacy_to_async_migration_util.dart,核心函数为:

Future<void> migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary({ required SharedPreferences legacySharedPreferencesInstance, required SharedPreferencesOptions sharedPreferencesAsyncOptions, required String migrationCompletedKey, })

迁移逻辑(对应 README 中的示例,见 README.md):

  1. 检查目标系统中migrationCompletedKey是否存在,存在则直接返回(幂等,可每次启动都调用);
  2. 对 legacy 实例执行reload(),取回全部 key;
  3. runtimeType分派:bool/int/double/String/List<String>逐一写入 async 实例;
  4. 处理List<String?>List<Object?>List<dynamic>等变体,遇到含非 String 元素的列表会捕获TypeError跳过;
  5. 最后写入migrationCompletedKey: true标记迁移完成。

使用该工具需要保证migrationCompletedKey不与业务 key 冲突,否则可能造成数据丢失。如果应用之前调用过setPrefix,必须在迁移前完成前缀设置。

2.7 DevTools 扩展:可视化调试(2.5.0)

2.5.0为 shared_preferences 新增 DevTools 扩展,2.5.4又更新了shared_preferences_tool的依赖并修复相关弃用问题。

DevTools 扩展的能力(见 shared_preferences_tool/README.md):

  • 列出应用中存储的所有 key;
  • 搜索特定 key;
  • 直接编辑或删除值,改动即时反映到运行中的应用;
  • 支持全部五种数据类型:StringintdoubleboolList<String>

数据层由 shared_preferences_devtools_extension_data.dart 提供:通过developer.postEventshared_preferences.前缀事件与 DevTools 通信,事件类型包括all_keysvaluechange_valueremove。值得注意的实现细节:requestValueChange中先jsonDecode再按kind分派,注释明确指出因为 double 有时会被解析成 int,所以必须校验 kind 而不是直接模式匹配——这是序列化边界上非常典型的坑。

本地运行该扩展的方式:先运行shared_preferences包的 example 工程并拷贝 debug service URL,然后执行:

flutter run -d chrome --dart-define=use_simulated_environment=true

三、平台支持与支持矩阵的演变

CHANGELOG 清晰记录了各平台“默认支持”的里程碑:

  • 0.5.5:macOS 默认支持(0.5.4+10新增shared_preferences_macos包);
  • 0.5.6:Web 默认支持(0.5.4+7为 Web 支持重构了项目结构,0.5.4+8切换到底层使用shared_preferences_platform_interface);
  • 0.5.8:Linux 默认支持;
  • 0.5.11:Windows 默认支持。

版本演进也伴随着系统版本要求的变化:2.2.3宣布 iOS 11 不再支持,2.1.2将 macOS 最低版本提升到 10.14,2.2.3要求 iOS 实现包含隐私清单(privacy manifest)。当前的支持矩阵(README.md):

平台支持版本
AndroidSDK 24+
iOS13.0+
Linux任意
macOS10.15+
Web任意
Windows任意

2.5.4特别说明:README 反映的是“最新版本 endorsed 平台实现”的支持情况,使用旧版本 Flutter 构建的应用仍可继续使用与之兼容的旧版本平台实现

四、Android 存储后端:从 SharedPreferences 到 DataStore(2.5.x)

2.3.5增加了 Android SharedPreferences 支持的相关说明,2.3.4是一次安全更新(强制要求shared_preferences_android升级到 2.3.4)。

在新 API 体系下,Android 存储后端可选:

  • DataStore Preferences:默认选项,也是平台官方推荐的偏好存储方案;
  • Android SharedPreferences:用于兼容“由不受你控制的代码写入的 SharedPreferences 数据”。

如果需要切换到 SharedPreferences 后端,使用SharedPreferencesAsyncAndroidOptions指定:

const SharedPreferencesAsyncAndroidOptions options = SharedPreferencesAsyncAndroidOptions( backend: SharedPreferencesAndroidBackendLibrary.SharedPreferences, originalSharedPreferencesOptions: AndroidSharedPreferencesStoreOptions( fileName: 'the_name_of_a_file', ), );

而旧的SharedPreferencesAPI 则固定使用原生 Android SharedPreferences 存储。各平台的存储位置对照(README.md):

平台SharedPreferencesSharedPreferencesAsync/WithCache
AndroidSharedPreferencesDataStore Preferences 或 SharedPreferences
iOSNSUserDefaultsNSUserDefaults
LinuxXDG_DATA_HOME 目录XDG_DATA_HOME 目录
macOSNSUserDefaultsNSUserDefaults
WebLocalStorageLocalStorage
Windowsroaming AppData 目录roaming AppData 目录

五、API 速查与选型建议

5.1 三套 API 的代码形态对比

旧 API(legacy,同步 getter + 缓存):

final SharedPreferences prefs = await SharedPreferences.getInstance(); // 写入 await prefs.setInt('counter', 10); await prefs.setBool('repeat', true); await prefs.setDouble('decimal', 1.5); await prefs.setString('action', 'Start'); await prefs.setStringList('items', <String>['Earth', 'Moon', 'Sun']); // 读取(同步,走本地缓存,不存在返回 null) final int? counter = prefs.getInt('counter'); final bool? repeat = prefs.getBool('repeat'); // 删除 await prefs.remove('counter');

新 API 之一(async,无缓存,全部异步):

final asyncPrefs = SharedPreferencesAsync(); await asyncPrefs.setBool('repeat', true); final bool? repeat = await asyncPrefs.getBool('repeat'); await asyncPrefs.remove('repeat'); // 强烈建议 clear 时带 allowList,避免清掉非本实例写入的数据 await asyncPrefs.clear(allowList: <String>{'action', 'repeat'});

新 API 之二(with cache,同步 getter):

final SharedPreferencesWithCache prefsWithCache = await SharedPreferencesWithCache.create( cacheOptions: const SharedPreferencesWithCacheOptions( allowList: <String>{'repeat', 'action'}, ), ); await prefsWithCache.setBool('repeat', true); final bool? repeat = prefsWithCache.getBool('repeat'); // 同步读缓存 await prefsWithCache.remove('repeat'); await prefsWithCache.clear();

5.2 选型决策要点

  • 新项目一律优先SharedPreferencesAsyncSharedPreferencesWithCache:README 明确说明旧SharedPreferences是 legacy API,未来会被废弃;
  • 读写频率高、读路径敏感 → 选SharedPreferencesWithCache(同步 getter),但需接受缓存一致性问题;
  • 数据可能被原生代码 / 其他 isolate / 其他引擎修改 → 选SharedPreferencesAsync(始终读最新数据);
  • 对任何clear/ 批量读取调用,优先提供allowList以规避副作用;
  • 存量代码迁移使用migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary,注意migrationCompletedKey的选择。

六、版本演进中的工程实践启示

从这份 CHANGELOG 中还能提炼出 Flutter 官方插件工程的几项长期实践:

  1. 破坏性变更集中在大版本(0.2.2、0.3.0、0.4.0、0.5.0、2.0.0),且每次都有明确说明与迁移指引,例如 0.5.0 的 AndroidX 迁移、2.0.0 的 null-safety 与remove()语义调整;
  2. 安全与合规持续跟进:2.3.4 安全更新、2.2.3 隐私清单、0.5.0 AndroidX、0.5.2+1 后台异步提交,反映了对平台政策变化的响应;
  3. 测试基础设施同步演进:0.2.4 引入setMockInitialValues与首个测试、0.5.1+2 增加 driver 测试、0.5.13 迁移到testWidgets、2.0.7 增加 iOS 单元测试目标——当前仓库的 test 目录 中shared_preferences_test.dartshared_preferences_async_test.dartshared_preferences_devtools_extension_data_test.dart三个测试文件正是这一积累的产物;
  4. SDK 约束随 Flutter 版本滚动:从 0.4.0 的 beta 约束、0.5.6 的 Flutter 1.12、2.0.18 的 Flutter 3.0,一路到 NEXT 版本的 Flutter 3.38/Dart 3.10,每个里程碑都同步更新environment约束(当前为sdk: ^3.10.0flutter: ">=3.38.0")。

七、总结

通过 CHANGELOG 这条时间轴可以完整看到shared_preferences从一个简单的读写封装,逐步成长为拥有联邦化架构、双新 API、白名单过滤、官方迁移工具与 DevTools 可视化调试的成熟插件。对于开发者而言,最重要的实操结论有三点:新代码选择SharedPreferencesAsync/SharedPreferencesWithCache并善用allowList;存量代码借助迁移工具平滑升级;涉及多进程或多引擎写入时优先使用无缓存 API 或显式 reload。相关源码、测试与配置均可在本仓库的 packages/shared_preferences 目录下继续深入阅读。

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询