Fluttershared_preferences三方库在 OpenHarmony 平台的适配实践
摘要
在将 Flutter 应用迁移到 OpenHarmony 平台时,数据持久化是首先要解决的挑战之一。shared_preferences作为 Flutter 生态中最常用的轻量级键值存储插件,其官方版本并未覆盖 OpenHarmony。本文将分享我们如何从原理出发,一步步为shared_preferences实现鸿蒙端的原生支持。内容涵盖适配背后的原因、整体架构设计、详细的代码实现、以及在实际集成中可能遇到的问题与优化建议,希望能为类似插件迁移提供一份实用的参考。
一、 为什么需要适配?
Flutter 的“一次编写,多端部署”能力,很大程度上依赖于各平台(Android/iOS)提供的原生接口实现。像shared_preferences这样的插件,在 Android 端调用的是SharedPreferencesAPI,在 iOS 端则是NSUserDefaults。然而,OpenHarmony 作为一套全新的操作系统,并不在 Flutter 官方原生支持的范围之内。因此,任何基于平台通道(Platform Channel)的 Flutter 插件,在鸿蒙上都无法直接运行。
如果我们希望 Flutter 应用能在 OpenHarmony 上正常使用shared_preferences,就必须为它实现一个鸿蒙专属的“后端”。这个后端需要负责通过 Flutter 的平台通道与 Dart 层通信,并调用 OpenHarmony 系统本身的持久化存储接口来完成数据读写。下面,我们就来详细说说具体的实现过程。
二、 适配思路与整体架构
1. Flutter 插件是如何工作的?
一个典型的 Flutter 插件分为三层:
- Dart 层:面向开发者,提供简洁的 API(例如
SharedPreferences.getInstance())。 - 平台层:分别在 Android(Java/Kotlin)和 iOS(Objective-C/Swift)上实现数据存取的具体逻辑。
- 通信层:双方通过MethodChannel进行异步消息传递。Dart 层发起调用并传递参数,平台层执行本地操作后,再将结果返回。
2. OpenHarmony 提供了哪种存储方案?
OpenHarmony 的持久化方案有好几种,对于shared_preferences这种简单的键值对存储,最匹配的是首选项(Preferences)子系统。
- 特点:它采用 KV 模型,数据以文件形式存储在应用沙箱内,通过
Preferences类进行访问。内部实现了内存缓存和异步回写,兼顾了性能和数据安全。
3. 我们的适配方案
核心目标很明确:在 OpenHarmony 端(使用 ArkTS)构建一个功能对等的平台层实现。整个过程可以分解为几个步骤:
- 搭建鸿蒙模块:在插件工程中,新建 OpenHarmony 平台目录(
ohos/)。 - 实现核心类:创建一个 ArkTS 类,作为插件在鸿蒙端的入口。
- 调用 Preferences API:在这个类中,利用
@ohos.data.preferences包的能力,实现getAll、setString、remove、clear等核心方法。 - 注册通道:在插件初始化时,将这个实现类注册到 Flutter Engine 的
MethodChannel上,完成 Dart 与鸿蒙的桥接。
三、 具体代码实现
接下来,我们从环境搭建开始,看看关键的代码应该如何编写。
1. 准备工程结构
首先,你需要获取插件源码,并建立适配所需的目录。
# 1. 克隆官方插件仓库(或从 pub.dev 下载指定版本源码) git clone https://github.com/flutter/packages.git cd packages/packages/shared_preferences # 2. 查看标准结构(通常会看到 android/, ios/, lib/ 等目录) ls -la # 3. 创建 OpenHarmony 平台的实现目录 mkdir -p ohos/src/main/ets/com/example/sharedpreferencesohos随后,在ohos目录下创建模块配置文件package.json:
{ "license": "BSD-3-Clause", "types": "./index.d.ts", "name": "@flutter/shared_preferences_ohos", "ohos": { "org": "flutter", "directoryLevel": "module", "buildTool": "hvigor" }, "description": "OHOS implementation of the shared_preferences plugin", "repository": "https://github.com/flutter/packages/tree/main/packages/shared_preferences", "version": "2.2.2+ohos", "main": "./src/main/ets/MainAbility/SharedPreferencesOhos.ts", "types": "./src/main/ets/MainAbility/SharedPreferencesOhos.ts", "dependencies": { "@ohos/data.preferences": "1.0.0" } }2. 鸿蒙端核心实现类
在刚创建的目录下,新建SharedPreferencesOhos.ets文件:
// SharedPreferencesOhos.ets import preferences from '@ohos.data.preferences'; import { BusinessError } from '@ohos.base'; import common from '@ohos.app.ability.common'; // 一个简单的管理器,确保每个配置文件只对应一个Preferences实例 class PreferencesManager { private static instance: PreferencesManager; private preferencesMap: Map<string, preferences.Preferences> = new Map(); static getInstance(): PreferencesManager { if (!PreferencesManager.instance) { PreferencesManager.instance = new PreferencesManager(); } return PreferencesManager.instance; } async getPreferences(context: common.Context, name: string): Promise<preferences.Preferences> { let prefs = this.preferencesMap.get(name); if (!prefs) { try { // 文件会存储在 /data/app/.../files/<name>.preferences prefs = await preferences.getPreferences(context, name); this.preferencesMap.set(name, prefs); console.info(`[SharedPreferencesOhos] Successfully loaded preferences: ${name}`); } catch (err) { const error = err as BusinessError; console.error(`[SharedPreferencesOhos] Failed to get preferences ${name}: ${error.code}, ${error.message}`); throw new Error(`Unable to get preferences: ${error.message}`); } } return prefs; } removePreferences(name: string): void { this.preferencesMap.delete(name); } } // Flutter平台通道的具体实现 export class SharedPreferencesOhos { private static CHANNEL_NAME = 'plugins.flutter.io/shared_preferences'; private manager: PreferencesManager = PreferencesManager.getInstance(); private context: common.Context | undefined = undefined; // 需要从Ability注入Context setContext(context: common.Context): void { this.context = context; console.info('[SharedPreferencesOhos] Context injected.'); } // 主入口:注册方法通道处理器 registerMethodCallHandler(): void { if (!this.context) { console.error('[SharedPreferencesOhos] Context is not set. Cannot register handler.'); return; } // 注意:这里假设Flutter for OHOS引擎提供了全局的`flutter`对象来注册通道。 // 实际集成方式可能因桥接方案而异,此处展示核心逻辑。 const methodChannel = flutter.engine.MethodChannel(this.CHANNEL_NAME); methodChannel.setMethodCallHandler(this.handleMethodCall.bind(this)); console.info(`[SharedPreferencesOhos] Method channel '${this.CHANNEL_NAME}' registered.`); } private async handleMethodCall(call: flutter.engine.MethodCall): Promise<any> { if (!this.context) { return Promise.reject(new Error('Context not available')); } const args = call.arguments; const prefsName = 'FlutterSharedPreferences'; // 采用与Android端相同的固定文件名 try { const prefs = await this.manager.getPreferences(this.context, prefsName); switch (call.method) { case 'getAll': const allEntries: Record<string, any> = {}; const keys = await prefs.getAllKeys(); for (const key of keys) { const value = await prefs.get(key, ''); // 这里需要将值包装成Flutter Dart层期望的特定格式 allEntries[key] = this._wrapValue(value); } return allEntries; case 'setBool': case 'setInt': case 'setDouble': case 'setString': case 'setStringList': const key = args['key'] as string; const value = args['value']; await prefs.put(key, value); await prefs.flush(); // 确保数据写入磁盘 return true; case 'remove': const removeKey = args['key'] as string; await prefs.delete(removeKey); await prefs.flush(); return true; case 'clear': await prefs.clear(); await prefs.flush(); // 清理内存中的实例 this.manager.removePreferences(prefsName); return true; default: console.warn(`[SharedPreferencesOhos] Unknown method: ${call.method}`); throw new Error(`Method '${call.method}' not implemented`); } } catch (err) { const error = err as BusinessError | Error; console.error(`[SharedPreferencesOhos] Error in method ${call.method}:`, error.message); // 抛出异常后,Dart侧会收到PlatformException throw error; } } // 辅助方法:将原始值转换为Flutter约定的格式(此处为简化示例,实际类型处理需更细致) private _wrapValue(rawValue: preferences.ValueType): any { if (typeof rawValue === 'boolean') { return { 'bool': rawValue }; } else if (typeof rawValue === 'number') { // 注意:Preferences存储不区分整型和浮点,都为number。可能需要额外约定。 return { 'double': rawValue }; } else if (typeof rawValue === 'string') { return { 'string': rawValue }; } else if (Array.isArray(rawValue)) { return { 'stringList': rawValue }; } return { 'string': String(rawValue) }; // 默认处理 } }3. 在鸿蒙应用入口初始化插件
最后,别忘了在你的鸿蒙主 Ability(例如EntryAbility.ets)中初始化插件:
// EntryAbility.ets (节选) import { SharedPreferencesOhos } from '../sharedpreferencesohos/SharedPreferencesOhos'; import AbilityStage from '@ohos.app.ability.AbilityStage'; let spOhos: SharedPreferencesOhos | undefined = undefined; export default class EntryAbility extends Ability { onCreate(want, launchParam) { console.info('[EntryAbility] onCreate'); // 初始化插件 spOhos = new SharedPreferencesOhos(); spOhos.setContext(this.context); // 注册方法通道 spOhos.registerMethodCallHandler(); } // ... 其他生命周期方法 }四、 集成与调试
1. 在 Flutter 项目中集成
由于修改了原生代码,你需要使用本地路径依赖的方式来引用我们适配后的插件。
在 Flutter 项目的pubspec.yaml中:
dependencies: shared_preferences: path: /path/to/your/adapted/packages/packages/shared_preferences2. 调试技巧
- 查看日志:充分利用代码中的
console.info/error输出,在 DevEco Studio 的 HiLog 或终端里观察插件运行状态。 - 检查数据文件:适配成功后,数据文件会保存在应用沙箱内。可以通过
hdc shell连接到设备,查看/data/app/.../files/FlutterSharedPreferences.preferences文件是否存在,内容是否正确。 - 编写简单测试:在 Flutter 侧写一个测试页面,验证基本功能。
Future<void> testSharedPreferences() async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('demo_key', 'Hello OpenHarmony!'); final value = prefs.getString('demo_key'); print('读取到的值: $value'); // 确认日志输出正确 assert(value == 'Hello OpenHarmony!'); }
五、 一些优化点和注意事项
1. 性能优化建议
- 批量写入:
shared_preferences每次set操作都会触发一次平台通信和文件写入。如果遇到高频写入场景,建议在 Dart 层自己做批量合并,减少通信和 I/O 开销。 - 管理内存:我们目前的实现用了一个简单的 Map 做内存缓存。在应用内存吃紧时,可以考虑引入 LRU 机制,或者提供手动释放不常用配置文件的接口。
- 确保持久化:
preferences.flush()是异步的,但会等待写盘完成。在应用退出前,要确保所有flush操作都已完成,避免数据丢失。
2. 需要留意的细节
- 数据类型映射:这是最容易出错的地方。OpenHarmony Preferences 支持
string、number、boolean、Array<string>,而 Flutter 侧有int和double之分(在鸿蒙端都对应number)。必须在_wrapValue和 Dart 层的解析逻辑中做好精细的类型转换与约定。 - 线程安全:虽然 OpenHarmony 的
PreferencesAPI 本身是线程安全的,但我们封装的PreferencesManager仍需考虑多线程同时访问的情况。 - 版本兼容:关注 OpenHarmony SDK 及
@ohos.data.preferencesAPI 的版本更新,适配代码可能需要随之调整。
六、 写在最后
通过以上步骤,我们系统地完成了shared_preferences插件在 OpenHarmony 平台上的适配。这个过程不仅涉及 Flutter 插件机制的深入理解,也要求对鸿蒙系统的存储 API 有实际的应用经验。
成功的适配离不开三点:
- 理解通信协议:准确把握 Flutter MethodChannel 的数据格式和调用约定。
- 用好原生能力:熟练掌握 OpenHarmony 相关子系统(如 Preferences)的接口特性。
- 处理兼容细节:耐心解决好数据类型、文件路径、生命周期等跨平台带来的差异。
这个方案也为其他 Flutter 插件(比如path_provider、sqflite)迁移到 OpenHarmony 提供了一个可行的思路和代码参考。随着 OpenHarmony 生态的逐步成熟,这类跨平台适配工作将成为连接 Flutter 开发与鸿蒙生态的重要一环。希望我们的实践能给你带来实实在在的帮助。