【Flutter for OpenHarmony】跨平台工程集成本地存储能力:键值存储、文件读写与数据加密

作者:maaath


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


一、前言

本地存储是移动应用不可或缺的基础能力。用户偏好设置需要轻量级键值存储,大段文本或结构化数据需要文件读写能力,密码和 Token 等敏感信息则必须依赖数据加密存储。在 Flutter for OpenHarmony(以下简称 FOH)跨平台工程中,这些存储能力并非 Flutter 引擎直接提供,而是需要通过 OpenHarmony 原生 API + Platform Channel 的双层架构来实现。

本文基于一个经过验证的 FOH 项目,系统讲解如何在跨平台工程中集成三大本地存储能力:

  • Preferences Storage:轻量级键值存储,基于 @ohos.data.preferences
  • File Storage:文件读写存储,基于 @ohos.file.fs
  • Encryption Storage:数据加密存储,基于 @ohos.security.cryptoFramework

核心主线是 Flutter 端通过 Platform Channel 调用原生 ArkTS 存储服务,实现一套代码、多端复用的本地存储方案。

仓库地址:https://atomgit.com/maaath/oh_demo11


二、技术方案总览

2.1 为什么需要原生存储 API?

Flutter 框架本身提供了若干存储能力:shared_preferences 用于键值存储、path_provider 用于获取应用目录、dart:io 提供基础文件操作。然而在 OpenHarmony 平台上,这些包尚未完成原生适配,开发者需要直接调用 OpenHarmony 原生 API 来实现存储功能。

FOH 的解决思路是:在 ArkTS 原生侧封装存储服务,通过 Platform Channel 向 Flutter 端暴露能力。Flutter 端使用熟悉的 Dart 语言调用,ArkTS 侧处理平台细节,对上层完全透明。

2.2 架构设计

┌──────────────────────────────────────────────────────────────┐
│                    Flutter 端 (Dart)                         │
│  ┌────────────────┐  ┌──────────────┐  ┌───────────────┐  │
│  │  StorageService │  │ StoragePage  │  │ StorageIndex  │  │
│  │ (MethodChannel) │  │ (UI 展示)    │  │ (抽象封装)    │  │
│  └───────┬────────┘  └──────────────┘  └───────────────┘  │
│          │ Platform Channel                                 │
│  ┌───────▼──────────────────────────────────────────────┐   │
│  │              FlutterEngine (Native 嵌入层)             │   │
│  └───────────────────────────────────────────────────────┘   │
│          │                                                 │
│  ┌───────▼──────────────────────────────────────────────┐   │
│  │              ArkTS 原生存储服务层                      │   │
│  │  ┌──────────┬──────────┬────────────┐              │   │
│  │  │Preferences│  File    │ Encryption │              │   │
│  │  │Storage    │ Storage  │ Storage    │              │   │
│  │  └──────────┴──────────┴────────────┘              │   │
│  └───────────────────────────────────────────────────────┘   │
│          │                                                 │
│  ┌───────▼──────────────────────────────────────────────┐   │
│  │              OpenHarmony 系统 API                     │   │
│  │  @ohos.data.preferences  @ohos.file.fs              │   │
│  │  @ohos.security.cryptoFramework                      │   │
│  └───────────────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────────────┘

2.3 项目文件结构

oh_demo11/
├── lib/                                   # Flutter Dart 代码
│   ├── main.dart                          # Flutter 入口
│   └── storage/                           # 存储相关 Flutter 模块
│       ├── storage_service.dart           # Platform Channel 封装
│       ├── storage_index.dart             # 存储服务入口索引
│       └── pages/
│           └── storage_demo_page.dart     # 存储演示页面
├── entry/src/main/ets/                    # ArkTS 原生代码
│   ├── utils/
│   │   ├── PreferencesStorage.ets         # 偏好设置存储
│   │   ├── FileStorage.ets                # 文件存储
│   │   ├── EncryptionStorage.ets          # 加密存储
│   │   └── StorageIndex.ets               # 存储索引(MethodChannel 注册)
│   ├── model/
│   │   └── StorageModels.ets              # 数据模型
│   ├── pages/
│   │   └── StorageDemo.ets               # 原生演示页面(ArkTS 对照参考)
│   └── entryability/
│       └── EntryAbility.ets               # Flutter 引擎入口
└── module.json5                           # 权限声明

三、ArkTS 原生存储服务实现

3.1 Preferences Storage — 轻量级键值存储

PreferencesStorage 基于 @ohos.data.preferences 实现,提供 String、Number、Boolean 类型的键值存储,适用于保存用户偏好设置、应用配置等轻量数据。

// ArkTS 侧 - utils/PreferencesStorage.ets

import dataPreferences from '@ohos.data.preferences';
import hilog from '@ohos.hilog';

const TAG = 'PreferencesStorage';
const DOMAIN = 0xFF02;

export class PreferencesStorage {
  private context: Context | null = null;
  private preferences: dataPreferences.Preferences | null = null;
  private readonly FILE_NAME: string = 'app_preferences';

  constructor(context: Context) {
    this.context = context;
  }

  async init(): Promise<boolean> {
    if (this.context === null) {
      hilog.error(DOMAIN, TAG, 'Context is null');
      return false;
    }
    try {
      this.preferences = await dataPreferences.getPreferences(
        this.context,
        this.FILE_NAME
      );
      hilog.info(DOMAIN, TAG, 'Preferences initialized');
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Init failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  async putString(key: string, value: string): Promise<boolean> {
    if (!this.preferences) {
      await this.init();
    }
    try {
      await this.preferences!.put(key, value);
      await this.preferences!.flush();
      hilog.info(DOMAIN, TAG, 'Put string: %{public}s', key);
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Put string failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  async getString(key: string, defaultValue: string = ''): Promise<string> {
    if (!this.preferences) {
      await this.init();
    }
    try {
      const value = await this.preferences!.get(key, defaultValue);
      return value as string;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Get string failed: %{public}s',
        JSON.stringify(error));
      return defaultValue;
    }
  }

  async putNumber(key: string, value: number): Promise<boolean> {
    if (!this.preferences) {
      await this.init();
    }
    try {
      await this.preferences!.put(key, value);
      await this.preferences!.flush();
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Put number failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  async getNumber(key: string, defaultValue: number = 0): Promise<number> {
    if (!this.preferences) {
      await this.init();
    }
    try {
      const value = await this.preferences!.get(key, defaultValue);
      return value as number;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Get number failed: %{public}s',
        JSON.stringify(error));
      return defaultValue;
    }
  }

  async putBoolean(key: string, value: boolean): Promise<boolean> {
    if (!this.preferences) {
      await this.init();
    }
    try {
      await this.preferences!.put(key, value);
      await this.preferences!.flush();
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Put boolean failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  async getBoolean(key: string, defaultValue: boolean = false): Promise<boolean> {
    if (!this.preferences) {
      await this.init();
    }
    try {
      const value = await this.preferences!.get(key, defaultValue);
      return value as boolean;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Get boolean failed: %{public}s',
        JSON.stringify(error));
      return defaultValue;
    }
  }

  async delete(key: string): Promise<boolean> {
    if (!this.preferences) {
      await this.init();
    }
    try {
      await this.preferences!.delete(key);
      await this.preferences!.flush();
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Delete failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  async clear(): Promise<boolean> {
    if (!this.preferences) {
      await this.init();
    }
    try {
      await this.preferences!.clear();
      await this.preferences!.flush();
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Clear failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }
}

ArkTS 关键设计要点

  • Preferences 实例通过 getPreferences(context, FILE_NAME) 获取,其中 FILE_NAME 是存储文件名(不带扩展名)。
  • 所有写操作(putdeleteclear)后必须调用 flush() 才能将数据持久化到磁盘。
  • get() 方法的第二个参数为默认值,当键不存在时返回该默认值。

3.2 File Storage — 文件读写存储

FileStorage 基于 @ohos.file.fs 实现,支持文本文件和 JSON 文件的读写,适用于存储笔记、配置、结构化数据等中等体量的数据。

// ArkTS 侧 - utils/FileStorage.ets

import fs from '@ohos.file.fs';
import hilog from '@ohos.hilog';

const TAG = 'FileStorage';
const DOMAIN = 0xFF02;

export class FileStorage {
  private context: Context | null = null;
  private readonly FILE_DIR: string = 'storage_data';

  constructor(context: Context) {
    this.context = context;
  }

  // 获取文件完整路径,自动创建目录
  private async getFilePath(fileName: string): Promise<string> {
    if (!this.context) {
      throw new Error('Context is null');
    }
    const dirPath = this.context.filesDir + '/' + this.FILE_DIR;
    if (!fs.accessSync(dirPath)) {
      fs.mkdirSync(dirPath, true);
    }
    return dirPath + '/' + fileName;
  }

  // ArrayBuffer 转字符串
  private arrayBufferToString(buffer: ArrayBuffer): string {
    const uint8Array = new Uint8Array(buffer);
    let result = '';
    for (let i = 0; i < uint8Array.length; i++) {
      result += String.fromCharCode(uint8Array[i]);
    }
    return result;
  }

  // 写入文本文件
  async writeTextFile(fileName: string, content: string): Promise<boolean> {
    try {
      const filePath = await this.getFilePath(fileName);
      const file = fs.openSync(filePath,
        fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      fs.writeSync(file.fd, content);
      fs.closeSync(file.fd);
      hilog.info(DOMAIN, TAG, 'Write text file: %{public}s', fileName);
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Write text failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  // 读取文本文件
  async readTextFile(fileName: string): Promise<string> {
    try {
      const filePath = await this.getFilePath(fileName);
      if (!fs.accessSync(filePath)) {
        hilog.info(DOMAIN, TAG, 'File not found: %{public}s', fileName);
        return '';
      }
      const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
      const stat = fs.statSync(file.fd);
      const buffer = new ArrayBuffer(stat.size);
      fs.readSync(file.fd, buffer);
      fs.closeSync(file.fd);
      return this.arrayBufferToString(buffer);
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Read text failed: %{public}s',
        JSON.stringify(error));
      return '';
    }
  }

  // 写入 JSON 文件(自动格式化)
  async writeJsonFile(fileName: string, data: object): Promise<boolean> {
    try {
      const jsonString = JSON.stringify(data, null, 2);
      return await this.writeTextFile(fileName, jsonString);
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Write JSON failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  // 读取 JSON 文件
  async readJsonFile(fileName: string): Promise<object | null> {
    try {
      const content = await this.readTextFile(fileName);
      if (!content) {
        return null;
      }
      return JSON.parse(content);
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Read JSON failed: %{public}s',
        JSON.stringify(error));
      return null;
    }
  }

  // 删除文件
  async deleteFile(fileName: string): Promise<boolean> {
    try {
      const filePath = await this.getFilePath(fileName);
      if (fs.accessSync(filePath)) {
        fs.unlinkSync(filePath);
      }
      hilog.info(DOMAIN, TAG, 'Delete file: %{public}s', fileName);
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Delete failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  // 检查文件是否存在
  async exists(fileName: string): Promise<boolean> {
    try {
      const filePath = await this.getFilePath(fileName);
      return fs.accessSync(filePath);
    } catch {
      return false;
    }
  }
}

ArkTS 关键设计要点

  • context.filesDir 返回应用私有目录的路径,确保数据不会泄露给其他应用。
  • 使用 fs.accessSync() 检查文件或目录是否存在,比 try-catch 更高效。
  • OpenMode.READ_WRITE | OpenMode.CREATE 组合表示文件不存在时创建、存在时覆盖。
  • JSON 写入使用 JSON.stringify(data, null, 2) 自动格式化,便于调试和阅读。

3.3 Encryption Storage — 数据加密存储

EncryptionStorage 基于 @ohos.security.cryptoFramework 实现,使用 AES-256-CBC 算法对数据进行加密后存储。加密过程使用随机 IV(初始化向量)确保相同明文每次加密产生不同密文,防止暴力分析。

// ArkTS 侧 - utils/EncryptionStorage.ets

import cryptoFramework from '@ohos.security.cryptoFramework';
import fs from '@ohos.file.fs';
import hilog from '@ohos.hilog';

const TAG = 'EncryptionStorage';
const DOMAIN = 0xFF02;

export class EncryptionStorage {
  private context: Context | null = null;
  private readonly FILE_DIR: string = 'encrypted_data';

  constructor(context: Context) {
    this.context = context;
  }

  private base64Chars: string =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

  // Base64 编码
  private base64Encode(data: Uint8Array): string {
    let result = '';
    const len = data.length;
    let i = 0;
    while (i < len) {
      const b1 = data[i++];
      const b2 = i < len ? data[i++] : 0;
      const b3 = i < len ? data[i++] : 0;
      result += this.base64Chars[b1 >> 2];
      result += this.base64Chars[((b1 & 0x03) << 4) | (b2 >> 4)];
      result += i > len + 1 ? '=' :
        this.base64Chars[((b2 & 0x0f) << 2) | (b3 >> 6)];
      result += i > len ? '=' : this.base64Chars[b3 & 0x3f];
    }
    return result;
  }

  // Base64 解码
  private base64Decode(base64: string): Uint8Array {
    const cleanBase64 = base64.replace(/=/g, '');
    const len = cleanBase64.length;
    const output: number[] = [];
    let i = 0;
    while (i < len) {
      const enc1 = this.base64Chars.indexOf(cleanBase64[i++]);
      const enc2 = this.base64Chars.indexOf(cleanBase64[i++]);
      const enc3 = this.base64Chars.indexOf(cleanBase64[i++]);
      const enc4 = this.base64Chars.indexOf(cleanBase64[i++]);
      output.push((enc1 << 2) | (enc2 >> 4));
      if (enc3 !== -1) {
        output.push(((enc2 & 0x0f) << 4) | (enc3 >> 2));
      }
      if (enc4 !== -1) {
        output.push(((enc3 & 0x03) << 6) | enc4);
      }
    }
    return new Uint8Array(output);
  }

  // 生成随机字节数组
  private generateRandomBytes(length: number): Uint8Array {
    const result = new Uint8Array(length);
    for (let i = 0; i < length; i++) {
      result[i] = Math.floor(Math.random() * 256);
    }
    return result;
  }

  // 获取文件路径
  private async getFilePath(fileName: string): Promise<string> {
    if (!this.context) {
      throw new Error('Context is null');
    }
    const dirPath = this.context.filesDir + '/' + this.FILE_DIR;
    if (!fs.accessSync(dirPath)) {
      fs.mkdirSync(dirPath, true);
    }
    return dirPath + '/' + fileName;
  }

  // 字符串转 Uint8Array
  private stringToUint8Array(str: string): Uint8Array {
    const arr = new Uint8Array(str.length);
    for (let i = 0; i < str.length; i++) {
      arr[i] = str.charCodeAt(i);
    }
    return arr;
  }

  // Uint8Array 转字符串
  private uint8ArrayToString(arr: Uint8Array): string {
    let result = '';
    for (let i = 0; i < arr.length; i++) {
      result += String.fromCharCode(arr[i]);
    }
    return result;
  }

  // 加密字符串
  async encryptString(plainText: string, password: string): Promise<string> {
    try {
      // 将密码转换为 32 字节密钥(AES-256)
      const keyData = this.stringToUint8Array(
        password.padEnd(32, '0').substring(0, 32));
      // 生成 16 字节随机 IV
      const iv = this.generateRandomBytes(16);

      // 创建对称密钥
      const symKeyGenerator =
        cryptoFramework.createSymKeyGenerator('AES256');
      const keyBlob: cryptoFramework.DataBlob = { data: keyData };
      const symKey = await symKeyGenerator.convertKey(keyBlob);

      // 创建加密器
      const cipher = cryptoFramework.createCipher('AES|CBC|PKCS7');
      const ivBlob: cryptoFramework.IvParamsSpec = {
        algName: 'AES',
        iv: { data: iv }
      };
      await cipher.init(
        cryptoFramework.CryptoMode.ENCRYPT_MODE,
        symKey,
        ivBlob
      );

      // 执行加密
      const plainData: cryptoFramework.DataBlob = {
        data: this.stringToUint8Array(plainText)
      };
      const encryptedResult = await cipher.doFinal(plainData);

      // 将 IV 和密文合并(IV 附加在头部)
      const combined = new Uint8Array(
        iv.byteLength + encryptedResult.data.byteLength);
      combined.set(iv, 0);
      combined.set(new Uint8Array(encryptedResult.data), iv.byteLength);

      // 返回 Base64 编码的加密数据
      return JSON.stringify({
        k: this.base64Encode(keyData),
        d: this.base64Encode(combined)
      });
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Encrypt failed: %{public}s',
        JSON.stringify(error));
      return '';
    }
  }

  // 解密字符串
  async decryptString(encryptedData: string): Promise<string> {
    try {
      const parsed: Record<string, string> = JSON.parse(encryptedData);
      const keyData = this.base64Decode(parsed.k);
      const combined = this.base64Decode(parsed.d);

      // 分离 IV 和密文
      const iv = combined.slice(0, 16);
      const encrypted = combined.slice(16);

      // 创建解密器
      const symKeyGenerator =
        cryptoFramework.createSymKeyGenerator('AES256');
      const keyBlob: cryptoFramework.DataBlob = { data: keyData };
      const symKey = await symKeyGenerator.convertKey(keyBlob);

      const cipher = cryptoFramework.createCipher('AES|CBC|PKCS7');
      const ivBlob: cryptoFramework.IvParamsSpec = {
        algName: 'AES',
        iv: { data: iv }
      };
      await cipher.init(
        cryptoFramework.CryptoMode.DECRYPT_MODE,
        symKey,
        ivBlob
      );

      // 执行解密
      const encryptedBlob: cryptoFramework.DataBlob = { data: encrypted };
      const decryptedResult = await cipher.doFinal(encryptedBlob);

      return this.uint8ArrayToString(
        new Uint8Array(decryptedResult.data));
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Decrypt failed: %{public}s',
        JSON.stringify(error));
      return '';
    }
  }

  // 加密并保存到文件
  async encryptAndSave(fileName: string, plainText: string): Promise<boolean> {
    try {
      const password = this.generatePassword();
      const encrypted = await this.encryptString(plainText, password);
      if (!encrypted) return false;

      const filePath = await this.getFilePath(fileName);
      const file = fs.openSync(filePath,
        fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      fs.writeSync(file.fd, encrypted);
      fs.closeSync(file.fd);
      hilog.info(DOMAIN, TAG, 'Encrypted save: %{public}s', fileName);
      return true;
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Encrypt save failed: %{public}s',
        JSON.stringify(error));
      return false;
    }
  }

  // 从文件读取并解密
  async loadAndDecrypt(fileName: string): Promise<string> {
    try {
      const filePath = await this.getFilePath(fileName);
      if (!fs.accessSync(filePath)) {
        return '';
      }

      const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
      const stat = fs.statSync(file.fd);
      const buffer = new ArrayBuffer(stat.size);
      fs.readSync(file.fd, buffer);
      fs.closeSync(file.fd);

      const encryptedData = this.arrayBufferToString(buffer);
      return await this.decryptString(encryptedData);
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Load decrypt failed: %{public}s',
        JSON.stringify(error));
      return '';
    }
  }

  // 生成密码
  private generatePassword(): string {
    const timestamp = Date.now().toString();
    const randomPart = Math.random().toString(36).substring(2, 15);
    return timestamp + randomPart;
  }

  private arrayBufferToString(buffer: ArrayBuffer): string {
    const uint8Array = new Uint8Array(buffer);
    let result = '';
    for (let i = 0; i < uint8Array.length; i++) {
      result += String.fromCharCode(uint8Array[i]);
    }
    return result;
  }
}

ArkTS 关键设计要点

  • AES-256-CBC 加密:密钥长度 256 位(32 字节),分组模式 CBC,填充方式 PKCS7。
  • 随机 IV:每次加密生成新的 16 字节随机 IV,附加在密文头部,接收端自动分离。
  • IvParamsSpec 必须包含 algName 属性:在 ArkTS 严格模式下,IvParamsSpec 接口要求 algName 字段,这是新手最容易忽略的报错点。
  • 数据格式:加密后的数据以 JSON 格式存储,包含 k(Base64 编码的密钥)和 d(Base64 编码的 IV + 密文组合)。

四、Platform Channel 接入层

ArkTS 原生存储服务通过 StorageIndex 类统一注册到 Flutter Platform Channel,Flutter 端通过统一的 StorageService 类调用:

// ArkTS 侧 - utils/StorageIndex.ets

import { FlutterPlugin, FlutterEngine } from '@ohos/flutter_ohos';
import { PreferencesStorage } from './PreferencesStorage';
import { FileStorage } from './FileStorage';
import { EncryptionStorage } from './EncryptionStorage';
import hilog from '@ohos.hilog';

const TAG = 'StorageIndex';
const DOMAIN = 0xFF02;
const CHANNEL_NAME = 'com.example.oh_demo11/storage';

export class StorageIndex implements FlutterPlugin {
  private context: Context | null = null;
  private preferencesStorage: PreferencesStorage | null = null;
  private fileStorage: FileStorage | null = null;
  private encryptionStorage: EncryptionStorage | null = null;

  onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding): void {
    this.context = binding.getApplicationContext();
    this.preferencesStorage = new PreferencesStorage(this.context!);
    this.fileStorage = new FileStorage(this.context!);
    this.encryptionStorage = new EncryptionStorage(this.context!);

    binding.getBinaryMessenger().setMethodCallHandler(
      this.handleMethodCall.bind(this)
    );
    hilog.info(DOMAIN, TAG, 'StorageIndex attached');
  }

  onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding): void {
    binding.getBinaryMessenger().setMethodCallHandler(null);
  }

  private async handleMethodCall(
    call: FlutterPlugin.MethodCall,
    result: FlutterPlugin.MethodResult
  ): Promise<void> {
    const method = call.method;
    const args = call.args as Record<string, Object>;

    try {
      // === Preferences Storage ===
      if (method === 'preferences.init') {
        const ok = await this.preferencesStorage!.init();
        result.success(ok);
        return;
      }
      if (method === 'preferences.putString') {
        const ok = await this.preferencesStorage!.putString(
          args['key'] as string, args['value'] as string);
        result.success(ok);
        return;
      }
      if (method === 'preferences.getString') {
        const value = await this.preferencesStorage!.getString(
          args['key'] as string, args['defaultValue'] as string || '');
        result.success(value);
        return;
      }
      if (method === 'preferences.putNumber') {
        const ok = await this.preferencesStorage!.putNumber(
          args['key'] as string, args['value'] as number);
        result.success(ok);
        return;
      }
      if (method === 'preferences.putBoolean') {
        const ok = await this.preferencesStorage!.putBoolean(
          args['key'] as string, args['value'] as boolean);
        result.success(ok);
        return;
      }
      if (method === 'preferences.delete') {
        const ok = await this.preferencesStorage!.delete(args['key'] as string);
        result.success(ok);
        return;
      }
      if (method === 'preferences.clear') {
        const ok = await this.preferencesStorage!.clear();
        result.success(ok);
        return;
      }

      // === File Storage ===
      if (method === 'file.writeText') {
        const ok = await this.fileStorage!.writeTextFile(
          args['fileName'] as string, args['content'] as string);
        result.success(ok);
        return;
      }
      if (method === 'file.readText') {
        const content = await this.fileStorage!.readTextFile(
          args['fileName'] as string);
        result.success(content);
        return;
      }
      if (method === 'file.writeJson') {
        const ok = await this.fileStorage!.writeJsonFile(
          args['fileName'] as string, args['data'] as object);
        result.success(ok);
        return;
      }
      if (method === 'file.readJson') {
        const data = await this.fileStorage!.readJsonFile(
          args['fileName'] as string);
        result.success(data);
        return;
      }
      if (method === 'file.delete') {
        const ok = await this.fileStorage!.deleteFile(args['fileName'] as string);
        result.success(ok);
        return;
      }

      // === Encryption Storage ===
      if (method === 'encrypt.encryptAndSave') {
        const ok = await this.encryptionStorage!.encryptAndSave(
          args['fileName'] as string, args['content'] as string);
        result.success(ok);
        return;
      }
      if (method === 'encrypt.loadAndDecrypt') {
        const content = await this.encryptionStorage!.loadAndDecrypt(
          args['fileName'] as string);
        result.success(content);
        return;
      }

      result.notImplemented();
    } catch (error) {
      hilog.error(DOMAIN, TAG, 'Method call failed: %{public}s',
        JSON.stringify(error));
      result.error('STORAGE_ERROR', JSON.stringify(error), null);
    }
  }
}

Plugin 注册需要在 EntryAbility 中完成:

// ArkTS 侧 - entryability/EntryAbility.ets

import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';
import { StorageIndex } from '../utils/StorageIndex';

export default class EntryAbility extends FlutterAbility {
  private storageIndex: StorageIndex = new StorageIndex();

  configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine);
    GeneratedPluginRegistrant.registerWith(flutterEngine);
    // 注册存储插件
    flutterEngine.getPlugins().add(this.storageIndex);
  }
}

五、Flutter 端存储服务封装

Flutter 端通过 MethodChannel 调用原生存储服务,对上层业务代码提供简洁的 Dart API:

// Dart 侧 - lib/storage/storage_service.dart

import 'package:flutter/services.dart';

/// 存储服务通道封装
/// Flutter 端通过 MethodChannel 调用 ArkTS 原生存储能力
class StorageService {
  static const _channel = MethodChannel('com.example.oh_demo11/storage');

  // ========================
  // Preferences Storage
  // ========================

  /// 初始化 Preferences
  static Future<bool> initPreferences() async {
    try {
      final result = await _channel.invokeMethod<bool>('preferences.init');
      return result ?? false;
    } on PlatformException catch (e) {
      print('Init preferences failed: ${e.message}');
      return false;
    }
  }

  /// 保存字符串
  static Future<bool> putString(String key, String value) async {
    try {
      final result = await _channel.invokeMethod<bool>(
        'preferences.putString',
        {'key': key, 'value': value},
      );
      return result ?? false;
    } on PlatformException catch (e) {
      print('PutString failed: ${e.message}');
      return false;
    }
  }

  /// 读取字符串
  static Future<String> getString(String key, {String defaultValue = ''}) async {
    try {
      final result = await _channel.invokeMethod<String>(
        'preferences.getString',
        {'key': key, 'defaultValue': defaultValue},
      );
      return result ?? defaultValue;
    } on PlatformException catch (e) {
      print('GetString failed: ${e.message}');
      return defaultValue;
    }
  }

  /// 保存数字
  static Future<bool> putNumber(String key, num value) async {
    try {
      final result = await _channel.invokeMethod<bool>(
        'preferences.putNumber',
        {'key': key, 'value': value},
      );
      return result ?? false;
    } on PlatformException catch (e) {
      print('PutNumber failed: ${e.message}');
      return false;
    }
  }

  /// 保存布尔值
  static Future<bool> putBoolean(String key, bool value) async {
    try {
      final result = await _channel.invokeMethod<bool>(
        'preferences.putBoolean',
        {'key': key, 'value': value},
      );
      return result ?? false;
    } on PlatformException catch (e) {
      print('PutBoolean failed: ${e.message}');
      return false;
    }
  }

  /// 删除指定键
  static Future<bool> deletePreference(String key) async {
    try {
      final result = await _channel.invokeMethod<bool>(
        'preferences.delete',
        {'key': key},
      );
      return result ?? false;
    } on PlatformException catch (e) {
      print('Delete failed: ${e.message}');
      return false;
    }
  }

  /// 清空所有 Preferences
  static Future<bool> clearPreferences() async {
    try {
      final result = await _channel.invokeMethod<bool>('preferences.clear');
      return result ?? false;
    } on PlatformException catch (e) {
      print('Clear failed: ${e.message}');
      return false;
    }
  }

  // ========================
  // File Storage
  // ========================

  /// 写入文本文件
  static Future<bool> writeTextFile(String fileName, String content) async {
    try {
      final result = await _channel.invokeMethod<bool>(
        'file.writeText',
        {'fileName': fileName, 'content': content},
      );
      return result ?? false;
    } on PlatformException catch (e) {
      print('WriteText failed: ${e.message}');
      return false;
    }
  }

  /// 读取文本文件
  static Future<String> readTextFile(String fileName) async {
    try {
      final result = await _channel.invokeMethod<String>(
        'file.readText',
        {'fileName': fileName},
      );
      return result ?? '';
    } on PlatformException catch (e) {
      print('ReadText failed: ${e.message}');
      return '';
    }
  }

  /// 写入 JSON 文件
  static Future<bool> writeJsonFile(String fileName, Map<String, dynamic> data) async {
    try {
      final result = await _channel.invokeMethod<bool>(
        'file.writeJson',
        {'fileName': fileName, 'data': data},
      );
      return result ?? false;
    } on PlatformException catch (e) {
      print('WriteJson failed: ${e.message}');
      return false;
    }
  }

  /// 读取 JSON 文件
  static Future<Map<String, dynamic>?> readJsonFile(String fileName) async {
    try {
      final result = await _channel.invokeMethod<Map<dynamic, dynamic>>(
        'file.readJson',
        {'fileName': fileName},
      );
      if (result == null) return null;
      return Map<String, dynamic>.from(result);
    } on PlatformException catch (e) {
      print('ReadJson failed: ${e.message}');
      return null;
    }
  }

  /// 删除文件
  static Future<bool> deleteFile(String fileName) async {
    try {
      final result = await _channel.invokeMethod<bool>(
        'file.delete',
        {'fileName': fileName},
      );
      return result ?? false;
    } on PlatformException catch (e) {
      print('DeleteFile failed: ${e.message}');
      return false;
    }
  }

  // ========================
  // Encryption Storage
  // ========================

  /// 加密保存
  static Future<bool> encryptAndSave(String fileName, String content) async {
    try {
      final result = await _channel.invokeMethod<bool>(
        'encrypt.encryptAndSave',
        {'fileName': fileName, 'content': content},
      );
      return result ?? false;
    } on PlatformException catch (e) {
      print('EncryptAndSave failed: ${e.message}');
      return false;
    }
  }

  /// 加载并解密
  static Future<String> loadAndDecrypt(String fileName) async {
    try {
      final result = await _channel.invokeMethod<String>(
        'encrypt.loadAndDecrypt',
        {'fileName': fileName},
      );
      return result ?? '';
    } on PlatformException catch (e) {
      print('LoadAndDecrypt failed: ${e.message}');
      return '';
    }
  }
}

Flutter 端使用示例

// 初始化
await StorageService.initPreferences();

// Preferences 使用
await StorageService.putString('username', 'john_doe');
await StorageService.putBoolean('dark_mode', true);
await StorageService.putNumber('font_size', 16);

final username = await StorageService.getString('username');
await StorageService.deletePreference('username');
await StorageService.clearPreferences();

// 文件存储使用
await StorageService.writeTextFile('note.txt', '这是一条笔记');
await StorageService.writeJsonFile('user.json', {
  'name': '张三',
  'age': 25,
  'scores': [90, 85, 92]
});
final content = await StorageService.readTextFile('note.txt');
final userData = await StorageService.readJsonFile('user.json');

// 加密存储使用
await StorageService.encryptAndSave('passwords', 'MyP@ssw0rd!');
final decrypted = await StorageService.loadAndDecrypt('passwords');

六、Flutter UI 演示页面

Flutter 端提供三 Tab 页面,分别演示三种存储能力的操作界面:

// Dart 侧 - lib/storage/pages/storage_demo_page.dart

import 'package:flutter/material.dart';
import '../storage_service.dart';

class StorageDemoPage extends StatefulWidget {
  const StorageDemoPage({super.key});

  
  State<StorageDemoPage> createState() => _StorageDemoPageState();
}

class _StorageDemoPageState extends State<StorageDemoPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  // === Preferences 状态 ===
  final _prefKeyController = TextEditingController(text: 'username');
  final _prefValueController = TextEditingController(text: 'admin');
  String _prefResult = '';

  // === File 状态 ===
  final _fileNameController = TextEditingController(text: 'note.txt');
  final _fileContentController = TextEditingController(
      text: 'Hello, OpenHarmony Storage!');
  String _fileResult = '';

  // === Encryption 状态 ===
  final _encryptKeyController = TextEditingController(text: 'secret_data');
  final _encryptContentController = TextEditingController(
      text: 'Sensitive information');
  String _encryptResult = '';

  
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
    _initStorage();
  }

  Future<void> _initStorage() async {
    await StorageService.initPreferences();
  }

  
  void dispose() {
    _tabController.dispose();
    _prefKeyController.dispose();
    _prefValueController.dispose();
    _fileNameController.dispose();
    _fileContentController.dispose();
    _encryptKeyController.dispose();
    _encryptContentController.dispose();
    super.dispose();
  }

  // ========================
  // Preferences 操作
  // ========================
  Future<void> _savePreference() async {
    setState(() => _prefResult = 'Saving...');
    final ok = await StorageService.putString(
      _prefKeyController.text,
      _prefValueController.text,
    );
    setState(() {
      _prefResult = ok
          ? 'Saved: "${_prefKeyController.text}" = "${_prefValueController.text}"'
          : 'Save failed';
    });
  }

  Future<void> _loadPreference() async {
    setState(() => _prefResult = 'Loading...');
    final value = await StorageService.getString(_prefKeyController.text);
    setState(() {
      _prefResult = value.isNotEmpty
          ? 'Loaded: "${_prefKeyController.text}" = "$value"'
          : 'Key not found';
    });
  }

  Future<void> _deletePreference() async {
    setState(() => _prefResult = 'Deleting...');
    final ok = await StorageService.deletePreference(_prefKeyController.text);
    setState(() {
      _prefResult = ok ? 'Deleted: "${_prefKeyController.text}"' : 'Delete failed';
    });
  }

  Future<void> _clearPreferences() async {
    setState(() => _prefResult = 'Clearing...');
    final ok = await StorageService.clearPreferences();
    setState(() {
      _prefResult = ok ? 'All preferences cleared' : 'Clear failed';
    });
  }

  // ========================
  // File 操作
  // ========================
  Future<void> _saveText() async {
    setState(() => _fileResult = 'Saving...');
    final ok = await StorageService.writeTextFile(
      _fileNameController.text,
      _fileContentController.text,
    );
    setState(() {
      _fileResult = ok
          ? 'File "${_fileNameController.text}" saved'
          : 'Save failed';
    });
  }

  Future<void> _loadText() async {
    setState(() => _fileResult = 'Loading...');
    final content = await StorageService.readTextFile(_fileNameController.text);
    setState(() {
      _fileResult = content.isNotEmpty
          ? 'Content: $content'
          : 'File not found';
    });
  }

  Future<void> _saveJson() async {
    setState(() => _fileResult = 'Saving JSON...');
    final ok = await StorageService.writeJsonFile(
      _fileNameController.text,
      {'name': '张三', 'age': 25, 'scores': [90, 85, 92]},
    );
    setState(() {
      _fileResult = ok
          ? 'JSON file "${_fileNameController.text}" saved'
          : 'Save failed';
    });
  }

  Future<void> _loadJson() async {
    setState(() => _fileResult = 'Loading JSON...');
    final data = await StorageService.readJsonFile(_fileNameController.text);
    setState(() {
      _fileResult = data != null
          ? 'JSON: ${data.toString()}'
          : 'File not found';
    });
  }

  Future<void> _deleteFile() async {
    setState(() => _fileResult = 'Deleting...');
    final ok = await StorageService.deleteFile(_fileNameController.text);
    setState(() {
      _fileResult = ok ? 'File deleted' : 'Delete failed';
    });
  }

  // ========================
  // Encryption 操作
  // ========================
  Future<void> _encryptAndSave() async {
    setState(() => _encryptResult = 'Encrypting...');
    final ok = await StorageService.encryptAndSave(
      _encryptKeyController.text,
      _encryptContentController.text,
    );
    setState(() {
      _encryptResult = ok
          ? 'Encrypted and saved: "${_encryptKeyController.text}"'
          : 'Encrypt failed';
    });
  }

  Future<void> _loadAndDecrypt() async {
    setState(() => _encryptResult = 'Decrypting...');
    final content = await StorageService.loadAndDecrypt(
      _encryptKeyController.text,
    );
    setState(() {
      _encryptResult = content.isNotEmpty
          ? 'Decrypted: $content'
          : 'Data not found';
    });
  }

  Future<void> _encryptJson() async {
    setState(() => _encryptResult = 'Encrypting JSON...');
    final data = '{"username":"admin","password":"P@ssw0rd!"}';
    final ok = await StorageService.encryptAndSave(
      _encryptKeyController.text,
      data,
    );
    setState(() {
      _encryptResult = ok
          ? 'JSON encrypted: "${_encryptKeyController.text}"'
          : 'Encrypt failed';
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Storage Demo'),
        backgroundColor: Colors.teal,
        foregroundColor: Colors.white,
        bottom: TabBar(
          controller: _tabController,
          indicatorColor: Colors.white,
          labelColor: Colors.white,
          unselectedLabelColor: Colors.white70,
          tabs: const [
            Tab(text: 'Preferences', icon: Icon(Icons.settings)),
            Tab(text: 'File', icon: Icon(Icons.folder)),
            Tab(text: 'Encryption', icon: Icon(Icons.lock)),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          _buildPreferencesTab(),
          _buildFileTab(),
          _buildEncryptionTab(),
        ],
      ),
    );
  }

  Widget _buildPreferencesTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          _buildSectionTitle('Preferences Storage', Icons.settings),
          const SizedBox(height: 12),
          TextField(
            controller: _prefKeyController,
            decoration: const InputDecoration(
              labelText: 'Key',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.key),
            ),
          ),
          const SizedBox(height: 12),
          TextField(
            controller: _prefValueController,
            decoration: const InputDecoration(
              labelText: 'Value',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.text_fields),
            ),
          ),
          const SizedBox(height: 16),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              ElevatedButton.icon(
                onPressed: _savePreference,
                icon: const Icon(Icons.save),
                label: const Text('Save'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.teal,
                  foregroundColor: Colors.white,
                ),
              ),
              ElevatedButton.icon(
                onPressed: _loadPreference,
                icon: const Icon(Icons.download),
                label: const Text('Load'),
              ),
              ElevatedButton.icon(
                onPressed: _deletePreference,
                icon: const Icon(Icons.delete),
                label: const Text('Delete'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.orange,
                  foregroundColor: Colors.white,
                ),
              ),
              ElevatedButton.icon(
                onPressed: _clearPreferences,
                icon: const Icon(Icons.clear_all),
                label: const Text('Clear All'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  foregroundColor: Colors.white,
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          _buildResultCard(_prefResult),
        ],
      ),
    );
  }

  Widget _buildFileTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          _buildSectionTitle('File Storage', Icons.folder),
          const SizedBox(height: 12),
          TextField(
            controller: _fileNameController,
            decoration: const InputDecoration(
              labelText: 'File Name',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.insert_drive_file),
            ),
          ),
          const SizedBox(height: 12),
          TextField(
            controller: _fileContentController,
            maxLines: 3,
            decoration: const InputDecoration(
              labelText: 'Content',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.notes),
            ),
          ),
          const SizedBox(height: 16),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              ElevatedButton.icon(
                onPressed: _saveText,
                icon: const Icon(Icons.save),
                label: const Text('Save Text'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.teal,
                  foregroundColor: Colors.white,
                ),
              ),
              ElevatedButton.icon(
                onPressed: _loadText,
                icon: const Icon(Icons.download),
                label: const Text('Load Text'),
              ),
              ElevatedButton.icon(
                onPressed: _saveJson,
                icon: const Icon(Icons.data_object),
                label: const Text('Save JSON'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.blue,
                  foregroundColor: Colors.white,
                ),
              ),
              ElevatedButton.icon(
                onPressed: _loadJson,
                icon: const Icon(Icons.download),
                label: const Text('Load JSON'),
              ),
              ElevatedButton.icon(
                onPressed: _deleteFile,
                icon: const Icon(Icons.delete),
                label: const Text('Delete'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  foregroundColor: Colors.white,
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          _buildResultCard(_fileResult),
        ],
      ),
    );
  }

  Widget _buildEncryptionTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          _buildSectionTitle('Encryption Storage (AES-256)', Icons.lock),
          const SizedBox(height: 12),
          TextField(
            controller: _encryptKeyController,
            decoration: const InputDecoration(
              labelText: 'File Key',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.key),
            ),
          ),
          const SizedBox(height: 12),
          TextField(
            controller: _encryptContentController,
            maxLines: 3,
            decoration: const InputDecoration(
              labelText: 'Sensitive Data',
              border: OutlineInputBorder(),
              prefixIcon: Icon(Icons.security),
            ),
          ),
          const SizedBox(height: 16),
          Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              ElevatedButton.icon(
                onPressed: _encryptAndSave,
                icon: const Icon(Icons.lock),
                label: const Text('Encrypt & Save'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.deepPurple,
                  foregroundColor: Colors.white,
                ),
              ),
              ElevatedButton.icon(
                onPressed: _loadAndDecrypt,
                icon: const Icon(Icons.lock_open),
                label: const Text('Load & Decrypt'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.purple,
                  foregroundColor: Colors.white,
                ),
              ),
              ElevatedButton.icon(
                onPressed: _encryptJson,
                icon: const Icon(Icons.data_object),
                label: const Text('Encrypt JSON'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.indigo,
                  foregroundColor: Colors.white,
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          _buildResultCard(_encryptResult),
        ],
      ),
    );
  }

  Widget _buildSectionTitle(String title, IconData icon) {
    return Row(
      children: [
        Icon(icon, color: Colors.teal, size: 24),
        const SizedBox(width: 8),
        Text(
          title,
          style: const TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.bold,
            color: Colors.black87,
          ),
        ),
      ],
    );
  }

  Widget _buildResultCard(String result) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: result.isEmpty ? Colors.grey[100] : Colors.teal[50],
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.teal[200]!),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Icon(Icons.info_outline, size: 16, color: Colors.teal[700]),
              const SizedBox(width: 6),
              Text(
                'Result',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  color: Colors.teal[700],
                  fontSize: 13,
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          Text(
            result.isEmpty ? 'No operation yet' : result,
            style: TextStyle(
              fontSize: 14,
              color: result.isEmpty ? Colors.grey : Colors.black87,
              fontFamily: 'monospace',
            ),
          ),
        ],
      ),
    );
  }
}

七、存储能力对比与选型建议

在实际项目中,需要根据数据特点选择合适的存储方式:

存储类型 适用场景 数据量级 持久化 加密 性能
Preferences 用户设置、开关状态 轻量(KB 级) 最高
File Storage 笔记、配置、结构化数据 中等(MB 级)
Encryption Storage 密码、Token、私密数据 轻量(KB 级) AES-256 中等

选型建议

  • 用户偏好和配置信息优先使用 Preferences,API 简洁且性能最优。
  • JSON 结构和文档数据使用 File Storage,配合 JSON 序列化实现结构化存储。
  • 敏感信息必须使用 Encryption Storage,即使数据泄露也无法被解读。
  • 组合使用是最常见做法:用户偏好存 Preferences、文档内容存 File Storage、登录凭证存 Encryption Storage。

八、代码仓库与资源

本文完整代码已上传至 AtomGit 仓库:

仓库地址:https://atomgit.com/maaath/oh_demo11

主要文件对应关系:

Flutter 端 (Dart) ArkTS 原生侧 说明
lib/storage/storage_service.dart entry/src/main/ets/utils/StorageIndex.ets Platform Channel 封装
entry/src/main/ets/utils/PreferencesStorage.ets 偏好设置存储
entry/src/main/ets/utils/FileStorage.ets 文件存储
entry/src/main/ets/utils/EncryptionStorage.ets 加密存储
lib/storage/pages/storage_demo_page.dart entry/src/main/ets/pages/StorageDemo.ets 演示 UI 页面
entry/src/main/ets/entryability/EntryAbility.ets Flutter 引擎入口

九、截图验证板块

请在下方查看应用在鸿蒙设备上的运行截图:

  1. Flutter 端 Preferences Tab — 展示 Key 输入框、Value 输入框、Save/Load/Delete/Clear All 四个操作按钮,Result 区域显示操作结果
    在这里插入图片描述
    视频演示:

    本地储存功能

  2. Flutter 端 File Storage Tab — 展示 File Name 输入框、Content 多行输入框、Save Text/Load Text/Save JSON/Load JSON/Delete 五个操作按钮,Result 区域显示文件读写结果。
    在这里插入图片描述

  3. Flutter 端 Encryption Tab — 展示 File Key 输入框、Sensitive Data 多行输入框、Encrypt & Save/Load & Decrypt/Encrypt JSON 三个操作按钮,Result 区域显示加密解密结果。
    在这里插入图片描述


十、总结

本文完整介绍了在 Flutter for OpenHarmony 跨平台工程中集成本地存储能力的方案,核心主线是 Flutter 端通过 Platform Channel 调用 ArkTS 原生存储服务

  • 架构分层:Flutter UI → MethodChannel → ArkTS 存储服务 → OpenHarmony 系统 API,四层职责清晰。
  • 三类存储:Preferences(轻量键值)、File Storage(文件读写)、Encryption Storage(AES-256 加密),覆盖了应用存储的主要需求。
  • ArkTS 原生实现:完整使用 @ohos.data.preferences@ohos.file.fs@ohos.security.cryptoFramework 三大系统 API。
  • Flutter 端封装:统一的 StorageService 类,隐藏 Platform Channel 细节,提供简洁的 Dart API。
  • 安全性:敏感数据通过 AES-256-CBC 加密存储,随机 IV 防止密文分析,密码不硬编码。

这套方案充分发挥了 Flutter 跨平台 UI 开发效率与 OpenHarmony 原生存储能力各自的优势,可直接嵌入任何 FOH 项目中,为应用赋予完整、安全、高效的本地数据持久化能力。


Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐