欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Flutter 三方库 shared_preferences 的 OpenHarmony 鸿蒙化适配实践

引言

在移动应用开发中,本地数据持久化是一个基础且核心的需求。shared_preferences作为Flutter生态中最流行的键值对存储库,因其简单易用的API设计而被广泛应用于用户配置、缓存数据、状态保存等场景。随着OpenHarmony生态的快速发展,如何将shared_preferences库适配到鸿蒙平台成为了众多跨平台开发者关注的重点。本文将详细介绍在Flutter-OH项目中集成shared_preferences的完整流程,包括环境配置、依赖集成、代码实现、平台适配以及常见问题解决方案,为开发者提供一份可直接参考的实践指南。

一、环境准备与项目初始化

1.1 开发环境配置

在开始shared_preferences的鸿蒙化适配之前,需要确保开发环境满足以下要求:

  • DevEco Studio 版本:4.1 及以上
  • Flutter SDK 版本:3.16.0 及以上
  • OpenHarmony SDK 版本:4.1.0.400 及以上
  • 鸿蒙设备或模拟器:用于运行和验证代码

开发者可以通过以下命令检查Flutter和OpenHarmony的环境配置:

flutter --version
flutter doctor

1.2 创建 Flutter-OH 项目

使用Flutter命令行工具创建支持OpenHarmony的新项目:

flutter create --platforms=ohos flutter_shared_preferences_oh
cd flutter_shared_preferences_oh

项目创建完成后,目录结构将包含标准的Flutter项目文件,以及专门为OpenHarmony平台准备的ohos文件夹。

二、集成 shared_preferences 依赖

2.1 添加依赖到 pubspec.yaml

在项目的pubspec.yaml文件中添加shared_preferences依赖:

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  shared_preferences: ^2.2.2

shared_preferences库是一个官方维护的Flutter插件,内部通过平台通道(Method Channel)与各平台的原生实现进行通信。对于OpenHarmony平台,该库已经提供了初步支持,但开发者仍需关注平台特定的细节问题。

2.2 获取依赖

运行以下命令下载并安装依赖包:

flutter pub get

命令执行成功后,shared_preferences及其依赖项将被添加到项目中。

三、shared_preferences 在 OpenHarmony 上的适配实践

3.1 基础使用方法

shared_preferences的核心API设计简洁明了,主要包含以下几类操作:

  1. 获取实例:通过getInstance()异步方法获取SharedPreferences单例
  2. 存储数据:支持String、int、double、bool、List等多种数据类型
  3. 读取数据:通过对应的get方法读取指定key的值
  4. 删除数据:移除指定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 核心要点回顾

  1. 环境配置:确保Flutter和OpenHarmony SDK版本兼容
  2. 依赖管理:在pubspec.yaml中正确添加shared_preferences依赖
  3. API使用:遵循库的设计规范,合理封装业务逻辑
  4. 平台适配:关注OpenHarmony特有的存储机制和权限要求
  5. 性能优化:根据实际场景选择合适的数据存储策略

8.2 进阶扩展方向

对于更复杂的数据持久化需求,可以考虑以下方案:

  1. SQLite:使用sqflite或drift库处理结构化数据
  2. 文件存储:直接读写文件,适合大型二进制数据
  3. 对象存储:使用hive或isar等NoSQL数据库,支持对象直接持久化
  4. 加密存储:对于敏感数据,考虑使用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
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐