Flutter 三方库 shared_preferences 的 OpenHarmony 鸿蒙化适配实践
摘要:本文介绍了Flutter三方库shared_preferences在OpenHarmony平台的适配实践。详细讲解了从环境配置、项目初始化到依赖集成的完整流程,提供了键值对存储的核心API封装示例和业务场景应用方法。文章包含StorageManager工具类实现和UserPreferences业务封装示例,帮助开发者快速实现本地数据持久化功能。通过这份实践指南,开发者可以轻松将shared_
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
Flutter 三方库 shared_preferences 的 OpenHarmony 鸿蒙化适配实践
引言
在移动应用开发中,本地数据持久化是一个基础且核心的需求。shared_preferences作为Flutter生态中最流行的键值对存储库,因其简单易用的API设计而被广泛应用于用户配置、缓存数据、状态保存等场景。随着OpenHarmony生态的快速发展,如何将shared_preferences库适配到鸿蒙平台成为了众多跨平台开发者关注的重点。本文将详细介绍在Flutter-OH项目中集成shared_preferences的完整流程,包括环境配置、依赖集成、代码实现、平台适配以及常见问题解决方案,为开发者提供一份可直接参考的实践指南。
一、环境准备与项目初始化
在开始shared_preferences的鸿蒙化适配之前,需要确保开发环境满足基本要求。虽然具体的版本号会随着技术发展而变化,但核心的环境配置原则是一致的。开发者应当关注Flutter与OpenHarmony SDK的兼容性,确保两者能够协同工作。
1.1 核心环境检查要点
无论使用哪个具体版本,以下几个核心检查点都是必不可少的:
-
Flutter环境完整性:确保Flutter SDK已正确安装且路径配置无误。可以通过
flutter doctor命令全面检查开发环境状态,包括Flutter、Dart、设备连接等各个方面。 -
OpenHarmony SDK配置:确认DevEco Studio中已安装对应版本的OpenHarmony SDK,并且环境变量配置正确。特别是对于鸿蒙设备调试,需要确保USB调试权限已开启。
-
项目结构验证:创建Flutter-OH项目后,检查项目目录结构是否完整。标准的Flutter-OH项目应包含
lib/、ohos/、android/、ios/等目录,其中ohos/目录专门用于存放鸿蒙平台的原生代码和配置。 -
依赖管理工具:确保pub包管理器工作正常,能够从pub.dev正确拉取shared_preferences等依赖包。在国内网络环境下,可能需要配置镜像源以提高下载速度。
1.2 创建与验证Flutter-OH项目
使用Flutter命令行工具创建支持OpenHarmony的新项目是标准流程,但创建后的验证同样重要:
flutter create --platforms=ohos flutter_shared_preferences_oh
cd flutter_shared_preferences_oh
项目创建完成后,建议执行以下验证步骤:
-
编译检查:运行
flutter analyze检查代码质量,确保没有明显的语法错误或潜在问题。 -
平台支持验证:检查
pubspec.yaml中的平台配置,确认ohos平台已正确添加。同时查看ohos/目录下的配置文件,特别是entry/src/main/module.json5,确保应用的基本信息配置正确。 -
依赖解析测试:执行
flutter pub get后,检查.packages和pubspec.lock文件,确认shared_preferences及其依赖已正确解析和下载。 -
最小化运行测试:尝试构建一个最简单的Hello World应用并在鸿蒙设备或模拟器上运行,验证整个开发链路是否通畅。
1.3 环境问题排查指南
在实际开发中,可能会遇到各种环境配置问题。以下是一些常见问题的排查思路:
-
Flutter版本不兼容:如果遇到编译错误,首先检查Flutter版本是否满足OpenHarmony适配的最低要求。可以通过
flutter channel切换稳定版或特定版本。 -
鸿蒙设备连接失败:确保设备开发者选项已开启,USB调试权限已授权。对于模拟器,检查DevEco Studio中的模拟器实例是否正常运行。
-
依赖包下载缓慢或失败:考虑配置国内镜像源,或使用VPN访问国际网络。也可以尝试清除pub缓存后重新获取:
flutter pub cache clean。 -
原生代码编译错误:检查
ohos/目录下的原生代码,特别是C++层与Dart层的接口对接是否正确。shared_preferences作为官方插件,其鸿蒙适配层通常比较稳定,但仍需关注版本兼容性。
通过以上系统化的环境准备和验证,可以为后续的shared_preferences集成打下坚实基础。良好的开发环境配置不仅能提高开发效率,还能减少因环境问题导致的调试时间。thod Channel)与各平台的原生实现进行通信。对于OpenHarmony平台,该库已经提供了初步支持,但开发者仍需关注平台特定的细节问题。
2.2 获取依赖
运行以下命令下载并安装依赖包:
flutter pub get
命令执行成功后,shared_preferences及其依赖项将被添加到项目中。
三、shared_preferences 在 OpenHarmony 上的适配实践
3.1 基础使用方法
shared_preferences的核心API设计简洁明了,主要包含以下几类操作:
- 获取实例:通过
getInstance()异步方法获取SharedPreferences单例 - 存储数据:支持String、int、double、bool、List等多种数据类型
- 读取数据:通过对应的get方法读取指定key的值
- 删除数据:移除指定key的数据或清空所有数据
下面是一个完整的工具类封装示例:
import 'package:shared_preferences/shared_preferences.dart';
class StorageManager {
static final StorageManager _instance = StorageManager._internal();
factory StorageManager() => _instance;
StorageManager._internal();
Future<SharedPreferences> get _prefs async =>
await SharedPreferences.getInstance();
// 保存字符串
Future<void> setString(String key, String value) async {
final prefs = await _prefs;
await prefs.setString(key, value);
}
// 获取字符串
Future<String?> getString(String key, [String? defaultValue]) async {
final prefs = await _prefs;
return prefs.getString(key) ?? defaultValue;
}
// 保存整数
Future<void> setInt(String key, int value) async {
final prefs = await _prefs;
await prefs.setInt(key, value);
}
// 获取整数
Future<int?> getInt(String key, [int? defaultValue]) async {
final prefs = await _prefs;
return prefs.getInt(key) ?? defaultValue;
}
// 保存布尔值
Future<void> setBool(String key, bool value) async {
final prefs = await _prefs;
await prefs.setBool(key, value);
}
// 获取布尔值
Future<bool?> getBool(String key, [bool? defaultValue]) async {
final prefs = await _prefs;
return prefs.getBool(key) ?? defaultValue;
}
// 保存字符串列表
Future<void> setStringList(String key, List<String> value) async {
final prefs = await _prefs;
await prefs.setStringList(key, value);
}
// 获取字符串列表
Future<List<String>?> getStringList(String key, [List<String>? defaultValue]) async {
final prefs = await _prefs;
return prefs.getStringList(key) ?? defaultValue;
}
// 删除指定key
Future<void> remove(String key) async {
final prefs = await _prefs;
await prefs.remove(key);
}
// 清空所有数据
Future<void> clear() async {
final prefs = await _prefs;
await prefs.clear();
}
// 检查key是否存在
Future<bool> containsKey(String key) async {
final prefs = await _prefs;
return prefs.containsKey(key);
}
}
3.2 实际业务场景封装
在真实的应用开发中,通常会基于StorageManager进一步封装业务相关的存储功能。例如用户偏好设置、应用配置、缓存管理等。下面是一个用户配置管理类的示例:
import 'storage_manager.dart';
class UserPreferences {
static final UserPreferences _instance = UserPreferences._internal();
factory UserPreferences() => _instance;
UserPreferences._internal();
final StorageManager _storage = StorageManager();
static const String _keyUsername = 'username';
static const String _keyDarkMode = 'dark_mode';
static const String _keyFontSize = 'font_size';
static const String _keyTodoList = 'todo_list';
// 用户名相关操作
Future<void> setUsername(String username) async {
await _storage.setString(_keyUsername, username);
}
Future<String> getUsername() async {
return await _storage.getString(_keyUsername, '访客');
}
// 暗黑模式相关操作
Future<void> setDarkMode(bool enabled) async {
await _storage.setBool(_keyDarkMode, enabled);
}
Future<bool> getDarkMode() async {
return await _storage.getBool(_keyDarkMode, false);
}
// 字体大小相关操作
Future<void> setFontSize(double size) async {
await _storage.setDouble(_keyFontSize, size);
}
Future<double> getFontSize() async {
return await _storage.getDouble(_keyFontSize, 16.0);
}
// 待办事项列表操作
Future<void> addTodoItem(String item) async {
final currentList = await getTodoList();
currentList.add(item);
await _storage.setStringList(_keyTodoList, currentList);
}
Future<void> removeTodoItem(int index) async {
final currentList = await getTodoList();
if (index >= 0 && index < currentList.length) {
currentList.removeAt(index);
await _storage.setStringList(_keyTodoList, currentList);
}
}
Future<List<String>> getTodoList() async {
return await _storage.getStringList(_keyTodoList, []);
}
}
3.3 UI 层集成示例
下面展示如何在Flutter应用的UI层中集成shared_preferences,实现用户配置的保存与读取:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter SharedPreferences OH Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _ageController = TextEditingController();
String _savedUsername = '';
int _savedAge = 0;
bool _isDarkMode = false;
List<String> _todoList = [];
void initState() {
super.initState();
_loadPreferences();
}
Future<void> _loadPreferences() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_savedUsername = prefs.getString('username') ?? '';
_savedAge = prefs.getInt('age') ?? 0;
_isDarkMode = prefs.getBool('dark_mode') ?? false;
_todoList = prefs.getStringList('todo_list') ?? [];
});
}
Future<void> _saveUsername() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', _usernameController.text);
setState(() {
_savedUsername = _usernameController.text;
});
_usernameController.clear();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('用户名保存成功')),
);
}
}
Future<void> _saveAge() async {
final prefs = await SharedPreferences.getInstance();
final age = int.tryParse(_ageController.text) ?? 0;
await prefs.setInt('age', age);
setState(() {
_savedAge = age;
});
_ageController.clear();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('年龄保存成功')),
);
}
}
Future<void> _toggleDarkMode(bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('dark_mode', value);
setState(() {
_isDarkMode = value;
});
}
Future<void> _addTodo(String todo) async {
if (todo.trim().isEmpty) return;
final prefs = await SharedPreferences.getInstance();
final newList = List<String>.from(_todoList)..add(todo);
await prefs.setStringList('todo_list', newList);
setState(() {
_todoList = newList;
});
}
Future<void> _removeTodo(int index) async {
final prefs = await SharedPreferences.getInstance();
final newList = List<String>.from(_todoList)..removeAt(index);
await prefs.setStringList('todo_list', newList);
setState(() {
_todoList = newList;
});
}
Future<void> _clearAll() async {
final prefs = await SharedPreferences.getInstance();
await prefs.clear();
setState(() {
_savedUsername = '';
_savedAge = 0;
_isDarkMode = false;
_todoList = [];
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('所有数据已清除')),
);
}
}
Widget build(BuildContext context) {
return Theme(
data: _isDarkMode ? ThemeData.dark() : ThemeData.light(),
child: Scaffold(
appBar: AppBar(
title: const Text('SharedPreferences OH 示例'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildCurrentData(),
const SizedBox(height: 20),
_buildUsernameInput(),
const SizedBox(height: 16),
_buildAgeInput(),
const SizedBox(height: 16),
_buildDarkModeSwitch(),
const SizedBox(height: 16),
_buildTodoList(),
const SizedBox(height: 20),
_buildClearButton(),
],
),
),
),
);
}
Widget _buildCurrentData() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'当前存储的数据',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Text('用户名: $_savedUsername'),
const SizedBox(height: 8),
Text('年龄: $_savedAge'),
const SizedBox(height: 8),
Text('暗黑模式: ${_isDarkMode ? '开启' : '关闭'}'),
const SizedBox(height: 8),
Text('待办事项数量: ${_todoList.length}'),
],
),
),
);
}
Widget _buildUsernameInput() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'保存用户名 (String)',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
TextField(
controller: _usernameController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: '请输入用户名',
),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _saveUsername,
child: const Text('保存用户名'),
),
],
),
),
);
}
Widget _buildAgeInput() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'保存年龄 (int)',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
TextField(
controller: _ageController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: '请输入年龄',
keyboardType: TextInputType.number,
),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: _saveAge,
child: const Text('保存年龄'),
),
],
),
),
);
}
Widget _buildDarkModeSwitch() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'暗黑模式 (bool)',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
Switch(
value: _isDarkMode,
onChanged: _toggleDarkMode,
),
],
),
),
);
}
Widget _buildTodoList() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'待办事项列表 (List<String>)',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: '添加待办事项',
),
onSubmitted: (value) {
_addTodo(value);
},
),
const SizedBox(height: 12),
_todoList.isEmpty
? const Text('暂无待办事项')
: ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: _todoList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_todoList[index]),
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => _removeTodo(index),
),
);
},
),
],
),
),
);
}
Widget _buildClearButton() {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
),
onPressed: _clearAll,
child: const Text('清除所有数据'),
);
}
}
四、OpenHarmony 平台特殊适配处理
4.1 权限配置
在OpenHarmony平台上,shared_preferences不需要额外的特殊权限配置,因为它使用的是应用沙箱内的存储。但需要确保ohos/entry/src/main/module.json5文件配置正确:
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": ["phone", "tablet"],
"abilities": [
{
"name": "EntryAbility",
"srcEntrance": "./ets/entryability/EntryAbility.ets",
"description": "$string:entry_ability_desc",
"icon": "$media:app_icon",
"label": "$string:entry_ability_label",
"type": "page",
"launchType": "standard"
}
]
}
}
4.2 存储位置与数据持久化
在OpenHarmony系统中,shared_preferences的数据会被保存在应用的私有目录下,具体位置通常为:/data/app/el2/100/base/<package_name>/haps/entry/files/
数据以XML格式或类似的键值对形式存储,应用卸载时会自动清除这些数据,符合系统的安全设计原则。
4.3 平台通道实现原理
shared_preferences在OpenHarmony上的实现依赖于Flutter的平台通道机制。当Dart层调用存储方法时,数据会通过MethodChannel传递到原生侧,然后由OpenHarmony的Preferences API进行实际的读写操作。这个过程对开发者是透明的,但理解其原理有助于排查问题。
五、常见问题与解决方案
5.1 问题:数据丢失或读取失败
原因分析:
- 应用版本更新时数据迁移问题
- 设备恢复出厂设置或清除应用数据
- 异步操作顺序错误导致的竞态条件
解决方案:
- 确保在应用启动时正确初始化SharedPreferences实例
- 使用try-catch捕获可能的异常
- 对于重要数据,考虑同时使用其他持久化方案进行备份
Future<void> safeSetString(String key, String value) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, value);
} catch (e) {
debugPrint('保存数据失败: $e');
// 可以在这里添加备用存储逻辑
}
}
5.2 问题:应用重启后配置不生效
原因分析:
- 没有在应用启动时正确读取已保存的配置
- 状态管理不当,UI没有正确响应数据变化
解决方案:
- 在StatefulWidget的initState方法中调用数据加载方法
- 使用setState确保UI在数据更新后重新渲染
- 考虑使用状态管理库(如Provider、Riverpod)来更好地管理配置状态
5.3 问题:大量数据存储导致性能下降
原因分析:
- shared_preferences设计用于轻量级数据存储,不适合存储大量数据
- 频繁的读写操作可能影响应用性能
解决方案:
- 对于大量结构化数据,使用SQLite或isar等数据库
- 合并多次写入操作为一次批量操作
- 对读取频繁的数据进行内存缓存
六、性能优化建议
6.1 合理使用数据类型
根据数据的实际需求选择最合适的数据类型,避免不必要的类型转换:
// 不推荐
Future<void> saveCount(int count) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('count', count.toString()); // 不必要的转换
}
// 推荐
Future<void> saveCount(int count) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('count', count); // 直接使用int类型
}
6.2 批量操作优化
当需要同时保存多个键值对时,虽然shared_preferences没有提供专门的批量API,但可以通过减少异步等待次数来优化性能:
Future<void> saveUserPreferences({
required String username,
required bool darkMode,
required double fontSize,
}) async {
final prefs = await SharedPreferences.getInstance();
// 只获取一次实例,然后进行多次操作
await prefs.setString('username', username);
await prefs.setBool('dark_mode', darkMode);
await prefs.setDouble('font_size', fontSize);
}
6.3 缓存策略
对于读取频繁且变化不频繁的数据,可以在内存中维护一份缓存,减少对磁盘的访问:
class CachedPreferences {
static final CachedPreferences _instance = CachedPreferences._internal();
factory CachedPreferences() => _instance;
CachedPreferences._internal();
SharedPreferences? _prefs;
final Map<String, dynamic> _cache = {};
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
Future<void> setString(String key, String value) async {
_cache[key] = value;
await _prefs?.setString(key, value);
}
String? getString(String key, [String? defaultValue]) {
if (_cache.containsKey(key)) {
return _cache[key] as String?;
}
final value = _prefs?.getString(key) ?? defaultValue;
_cache[key] = value;
return value;
}
}
七、运行验证
7.1 构建与运行
在OpenHarmony设备或模拟器上运行项目:
# 检查可用设备
flutter devices
# 运行应用
flutter run -d <device_id>
7.2 功能测试清单
确保测试以下功能点:
- 保存和读取字符串数据
- 保存和读取整数数据
- 保存和读取布尔值数据
- 保存和读取字符串列表
- 删除单个键值对
- 清除所有数据
- 应用重启后数据持久化验证
- 暗黑模式开关状态保持
- 待办事项列表的增删操作
八、总结与扩展
通过本文的实践,我们成功完成了shared_preferences库在Flutter-OH项目中的集成与适配。shared_preferences作为一个成熟的Flutter插件,在OpenHarmony平台上的适配相对平滑,开发者主要需要关注的是数据持久化的可靠性和性能优化。
8.1 核心要点回顾
- 环境配置:确保Flutter和OpenHarmony SDK版本兼容
- 依赖管理:在pubspec.yaml中正确添加shared_preferences依赖
- API使用:遵循库的设计规范,合理封装业务逻辑
- 平台适配:关注OpenHarmony特有的存储机制和权限要求
- 性能优化:根据实际场景选择合适的数据存储策略
8.2 进阶扩展方向
对于更复杂的数据持久化需求,可以考虑以下方案:
- SQLite:使用sqflite或drift库处理结构化数据
- 文件存储:直接读写文件,适合大型二进制数据
- 对象存储:使用hive或isar等NoSQL数据库,支持对象直接持久化
- 加密存储:对于敏感数据,考虑使用flutter_secure_storage等加密存储方案
shared_preferences虽然简单,但却是Flutter应用开发中不可或缺的工具。结合OpenHarmony平台的特性合理使用,可以为用户提供流畅且稳定的使用体验。希望本文能为正在进行鸿蒙化适配的开发者提供有价值的参考。
本文仓库地址:https://atomgit.com/your_username/flutter_shared_preferences_oh
参考文献:
- OpenHarmony 官方文档:https://gitee.com/openharmony/docs
- shared_preferences 官方文档:https://pub.dev/packages/shared_preferences
- Flutter for OpenHarmony 文档:https://gitee.com/openharmony-sig/flutter
更多推荐
所有评论(0)