Flutter for OpenHarmony 实战:Shared Preferences 轻量级数据存储全指南
摘要:本文介绍了在Flutter for OpenHarmony中使用SharedPreferences进行轻量级数据存储的完整指南。首先讲解了鸿蒙平台特有的环境配置方法,包括从AtomGit引入适配包。然后详细演示了基础CRUD操作,涵盖数据读写和删除等核心功能。最后通过构建强类型的PreferencesService单例,展示了企业级封装方案,有效解决了Magic String问题,提升了代码
Flutter for OpenHarmony 实战:Shared Preferences 轻量级数据存储全指南
前言
在移动应用开发中,我们经常需要存储一些轻量级的用户数据,比如用户是否登录、应用的主题颜色设置、用户的语言偏好等。在 Flutter 生态中,shared_preferences 是处理此类需求的首选插件。
随着 Flutter for OpenHarmony 的不断成熟,我们也可以在鸿蒙设备上流畅使用这一能力。本文将带大家从零开始,在 OpenHarmony 项目中集成、使用并封装 shared_preferences,同时深入探讨它与前端存储方案的异同,通过企业级封装实战提升你的代码质量。
一、 环境配置与依赖安装
在 OpenHarmony 平台上使用 Flutter 插件,最大的不同在于依赖源的配置。由于鸿蒙社区的适配包通常还在快速迭代中,我们往往需要直接指向 AtomGit 上的源码仓库。
1.1 引入鸿蒙适配包


我们需要用到 OpenHarmony TPC (Third Party Components) 提供的适配版本。该版本基于 ohos.data.preferences 原生能力实现,确保了数据读写的高效与安全。
请打开项目根目录下的 pubspec.yaml 文件,添加如下配置:
dependencies:
flutter:
sdk: flutter
# 1. 引入官方标准包(用于提供统一的 API 接口)
shared_preferences: ^2.2.0
# 2. 引入 OpenHarmony 平台适配实现
shared_preferences_ohos:
git:
url: https://atomgit.com/openharmony-tpc/flutter_packages.git
path: packages/shared_preferences/shared_preferences_ohos
📌 注意:
url指向了 AtomGit 上的flutter_packages聚合仓库,这是一个 Monorepo,包含了多个官方插件的鸿蒙适配版。path必须精确指向packages/shared_preferences/shared_preferences_ohos子目录,否则 Pub 工具将无法找到pubspec.yaml。
1.2 权限配置(可选)
对于简单的数据存储,OpenHarmony 的首选项(Preferences)通常不需要额外的敏感权限。但如果你的应用涉及跨设备数据同步等高级功能,或者需要存储敏感的用户隐私数据,建议查阅鸿蒙官方文档确认 module.json5 中的配置。对于当前的基础使用,无需额外配置权限,即插即用。
二、 基础实战:CRUD 操作详解

SharedPreferences 的操作非常直观,它基于 Key-Value(键值对)模型。底层在鸿蒙系统上映射为 XML 或二进制文件,适合存储配置类的小数据。
2.1 获取实例与写入数据

所有的写操作都是异步的(Future),建议配合 await 使用,以确保数据已成功落盘。
import 'package:shared_preferences/shared_preferences.dart';
Future<void> saveUserSettings() async {
// 1. 获取实例(单例模式,内部会自动缓存)
final SharedPreferences prefs = await SharedPreferences.getInstance();
// 2. 写入不同类型的数据
await prefs.setInt('counter', 10); // 整数
await prefs.setBool('is_dark_mode', true); // 布尔值
await prefs.setDouble('font_scale', 1.5); // 浮点数
await prefs.setString('user_name', 'OpenHarmony Dev'); // 字符串
await prefs.setStringList('tags', ['Flutter', 'HarmonyOS']); // 字符串列表
print('✅ 数据保存成功!');
}
2.2 读取数据
读取操作是同步的,这是因为 getInstance 时插件已通过 MethodChannel 将磁盘数据一次性加载到了内存中。
Future<void> loadUserSettings() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// 读取数据,如果 key 不存在,返回 null
final int? counter = prefs.getInt('counter');
// 💡 技巧:使用 ?? 提供默认值,防止空指针异常
final bool isDarkMode = prefs.getBool('is_dark_mode') ?? false;
print('Counter: $counter, Dark Mode: $isDarkMode');
}
2.3 删除与清空
Future<void> clearData() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// 删除单个 Key
await prefs.remove('user_name');
// 🔥 慎用:清空所有数据(通常仅在“退出登录且清除缓存”时使用)
await prefs.clear();
}
三、 进阶实战:打造强类型的配置管理服务
在真实的大型项目中,到处写 prefs.getString('key') 既容易拼写错误(Magic String),又难以维护。下面我们通过一个 单例模式 的 StorageService 来封装这些逻辑,实现代码的解耦。
3.1 封装 PreferencesService
import 'package:shared_preferences/shared_preferences.dart';
class PreferencesService {
// 私有构造函数,防止外部误实例化
PreferencesService._();
static final PreferencesService _instance = PreferencesService._();
static PreferencesService get instance => _instance;
late SharedPreferences _prefs;
// 初始化方法,通常在 main.dart 中调用
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
// --- 定义所有的 Key 常量 ---
static const String _kIsDarkMode = 'app_theme_mode';
static const String _kLanguageCode = 'app_language';
static const String _kUserToken = 'user_auth_token';
// --- 强类型的 Getter/Setter ---
// 主题设置:自动处理空值
bool get isDarkMode => _prefs.getBool(_kIsDarkMode) ?? false;
set isDarkMode(bool value) => _prefs.setBool(_kIsDarkMode, value);
// 语言设置:提供默认值 'zh'
String get languageCode => _prefs.getString(_kLanguageCode) ?? 'zh';
set languageCode(String value) => _prefs.setString(_kLanguageCode, value);
// 用户 Token:包含业务逻辑(存 null 即删除)
String? get userToken => _prefs.getString(_kUserToken);
Future<void> setUserToken(String? token) async {
if (token == null) {
await _prefs.remove(_kUserToken);
} else {
await _prefs.setString(_kUserToken, token);
}
}
}
3.2 在 App 中使用
首先在 main.dart 初始化:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 等待初始化完成,确保后续同步读取不会报错
await PreferencesService.instance.init();
runApp(const MyApp());
}
在业务页面中调用:
// 读取
bool isDark = PreferencesService.instance.isDarkMode;
// 写入 - 就像操作普通变量一样简单,且带有类型检查
PreferencesService.instance.isDarkMode = !isDark;
这种封装方式极大地提高了代码的可读性和健壮性,是商业级项目的标配。
四、 技术对标:Shared Preferences vs LocalStorage vs Cookies
对于从 Web 前端(Vue/React)转到 Flutter for OpenHarmony 的开发者,理解这几种存储方式的区别至关重要。
| 特性 | Shared Preferences (Flutter/Native) | LocalStorage (Web) | Cookies (Web) |
|---|---|---|---|
| 鸿蒙底层 | 用户首选项 (Preferences) | Webview 内核存储 | Webview 内核存储 |
| 数据类型 | int, double, bool, string, List | 仅 String | 仅 String |
| 网络传输 | 不随请求发送 | 不随请求发送 | 每次请求自动携带 |
| 访问速度 | 内存缓存 (极快) | 磁盘 IO (较快) | 磁盘 IO (较慢) |
4.1 核心差异深度剖析
(1)纯净性与安全性
SP 是纯粹的本地存储,不会像 Cookie 一样自动附加到 HTTP 请求头中。这意味着它更适合存储 UI 状态(如深色模式)。如果你需要实现“保持登录”,需要手动从 SP 读取 Token 并添加到 Dio 的 Header 中,这反而增加了 CSRF 攻击的防护能力。
(2)类型安全优势
SP 原生支持基础数据类型。在 LocalStorage 中存储布尔值 true 往往变成字符串 "true",在读取时 if ("false") 依然为真,极易导致 Bug。而 SP 省去了手动 JSON.parse 的过程。
(3)鸿蒙底层机制
在 OpenHarmony 上,SP 底层映射为 ohos.data.preferences。它针对 JS UI 框架和 ArkUI 进行了深度优化,采用内存缓存 + 异步刷盘机制,且支持对数据变化的监听(虽然 Flutter 插件层暂时封装了由 Dart 侧管理),性能远高于简单的文件 IO。
五、 核心避坑指南 (Best Practices)
在实际开发中,不当使用 Shared Preferences 可能会导致卡顿或数据丢失。
5.1 避免在 build 方法中 await
build() 方法应该是纯同步且快速的。
- ❌ 错误:在
build中调用await prefs.setInt(...)。 - ✅ 正确:在
onTap回调或initState生命周期中处理异步逻辑。
5.2 避免存储巨型 JSON
虽然 SP 可以存储 String,但它不是数据库。
- ❌ 错误:将几万条聊天记录 JSON 序列化后存入 SP。这会阻塞 UI 线程(因为加载是同步的)。
- ✅ 正确:对于大量结构化数据,请使用
sqflite或鸿蒙原生的 RDB (Relational Database)。
5.3 单元测试中的 Mock
编写单元测试时,真正的 SP 无法在非真机环境运行。
- ✅ 推荐:使用
SharedPreferences.setMockInitialValues({})来注入测试数据。
test('测试 Token 读取', () async {
SharedPreferences.setMockInitialValues({
'user_auth_token': 'mock_token_123'
});
final prefs = await SharedPreferences.getInstance();
expect(prefs.getString('user_auth_token'), 'mock_token_123');
});
六、 完整示例:鸿蒙计数器 (持久化版)
下面是一个完整的计数器示例,展示了如何在鸿蒙设备上实现数据的持久化保存。
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MaterialApp(home: PersistentCounterPage()));
}
class PersistentCounterPage extends StatefulWidget {
const PersistentCounterPage({super.key});
State<PersistentCounterPage> createState() => _PersistentCounterPageState();
}
class _PersistentCounterPageState extends State<PersistentCounterPage> {
int _counter = 0;
final String _keyCounter = 'counter_value';
void initState() {
super.initState();
_loadCounter(); // 初始化时加载数据
}
// 📥 读取数据
Future<void> _loadCounter() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_counter = prefs.getInt(_keyCounter) ?? 0;
});
}
// 💾 保存数据
Future<void> _incrementCounter() async {
final prefs = await SharedPreferences.getInstance();
final newValue = _counter + 1;
// UI 更新
setState(() {
_counter = newValue;
});
// 持久化保存
await prefs.setInt(_keyCounter, newValue);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('鸿蒙持久化计数器')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('你已经点击了这么多次:', style: TextStyle(fontSize: 18)),
const SizedBox(height: 10),
Text(
'$_counter',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: Colors.blue
),
),
const SizedBox(height: 30),
const Text(
'即使重启应用,数字也不会丢失哦!',
style: TextStyle(color: Colors.grey),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: '增加',
child: const Icon(Icons.add),
),
);
}
}

七、 总结
shared_preferences 是 Flutter for OpenHarmony 开发中不可或缺的基础设施。通过引入 shared_preferences_ohos 适配包,我们可以无缝地将现有的 Flutter 经验迁移到鸿蒙生态中。
相比于 Web 前端的 LocalStorage,SP 提供了更规范的类型支持和更高效的底层实现。在实战中,推荐大家使用单例模式进行二次封装,以保持代码的整洁和可维护性。
🌐 欢迎加入开源鸿蒙跨平台社区:开源鸿蒙跨平台开发者社区
更多推荐


所有评论(0)