Flutter `shared_preferences` 三方库在 OpenHarmony 平台的适配实践

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)构建一个功能对等的平台层实现。整个过程可以分解为几个步骤:

  1. 搭建鸿蒙模块 :在插件工程中,新建 OpenHarmony 平台目录(ohos/)。
  2. 实现核心类:创建一个 ArkTS 类,作为插件在鸿蒙端的入口。
  3. 调用 Preferences API :在这个类中,利用 @ohos.data.preferences 包的能力,实现 getAllsetStringremoveclear 等核心方法。
  4. 注册通道 :在插件初始化时,将这个实现类注册到 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. 调试技巧
  1. 查看日志 :充分利用代码中的 console.info/error 输出,在 DevEco Studio 的 HiLog 或终端里观察插件运行状态。

  2. 检查数据文件 :适配成功后,数据文件会保存在应用沙箱内。可以通过 hdc shell 连接到设备,查看 /data/app/.../files/FlutterSharedPreferences.preferences 文件是否存在,内容是否正确。

  3. 编写简单测试 :在 Flutter 侧写一个测试页面,验证基本功能。

    dart 复制代码
    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 支持 stringnumberbooleanArray<string>,而 Flutter 侧有 intdouble 之分(在鸿蒙端都对应 number)。必须在 _wrapValue 和 Dart 层的解析逻辑中做好精细的类型转换与约定。
  • 线程安全 :虽然 OpenHarmony 的 Preferences API 本身是线程安全的,但我们封装的 PreferencesManager 仍需考虑多线程同时访问的情况。
  • 版本兼容 :关注 OpenHarmony SDK 及 @ohos.data.preferences API 的版本更新,适配代码可能需要随之调整。

六、 写在最后

通过以上步骤,我们系统地完成了 shared_preferences 插件在 OpenHarmony 平台上的适配。这个过程不仅涉及 Flutter 插件机制的深入理解,也要求对鸿蒙系统的存储 API 有实际的应用经验。

成功的适配离不开三点:

  1. 理解通信协议:准确把握 Flutter MethodChannel 的数据格式和调用约定。
  2. 用好原生能力:熟练掌握 OpenHarmony 相关子系统(如 Preferences)的接口特性。
  3. 处理兼容细节:耐心解决好数据类型、文件路径、生命周期等跨平台带来的差异。

这个方案也为其他 Flutter 插件(比如 path_providersqflite)迁移到 OpenHarmony 提供了一个可行的思路和代码参考。随着 OpenHarmony 生态的逐步成熟,这类跨平台适配工作将成为连接 Flutter 开发与鸿蒙生态的重要一环。希望我们的实践能给你带来实实在在的帮助。

相关推荐
音浪豆豆_Rachel2 小时前
Flutter鸿蒙文件选择器内核解析:从Dart调用到ArkTS系统级对话
flutter·harmonyos
音浪豆豆_Rachel2 小时前
Flutter鸿蒙文件选择器实现层解析:消息通道、协议转换与数据处理
flutter·华为·harmonyos
音浪豆豆_Rachel2 小时前
Flutter鸿蒙文件选择器入口解析:插件生命周期与平台绑定
flutter·harmonyos
消失的旧时光-19433 小时前
401 刷新 Token 的队列版(请求挂起排队 + 刷新后统一重放/统一失败)
flutter·dio
消失的旧时光-19433 小时前
Repository 层如何无缝接入本地缓存 / 数据库
数据库·flutter·缓存
消失的旧时光-19433 小时前
用 Drift 实现 Repository 无缝接入本地缓存/数据库(SWR:先快后准)
数据库·flutter·缓存
消失的旧时光-19433 小时前
Android(Kotlin) ↔ Flutter(Dart) 的“1:1 对应表”:架构分层来对照(MVVM/MVI 都适用)
android·flutter·kotlin
子春一212 小时前
Flutter 2025 性能工程体系:从启动优化到帧率稳定,打造丝滑、省电、低内存的极致用户体验
flutter·ux
子春一213 小时前
Flutter 2025 性能工程体系:从启动优化到帧率稳定,打造丝滑如原生的用户体验
flutter·ux