Flutter shared_preferences 三方库在 OpenHarmony 平台的适配实践
摘要
在将 Flutter 应用迁移到 OpenHarmony 平台时,数据持久化是首先要解决的挑战之一。shared_preferences 作为 Flutter 生态中最常用的轻量级键值存储插件,其官方版本并未覆盖 OpenHarmony。本文将分享我们如何从原理出发,一步步为 shared_preferences 实现鸿蒙端的原生支持。内容涵盖适配背后的原因、整体架构设计、详细的代码实现、以及在实际集成中可能遇到的问题与优化建议,希望能为类似插件迁移提供一份实用的参考。
一、 为什么需要适配?
Flutter 的"一次编写,多端部署"能力,很大程度上依赖于各平台(Android/iOS)提供的原生接口实现。像 shared_preferences 这样的插件,在 Android 端调用的是 SharedPreferences API,在 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. 准备工程结构
首先,你需要获取插件源码,并建立适配所需的目录。
bash
# 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:
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 文件:
typescript
// 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)中初始化插件:
typescript
// 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 中:
yaml
dependencies:
shared_preferences:
path: /path/to/your/adapted/packages/packages/shared_preferences
2. 调试技巧
-
查看日志 :充分利用代码中的
console.info/error输出,在 DevEco Studio 的 HiLog 或终端里观察插件运行状态。 -
检查数据文件 :适配成功后,数据文件会保存在应用沙箱内。可以通过
hdc shell连接到设备,查看/data/app/.../files/FlutterSharedPreferences.preferences文件是否存在,内容是否正确。 -
编写简单测试 :在 Flutter 侧写一个测试页面,验证基本功能。
dartFuture<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 开发与鸿蒙生态的重要一环。希望我们的实践能给你带来实实在在的帮助。