开发工具: 华为云码道

本文配套仓库: 上游 m-abdulmonem/file-encryptor;鸿蒙适配改动位于本地仓库的 ohos/example/ohos/README.OpenHarmony_CN.mdREADME.OpenHarmony.mdCHANGELOG.OpenHarmony.mddocs/ohos-test/
鸿蒙适配后仓库https://atomgit.com/oh-flutter/file-encryptor

file_encryptor 把文件加解密封装成两个 Dart 方法:encrypt(path, content) 使用 AES-128-CBC 加密文本并以 base64 写入 .aes 文件,decrypt(path) 读取该文件并还原原文。Dart 层不维护平台通道,而是直接调用 dart:iopath_provider 获取应用沙箱目录;OHOS 原生侧只需注册 file_encryptor 通道并保留 getPlatformVersion 契约。本文以 file_encryptor 0.0.1 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 真机运行。

插件目前支持 ohos 平台,上游源码位于 GitHub 仓库。文中的代码以上游 main 分支提交 dcbea1a09aca848aaa8d154c0ef88b19eb709d89 为适配基线。

在这里插入图片描述
OHOS 适配https://atomgit.com/oh-flutter/file-encryptor改动当前在 main 分支工作区,发布 TAG 为 0.0.1-ohos-1.0.0-beta.1

在这里插入图片描述


Example 启动授权 复制后显示剪贴板内容 获取复制后显示剪贴板内容

操作预期表现
输入文本并点击 Encrypt生成 ${path}.aes 文件,状态显示文件大小与 base64 密文
点击 Decrypt读取同一文件并还原为原文
进程重启后 Decrypt 旧文件抛出异常(key/iv 已重新生成,继承自上游限制)

以下是操作的视屏,可以参考一下:

Example 启动授权


一、插件简介与适配目标

本地敏感数据的落盘保护是移动应用常见需求。file_encryptor 把 AES 文件加解密封装成两个 Future 方法:encrypt(path, content) 使用 AES-128-CBC 对文本加密,再以 base64 编码写入 ${path}.aes 文件;decrypt(path) 读取该文件并还原原文。Dart 层没有自建加密通道,而是依赖纯 Dart 的 encrypt 包完成算法,依赖 path_provider 获取应用沙箱目录,依赖 dart:io 完成文件写入。

例如,用户输入一段文本后点击 Encrypt,插件把密文保存到应用文档目录;点击 Decrypt 后读取同一文件并还原,方便直观验证加解密一致性。

正因如此,这个插件的 OHOS 适配工作集中在三点:在 pubspec.yaml 声明 ohos 平台并生成 HAR 模块;补充 OHOS 路径提供能力 path_provider_ohos;原生侧保留与 Android/iOS 一致的通道契约——注册 file_encryptor 通道,并让 getPlatformVersion 返回与两端格式一致的系统版本字符串。


二、环境准备

环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。

完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:

flutter --version
flutter doctor -v
hdc list targets

在这里插入图片描述

在这里插入图片描述

编辑用户

工程使用的工具链和 SDK 配置如下:

项目版本或配置用途
Flutter OHOS SDK3.44.9+ohos-0.0.1-canary1Flutter 编译与 OHOS 平台工具链
Flutter 分支0.0.1-ohos-1.0.0-beta.1CPF-Flutter 对应开发分支
Dart SDK3.12.2Dart 语言与包管理环境
HarmonyOS 开发套件26.0.0(API 26)开发套件版本及对应的 API 级别
compatibleSdkVersion5.1.0(18)当前工程声明的最低兼容版本
插件版本0.0.1pubspec.yaml 中的包版本
原生语言ArkTSHarmonyOS 插件实现
插件产物HAR被应用 entry 模块依赖

2.1 开发套件版本与工程中的 SDK 版本配置

26.0.0(API 26) 涉及 HarmonyOS 开发套件版本(API 版本)及其底座 OpenHarmony 的版本号体系。在本文工程中,相关版本的含义与配置方式如下:

  • 26.0.0(API 26) 表示本机 DevEco Studio 安装的开发套件为 26.0.0,对应 API 26,Flutter 工具链构建时使用该 SDK;
  • 本工程的 example/ohos/build-profile.json5 没有显式声明 compileSdkVersiontargetSdkVersion,构建时按开发套件默认的 API 26 编译;
  • 5.1.0(18) 是本文工程中 compatibleSdkVersion 的属性值,声明最低兼容 API 18。

对应的 product 配置为:

{
  "name": "default",
  "signingConfig": "default",
  "compatibleSdkVersion": "5.1.0(18)",
  "runtimeOS": "HarmonyOS"
}

在这里插入图片描述

这组配置最低兼容 API 18。本插件的加解密算法为纯 Dart 实现,不依赖系统能力,因此其他 API 版本同样可用,只要 Flutter OH 工具链与 path_provider_ohos 兼容即可。

三、从源码仓库开始准备适配工程

3.1 将上游源码同步到 AtomGit

适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。

在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yamlLICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。

file_encryptor 的上游位于 GitHub,采用 BSD 三条款许可证。可以按上文流程把上游仓库导入 AtomGit 或其他 Git 托管平台,获得自己有写权限的工作仓库;本文直接从 GitHub 地址拉取代码。

3.2 将代码拉取到宿主机

在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:

git clone https://github.com/m-abdulmonem/file-encryptor.git
cd file-encryptor
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

在这里插入图片描述

git clone 会创建 file-encryptor/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yamllib/example/。本例的仓库名与 Dart 包名相同,都是 file_encryptor

需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:

git switch --detach dcbea1a09aca848aaa8d154c0ef88b19eb709d89

适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

在这里插入图片描述

图 1:在宿主机终端输入仓库拉取命令。

3.3 在仓库根目录创建适配分支

接着在 file-encryptor/ 根目录创建分支。分支名统一使用 feat/ohos_库名称_版本号,库名称取 pubspec.yamlname,版本号取此次适配的基线版本。本例为:

git switch -c feat/ohos_file_encryptor_0.0.1
git branch --show-current

如果该分支已存在,使用 git switch feat/ohos_file_encryptor_0.0.1 切换即可。上游仓库的 main 分支是当前主线,没有打过 tag,本次适配以 main 的提交 dcbea1a 为基线。

请添加图片描述

图 2:在 file-encryptor 仓库根目录输入适配分支创建命令。

3.4 自动补全 OHOS 适配结构

分支创建后,仍在同一个插件根目录执行结构补全。以下命令适用于尚无 ohos/ 目录的既有 Flutter 平台插件

flutter create --template=plugin --platforms=ohos --project-name file_encryptor .
git status --short
git diff -- pubspec.yaml .gitignore
  • --template=plugin 指定插件模板。
  • --platforms=ohos 指定需要补全的平台。
  • --project-name file_encryptor 使用 Dart 包名,与 pubspec.yaml 中的 name 保持一致。
  • 最后的 . 表示在当前插件目录补全工程,不是另建一层 file-encryptor/

本例执行后 git status --short 的输出为:

 M example/ios/Flutter/Debug.xcconfig
 M example/ios/Flutter/Release.xcconfig
 M example/lib/main.dart
 M example/linux/flutter/generated_plugins.cmake
 M example/macos/Flutter/Flutter-Debug.xcconfig
 M example/macos/Flutter/Flutter-Release.xcconfig
 M example/macos/Flutter/GeneratedPluginRegistrant.swift
 M example/pubspec.lock
 M pubspec.yaml
 M CHANGELOG.OpenHarmony.md
 M README.OpenHarmony.md
 M README.OpenHarmony_CN.md
 M docs/
 M example/ios/Podfile
 M example/ohos/
 M example/macos/Podfile
 M ohos/

pubspec.yaml 新增了 ohos: pluginClass: FileEncryptorPlugin 两行,并追加 path_provider_ohos: ^2.2.1 依赖;.gitignore 追加了 ohos/ 构建产物和本机缓存的忽略规则;ohos/example/ohos/ 是新生成的 HAR 脚手架和宿主工程;README.OpenHarmony_CN.mdREADME.OpenHarmony.mdCHANGELOG.OpenHarmony.mddocs/ohos-test/ 是本次适配新增的文档与测试截图;example/ios/Podfile、两份 xcconfig 与 macOS 工程变化来自模板对 iOS/macOS 示例工程的同步更新。生成后通过 diff 检查变化,保留已有 API、其他平台注册项及依赖配置。不同 Flutter OH 版本生成的模板可能略有差异。

如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:

cd example
flutter create --platforms=ohos .
cd ..

配套仓库已经包含 ohos/example/ohos/,直接运行示例时可以跳过结构补全。新建插件则使用 flutter create --org com.mabdulmonem --template=plugin --platforms=ohos file_encryptor;已有插件使用上面的 . 在当前目录补全。

请添加图片描述

图 3:在插件根目录输入 OHOS 结构补全命令。

3.5 适配后的项目目录

适配后的关键目录如下:

file_encryptor/
├── lib/
│   └── file_encryptor.dart
├── ohos/
│   ├── index.ets
│   ├── oh-package.json5
│   ├── build-profile.json5
│   ├── hvigorfile.ts
│   └── src/main/
│       ├── ets/components/plugin/FileEncryptorPlugin.ets
│       └── module.json5
├── example/
│   ├── lib/main.dart
│   └── ohos/entry/
├── test/
│   ├── file_encryptor_test.dart
│   └── test_file.aes
├── android/
├── ios/
├── macos/
├── windows/
├── linux/
├── docs/
│   └── ohos-test/
│       ├── 01-app-home.jpeg
│       ├── 02-encrypted.jpeg
│       └── 03-decrypted.jpeg
└── pubspec.yaml

项目根目录如下,其中包含 ohos/example/ohos/、已补齐的 README.OpenHarmony_CN.mdCHANGELOG.OpenHarmony.md,以及真机测试截图目录 docs/ohos-test/

请添加图片描述

图 4:适配后的 file_encryptor 项目根目录。

文件主要职责
lib/file_encryptor.dart提供 encrypt / decrypt 方法,AES 加解密与文件读写
FileEncryptorPlugin.ets注册 file_encryptor 通道,保持与 Android/iOS 一致的命令契约
插件 module.json5声明 HAR 模块
示例 entry module.json5声明宿主应用 Ability、设备类型和 INTERNET 权限
example/lib/main.dart完整 Demo:输入文本、Encrypt、Decrypt、展示密文与文件信息
docs/ohos-test/真机运行截图与测试日志

四、Dart 接口与通道分析

OHOS 适配要分清两条路径的职责:加解密与文件读写完全在 Dart 层完成;插件自有通道 file_encryptor 只保留命令契约。先阅读 lib/file_encryptor.dart 和 Android/iOS 的原生实现,再在 ohos/src/main/ets/components/plugin/ 中实现对应的原生类。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。

本例的对应关系如下:

Dart 入口或模型实际依赖OHOS 实现应保持的行为
FileEncryptor.encrypt(path, content)encrypt 包 + dart:io + path_provider纯 Dart / path_provider_ohos写入 ${path}.aes 密文文件
FileEncryptor.decrypt(path)encrypt 包 + dart:io纯 Dart读取 ${path}.aes 并还原原文
getPlatformVersion(模板探活)插件通道 file_encryptorFileEncryptorPlugin.ets 返回 'OpenHarmony ' + displayVersion与 Android/iOS 返回格式一致

通道名和命令名属于跨语言协议。任何一端拼写不一致,都会让探活命令失效或出现 MissingPluginException

4.1 跨端架构与调用时序

Flutter 侧和 HarmonyOS 侧之间有两个参与者,但它们互不交叉:

  1. 业务数据流:FileEncryptor 直接调用 encrypt 包和 dart:io,路径由 path_provider 提供,不经过插件通道;
  2. 插件自有通道 file_encryptor:Dart 侧当前业务代码并不调用它,原生侧注册后只响应 getPlatformVersion,用于保持与 Android/iOS 一致的插件契约。

探活

getPlatformVersion

Flutter 页面

FileEncryptor

encrypt 包 AES-128-CBC

path_provider 获取目录

dart:io 读写 .aes 文件

MethodChannel file_encryptor

FileEncryptorPlugin.ets

插件通道只承担契约保留,真正的加解密和文件读写完全在 Dart 层完成。

4.1.1 一次完整加密与解密的时序
dart:io path_provider encrypt (AES) FileEncryptor Flutter App dart:io path_provider encrypt (AES) FileEncryptor Flutter App encrypt(path, content) getApplicationDocumentsDirectory() appDocDir.path encrypt(content, iv) base64 cipher write ${path}.aes file.exists() decrypt(path) read ${path}.aes base64 cipher decrypt64(cipher, iv) plain text 返回原文

4.2 工具类模型:lib/file_encryptor.dart

FileEncryptor 是纯 Dart 工具类,没有状态和生命周期:

class FileEncryptor {
  static Key key = Key.fromSecureRandom(16);
  static IV iv = IV.fromSecureRandom(16);

  Encrypter get encrypter => Encrypter(AES(key));

  Future<bool> encrypt(String path, String content) async {
    final file = File("$path.aes");
    file.writeAsString(encrypter.encrypt(content, iv: iv).base64);
    return file.exists();
  }

  Future<String> decrypt(String path) async {
    final file = File("$path.aes");
    final data = await file.readAsString();
    return encrypter.decrypt64(data, iv: iv);
  }
}
成员签名行为
key / ivstatic Key / static IV进程内静态随机值,同一进程内加解密可互逆
encryptFuture<bool> encrypt(String path, String content)加密后写入 ${path}.aes,返回文件是否生成
decryptFuture<String> decrypt(String path)读取 ${path}.aes 并还原原文;文件不存在或内容异常时抛出异常

边界处理值得注意:encrypt 直接调用 writeAsString,不会等待写入完成就检查 exists(),在 IO 极慢时可能返回不准确结果;decrypt 未检查文件存在性,文件不存在时由 File.readAsString 抛出异常。调用方应使用 try/catch 包裹。另外,由于 keyiv 是进程内静态随机值,进程重启后无法解密历史文件,这一限制继承自上游库。

4.3 公开 API 与平台接口

公开 API 只有两个方法,库没有单独的平台接口层(没有 platform_interface / method_channel 文件):

Future<bool> encrypt(String path, String content) async { ... }
Future<String> decrypt(String path) async { ... }
  • 两者都是 async,内部 await 文件 IO 和加密操作后返回;
  • 调用时机完全由业务决定:encrypt 在用户点击加密按钮时调用一次;decrypt 在需要读取时调用,立即返回当前文件内容;
  • 没有 Stream、回调或需要取消的订阅,重复调用互不影响。

pubspec.yaml 中的多端 pluginClass: FileEncryptorPlugin 只用于原生插件注册,与这两个方法的调用路径无关。真正的跨平台依赖是 path_provider(在 OHOS 上由 path_provider_ohos 实现)。

4.4 Dart 通道协议分析

4.4.1 通道名称必须两端完全一致

插件自有通道的名称三端完全一致,都是 file_encryptor

// Android
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "file_encryptor")
// iOS
let channel = FlutterMethodChannel(name: "file_encryptor", binaryMessenger: registrar.messenger())
// OHOS
this.channel = new MethodChannel(binding.getBinaryMessenger(), "file_encryptor");

当前 Dart 业务代码并不调用这条通道(加解密走纯 Dart 路径),但测试模板和未来扩展都依赖这一契约,OHOS 侧注册时必须与两端拼写一致。

4.4.2 命令处理与契约保留

原生侧的命令处理保持与 Android/iOS 相同的语义——只实现 getPlatformVersion,其余返回 notImplemented

// Android 返回值,作为对照
result.success("Android ${android.os.Build.VERSION.RELEASE}")
// OHOS
onMethodCall(call: MethodCall, result: MethodResult): void {
  try {
    if (call.method == "getPlatformVersion") {
      result.success("OpenHarmony " + deviceInfo.displayVersion);
    } else {
      result.notImplemented();
    }
  } catch (e) {
    hilog.error(DOMAIN, TAG, 'onMethodCall failed, method=%{public}s, error=%{public}s',
      call.method, JSON.stringify(e));
    result.error("file_encryptor_error", `handle method "${call.method}" failed`, e);
  }
}

版本字符串带平台前缀,与 Android 的 "Android "、iOS 的 "iOS " 格式对齐。每个方法调用都被 try/catch 包裹,原生侧异常被转换成 result.error 回到 Dart,并通过 hilog 输出日志,而不是让插件崩溃。

4.4.3 解绑与清理

插件没有需要取消的订阅。Engine 解绑时,onDetachedFromEngine 清理通道处理器:

onDetachedFromEngine(binding: FlutterPluginBinding): void {
  if (this.channel != null) {
    this.channel.setMethodCallHandler(null);
    this.channel = null;
  }
}

这一清理覆盖业务未主动释放的情况;file_encryptor 通道上也没有需要持久保存的原生状态。

五、补全 OHOS 原生实现与工程配置

5.1 在 FileEncryptorPlugin.ets 中实现原生能力

业务仍使用 FileEncryptor.encrypt / FileEncryptor.decrypt,加解密和文件读写由 Dart 层承担,不需要插件写任何 OHOS 文件或加密代码。需要补全的是 FileEncryptorPlugin.ets 中的通道注册与命令契约:注册 file_encryptor 通道,让 getPlatformVersion 返回与 Android/iOS 格式一致的系统版本字符串,并把所有调用置于异常保护之下。

FileEncryptorPlugin 实现 FlutterPluginMethodCallHandler,没有平台视图,也不需要 AbilityAware

原生插件位于(本库只有一个原生文件):

ohos/src/main/ets/components/plugin/FileEncryptorPlugin.ets
5.1.1 引入 Flutter 和 HarmonyOS 能力
import deviceInfo from '@ohos.deviceInfo';
import { hilog } from '@kit.PerformanceAnalysisKit';
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';

其中:

  • FlutterPlugin 负责接入 Flutter Engine 生命周期;
  • MethodChannelMethodCallMethodCallHandlerMethodResult 组成命令处理的完整链路;
  • deviceInfo 提供 displayVersion 系统版本名,用于拼装 getPlatformVersion 的返回值;
  • hilog 用于在异常时输出原生侧日志。

与文件或加密相关的 ArkTS 导入一个都没有:加解密不走插件,插件也不直接依赖 @ohos.file.fs 或加密 API。

5.1.2 连接 Flutter Engine
getUniqueClassName(): string {
  return "FileEncryptorPlugin";
}

onAttachedToEngine(binding: FlutterPluginBinding): void {
  this.channel = new MethodChannel(binding.getBinaryMessenger(), "file_encryptor");
  this.channel.setMethodCallHandler(this);
}

Engine 启动时创建通道并注册处理器,通道名与 Android/iOS 一致。getUniqueClassName 返回类名,供引擎侧的插件管理使用。

5.1.3 保持版本命令契约

版本命令的契约是“平台名 + 系统版本”:

onMethodCall(call: MethodCall, result: MethodResult): void {
  // ...
  if (call.method == "getPlatformVersion") {
    result.success("OpenHarmony " + deviceInfo.displayVersion);
  }
  // ...
}

三端对照如下:

平台返回值
Android"Android " + Build.VERSION.RELEASE
iOS"iOS " + UIDevice.current.systemVersion
OHOS"OpenHarmony " + deviceInfo.displayVersion

displayVersion 来自 @ohos.deviceInfo,用于获取面向用户的系统版本显示名。

5.1.4 Dart 层承担加解密与文件读写

加解密能力的真实承担者不是插件,而是 Dart 层的 encrypt 包与 dart:io。Dart 侧 FileEncryptor.encrypt / FileEncryptor.decrypt 直接操作文件:

final file = File("$path.aes");
file.writeAsString(encrypter.encrypt(content, iv: iv).base64);

路径由 path_provider 提供。在 OHOS 上,path_provider 的能力由 path_provider_ohos 插件实现,因此 pubspec.yaml 需要追加依赖:

dependencies:
  path_provider: ^2.0.11
  path_provider_ohos: ^2.2.1

这意味着:

  • 插件原生代码不 import 任何 ArkTS 文件或加密 API,也不处理文件消息;
  • 只要 path_provider_ohos 能正确返回应用文档目录,加解密就能工作;
  • OHOS 侧需要让 Flutter 工具在 GeneratedPluginRegistrant 中同时注册 FileEncryptorPluginPathProviderPlugin

验证这条链路是否通畅,比给插件堆原生代码更重要:适配完成后在真机上执行 8.5 的加密解密步骤即可确认。

5.1.5 处理命令与异常保护

onMethodCall 的完整实现把命令分发包进 try/catch

onMethodCall(call: MethodCall, result: MethodResult): void {
  try {
    if (call.method == "getPlatformVersion") {
      result.success("OpenHarmony " + deviceInfo.displayVersion);
    } else {
      result.notImplemented();
    }
  } catch (e) {
    hilog.error(DOMAIN, TAG, 'onMethodCall failed, method=%{public}s, error=%{public}s',
      call.method, JSON.stringify(e));
    result.error("file_encryptor_error", `handle method "${call.method}" failed`, e);
  }
}
  • 未知命令一律 result.notImplemented(),与上游 Android/iOS 行为一致,Dart 侧会收到 MissingPluginException 而不是原生崩溃;
  • 异常统一转换成错误码 "file_encryptor_error"result.error,并通过 hilog 记录,方便 DevEco Studio 的 Log 窗口定位。
5.1.6 Engine 解绑时释放资源
onDetachedFromEngine(binding: FlutterPluginBinding): void {
  if (this.channel != null) {
    this.channel.setMethodCallHandler(null);
    this.channel = null;
  }
}

Flutter Engine 销毁时清理通道 Handler 并释放引用。插件没有其他原生资源(无视图、无监听器、无权限句柄),解绑即完成全部清理。

5.2 声明插件和宿主权限

本插件的加解密和文件读写全部发生在应用沙箱内,不需要访问系统剪贴板、通讯录或外部存储,因此不需要 READ_PASTEBOARD 等敏感权限。当前宿主工程仅声明了 INTERNET

ohos.permission.INTERNET
5.2.1 插件 HAR 的权限

插件的 ohos/src/main/module.json5 只声明 HAR 模块信息,不带 requestPermissions

{
  "module": {
    "name": "file_encryptor",
    "type": "har",
    "deviceTypes": ["default", "tablet"]
  }
}

权限统一由宿主应用声明,HAR 保持无权限依赖,接入方可以按自己的场景决定是否声明。

5.2.2 应用 entry 的权限

最终安装的是宿主应用。本例只需要在 example/ohos/entry/src/main/module.json5 中保留或合并 INTERNET

{
  "module": {
    "requestPermissions": [
      {"name": "ohos.permission.INTERNET"}
    ]
  }
}

不需要剪贴板、存储等运行时权限。若业务方把密文文件通过其他方式(如网络上传)传出应用沙箱,则需按业务场景单独申请对应权限,但这已超出本插件的范围。

5.3 注册并导出插件

pubspec.yaml 通过以下配置声明 OHOS 插件类:

flutter:
  plugin:
    platforms:
      ohos:
        pluginClass: FileEncryptorPlugin

插件的 ohos/index.ets 需要导出实现:

import FileEncryptorPlugin from './src/main/ets/components/plugin/FileEncryptorPlugin';
export default FileEncryptorPlugin;

执行 flutter pub get 和构建后,Flutter 工具会为应用生成插件注册代码,GeneratedPluginRegistrant.ets 中会出现:

import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import FileEncryptorPlugin from 'file_encryptor';
import PathProviderPlugin from 'path_provider_ohos';

export class GeneratedPluginRegistrant {
  static registerWith(flutterEngine: FlutterEngine) {
    try {
      flutterEngine.getPlugins()?.add(new FileEncryptorPlugin());
      flutterEngine.getPlugins()?.add(new PathProviderPlugin());
    } catch (e) {
      Log.e(TAG, "Tried to register plugins with FlutterEngine failed.");
    }
  }
}

通常不应手工编辑该文件,因为下次构建可能覆盖它。这里同时注册了 FileEncryptorPluginPathProviderPlugin;缺少后者会导致 getApplicationDocumentsDirectory() 抛出 MissingPluginException,加密按钮点击后直接报错。

注册异常的排查步骤见第九节 MissingPluginException

5.4 检查 example 的 OHOS 应用结构

本例的 example/ohos/build-profile.json5 应在 products 中设置版本。下面是需核对的配置片段,请合并到现有工程;其中 signingConfig: "default" 需要与本机配置的签名名称一致,签名材料保留在本地:

{
  "app": {
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "compatibleSdkVersion": "5.1.0(18)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": ["default"]
        }
      ]
    }
  ]
}

配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。

六、补全交付文件并提交适配分支

6.1 除代码外还要补全哪些文件

代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:

文件应写清楚的内容
README.OpenSource上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖
README.md原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息
README.OpenHarmony_CN.md简介、安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题
README.OpenHarmony.md与中文说明对应的英文文档
CHANGELOG.OpenHarmony.mdOHOS 新增能力、适配版本、兼容限制与测试范围
LICENSE / NOTICE保留上游许可证;NOTICE 按许可证和原项目要求保留或补充
example/README.md依赖方式、运行目录、签名、操作步骤与效果图;覆盖加密与解密操作
pubspec.yamlohos/oh-package.json5核对包名、版本、插件注册、仓库地址、许可证和依赖
.gitignore忽略构建缓存及本机签名材料,不漏提交必要源码和配置

本仓库已经补齐了 README.OpenHarmony_CN.mdREADME.OpenHarmony.mdCHANGELOG.OpenHarmony.mddocs/ohos-test/ 真机截图,但提交前仍需核对两处许可证一致性:

  1. ohos/oh-package.json5license 字段仍是脚手架默认值 Apache-2.0,与上游 LICENSE(BSD 三条款)不一致,应改为一致的许可证声明;
  2. README.OpenHarmony_CN.md 末尾“开源协议”一节写为 MIT,同样与上游 BSD 三条款不一致,应同步修正。

此外,pubspec.yamlenvironment.sdk 上限仍是上游的 <3.0.0,影响范围见 9.11。

6.2 提交前检查

提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:

git branch --show-current
git diff --check
git status --short
git diff --stat
git diff

检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。

6.3 提交并推送适配分支

文档和代码整理完成后,在根目录暂存并提交。文件名按项目实际情况调整:

git add ohos example/ohos pubspec.yaml .gitignore
git add example/pubspec.lock
git add example/ios/Podfile example/macos/Podfile
git add example/ios/Flutter/Debug.xcconfig example/ios/Flutter/Release.xcconfig
git add example/macos/Flutter/Flutter-Debug.xcconfig example/macos/Flutter/Flutter-Release.xcconfig
git add example/macos/Flutter/GeneratedPluginRegistrant.swift
git add example/linux/flutter/generated_plugins.cmake example/windows/flutter/generated_plugins.cmake
git add README.OpenHarmony.md README.OpenHarmony_CN.md CHANGELOG.OpenHarmony.md
git add docs/ohos-test
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: add OHOS support for file_encryptor 0.0.1"
git remote -v
git branch --show-current
git push -u origin feat/ohos_file_encryptor_0.0.1

DevEco 可能向 example/ohos/build-profile.json5 写入本机签名配置,需要在提交前从暂存内容中移除。推送时,origin 应指向自己有写权限的仓库,当前分支为 feat/ohos_file_encryptor_0.0.1;上游仓库在 GitHub,无权限直接推送时先推送到自己的镜像。

推送后在托管平台发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行说明或运行图。目标分支和评审流程以接收仓库要求为准。

七、使用根目录 example 演示接入

仓库自带 example/,可以直接用来调试插件和体验加密与解密流程。

7.1 本地适配时使用路径依赖

当前 example/pubspec.yaml 的依赖是:

dependencies:
  flutter:
    sdk: flutter
  file_encryptor:
    path: ../

../ 相对于 example/pubspec.yaml 指向插件根目录,修改根目录插件后可直接联调。

7.2 通过 Git 依赖引入插件

业务应用通过 Git 引入时,将 file_encryptorpath 配置替换为下面的 Git 依赖。上游 GitHub 仓库尚未包含 OHOS 适配,先按第六节把自己的适配分支推送到有写权限的仓库,再固定到该分支:

dependencies:
  flutter:
    sdk: flutter
  file_encryptor:
    git:
      url: https://atomgit.com/{your-org}/file-encryptor.git
      ref: feat/ohos_file_encryptor_0.0.1

使用自己的适配版本时,将 url 改为对应仓库。正式发布后可固定到 tag 或 commit。

从插件根目录执行:

cd example
flutter pub get
flutter pub deps

检查 example/pubspec.lockfile_encryptor 的来源为 git,并核对 urlrefresolved-ref。同时检查没有 dependency_overridespubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。

7.3 调用接口实现文件加解密

仓库中的 example/lib/main.dart 已经是一个完整的加解密演示页 EncryptorPage,无需额外改写即可运行:

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:file_encryptor/file_encryptor.dart';

class EncryptorPage extends StatefulWidget { ... }

class _EncryptorPageState extends State<EncryptorPage> {
  final TextEditingController _contentController =
      TextEditingController(text: 'Hello OpenHarmony!');
  String _status = 'Ready. Input text and tap Encrypt / Decrypt.';
  String _cipherText = '';
  String _filePath = '';
  int _fileSize = 0;

  Future<String> _targetFilePath() async {
    final Directory appDocDir = await getApplicationDocumentsDirectory();
    return join(appDocDir.path, 'test_file');
  }

  Future<void> _encrypt() async {
    final String path = await _targetFilePath();
    final bool ok = await FileEncryptor().encrypt(path, _contentController.text);
    final File cipherFile = File('$path.aes');
    setState(() {
      _filePath = '$path.aes';
      _fileSize = ok ? cipherFile.lengthSync() : 0;
      _status = ok ? 'Encrypt OK: ...' : 'Encrypt failed';
    });
  }

  Future<void> _decrypt() async { ... }
}

页面包含:

  • 功能说明卡片;
  • 输入框,默认填充 "Hello OpenHarmony!"
  • Encrypt / Decrypt 两个按钮;
  • 结果卡片,展示状态、base64 密文、文件路径与大小。

该页面同时演示了 path_providerfile_encryptor 的协同:先取应用文档目录,再拼接目标路径,最后调用加解密。

7.4 页面退出时的资源处理

FileEncryptor 是无状态工具类,没有需要取消的订阅;页面 dispose 中只需释放自己的 TextEditingController 后调用 super.dispose()

仓库中的实现顺序值得注意:源码把 super.dispose() 放在最前面、之后才释放控制器。Flutter 的规范是先释放自己的资源、最后调用 super.dispose(),接入时建议按本节开头的方式调整,避免在已标记 disposed 的状态上继续操作。

八、验证、构建与鸿蒙设备运行效果

8.1 分别验证插件与 example

从插件仓库根目录执行:

flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze

本仓库当前的 flutter analyze 在插件根目录和 example/ 目录均未发现问题(No issues found!)。

flutter test 会失败:测试文件 test/file_encryptor_test.dart 调用 getApplicationDocumentsDirectory(),该函数依赖 path_provider 的平台通道;在单元测试环境中没有注册 path_provider 的 mock,因此抛出 MissingPluginException。处理方式是使用 TestWidgetsFlutterBinding.ensureInitialized() 配合 setMockMethodCallHandler mock 平台通道,或改为集成测试在真机/模拟器上运行。这不属于 OHOS 适配本身的缺陷。

Dart 测试只能覆盖工具类的分支逻辑,文件 IO 和 path_provider 的真实行为还需要在鸿蒙设备上验证。

8.2 确认设备连接

hdc list targets
flutter devices

设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。

8.3 配置签名

真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 entry 模块配置自动签名:

  1. 用 DevEco Studio 打开 example/ohos,不是仓库根目录;
  2. 等待工程 Sync 成功,确认 Project 视图中存在 entry 模块;
  3. 打开 File > Project Structure > Signing Configs
  4. default product 选择或生成签名;
  5. 确认设备、应用包名、证书和 Profile 匹配;
  6. 再回到终端执行 Flutter 构建或运行。

签名材料保存在本机,公开仓库中只保留构建所需的通用配置。

8.4 运行示例

以下命令在 example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:

flutter run -d <device-id>

也可以先构建 HAP:

flutter build hap --debug

典型产物位于:

example/ohos/entry/build/default/outputs/default/

目录中通常包含已签名和未签名 HAP。真机安装应选择与当前设备匹配的已签名产物。构建失败时按第九节的检查项排查签名与 SDK 配置后重试。

注:本机 flutter create . --template=plugin --platforms=ohos 曾因 Xcode 13.2.1 不支持 -skipPackageUpdates 选项而崩溃(记录于 flutter_01.log)。该崩溃与 OHOS 构建无关,升级 Xcode 或直接用已生成的 ohos/ 工程即可继续。

8.5 在设备上测试加密与解密

  1. 打开应用,确认页面显示 “FileEncryptor Demo” 标题、输入框、Encrypt 和 Decrypt 按钮;
  2. 在输入框保留默认文本 "Hello OpenHarmony!",点击 Encrypt,确认状态显示文件大小(例如 44 bytes)并展示 base64 密文;
  3. 点击 Decrypt,确认状态还原为 "Hello OpenHarmony!"
  4. 清空输入框或输入新文本,再次点击 Encrypt,确认文件内容被覆盖为最新密文;
  5. 卸载应用后重新安装(或重启进程),再次 Decrypt 同一文件,确认无法还原(因 key/iv 是进程内静态随机值,该行为继承自上游);
  6. 修改密文文件中的任意字符后点击 Decrypt,确认抛出异常并在 UI 上显示错误信息。

8.6 鸿蒙设备运行效果

完成适配后,Flutter 应用在 OHOS 上可以正常执行 AES 文件加解密:输入文本经 Encrypt 写入应用沙箱内的 .aes 文件,Decrypt 读取并还原原文。

本仓库已归档三张开行真机截图,分别对应初始页面、加密结果和解密结果。初始页面展示应用标题、输入框与两个操作按钮;加密结果页面展示生成的密文文件路径、大小与 base64 内容;解密结果页面展示还原后的原文。


Example 启动授权 复制后显示剪贴板内容 获取复制后显示剪贴板内容

操作预期表现
输入文本并点击 Encrypt生成 ${path}.aes 文件,状态显示文件大小与 base64 密文
点击 Decrypt读取同一文件并还原为原文
进程重启后 Decrypt 旧文件抛出异常(key/iv 已重新生成,继承自上游限制)

以下是操作的视屏,可以参考一下:

Example 启动授权


加解密能力依赖 encrypt 包与 path_provider_ohos 的协同。即使系统版本满足要求,不同型号也可能存在沙箱路径或文件权限差异,需要在目标设备上测试。

九、FAQ:适配过程与使用问题

9.1 Missing SDK components

典型错误如下:

Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.

这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。

处理顺序:

  1. 在 DevEco Studio SDK Manager 中确认 API 26 组件已经下载完整;
  2. 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
  3. 避免误用 /Applications/DevEco-Studio.app/Contents/sdk 之类的不完整目录;
  4. 确认 SDK 根目录下存在 toolchainsetsjsnativepreviewer
  5. 执行 flutter config --ohos-sdk <正确路径>
  6. 重新执行 flutter doctor -v 和 DevEco Studio Sync。
为什么连接 API 24 手机仍然会报这个错误?

因为 Sync 和 Compile 首先读取 Mac 本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。

当前工程的 compatibleSdkVersion 是 API 18,因此 API 24 在安装版本门槛上是满足的;但设备还必须支持文件沙箱读写能力,并满足签名要求。

9.2 DevEco Studio 中看不到 entry 模块

插件的 ohos/ 目录是 HAR 模块,可安装应用的 entry 模块位于 example/ohos/entry

请直接使用 DevEco Studio 打开:

file-encryptor/example/ohos

如果仍看不到 entry,先解决 SDK Sync 错误,再检查 example/ohos/build-profile.json5modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。

9.3 无法手动签名

签名配置依附于可构建的应用模块和 product。只有 HAR 插件模块、工程 Sync 失败,或者打开了错误目录时,DevEco Studio 都可能无法提供 entry 签名入口。

建议先确认:

  • 打开的是 example/ohos
  • SDK 组件完整并且 Sync 成功;
  • entry 的模块类型为 entry
  • default product 和 target 已正确关联;
  • 当前账号、证书和调试设备状态有效。

9.4 能安装但加密或解密失败

按以下顺序检查:

  1. 输入文本是否为空——空字符串仍会被加密,但生成的密文文件大小较小;
  2. path_provider_ohos 是否已在 pubspec.yaml 中声明并执行过 flutter pub get
  3. example/ohos/entry/.../GeneratedPluginRegistrant.ets 是否同时注册了 FileEncryptorPluginPathProviderPlugin
  4. 应用沙箱路径是否可写——查看错误日志中是否包含 FileSystemException
  5. 解密前是否先执行过加密,且未跨进程重启(key/iv 为进程内静态随机值);
  6. 是否误判了故障位置——真正承担加解密的是 Dart 层 encrypt 包,插件通道 file_encryptor 只用于探活命令。

在这里插入图片描述

9.5 MissingPluginException

这通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用。从仓库根目录执行:

cd example
flutter clean
flutter pub get
flutter run -d <device-id>

如果仍然出现,检查自动生成的插件注册文件中是否包含 FileEncryptorPlugin,同时核对 pubspec.yamlohos/index.etsoh-package.json5

9.6 重复调用没有累积效果

FileEncryptor 的两个方法都是无状态的一次性调用,没有可以取消的订阅,也没有累积的事件:

  • 重复 encrypt 同一文件:后一次调用会覆盖 .aes 文件,文件内容为最后一次加密结果;
  • 重复 decrypt:每次读取当前文件并返回,上一次的返回值不会被记住;
  • encrypt 返回 bool 表示文件是否生成,但不保证写入已 flush 完成。

如果业务需要“批量加密历史记录”或“加密成功回调”,需要自己保存每次调用的结果;插件层面没有缓存或事件通知。

9.7 编译成功但安装失败

常见原因包括:

  • HAP 未签名或使用了错误的 Profile;
  • 设备未加入调试设备列表;
  • 包名与签名 Profile 不匹配;
  • 安装包的 compatibleSdkVersion 高于设备 API;
  • 手机上已经安装了使用不同证书签名的同包名应用。

根据安装错误码区分签名、版本和包名冲突,再处理对应配置。

9.8 flutter create 不认识 ohos,或包名不合法

先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name file_encryptor。生成后检查 diff,再核对 ArkTS 实现与两端契约一致。

本机曾遇到 Xcode 13.2.1 不支持 -skipPackageUpdates 导致 flutter create --platforms=ohos 崩溃(见 flutter_01.log)。该问题可通过升级 Xcode 或跳过结构补全、直接使用已生成工程来规避。

9.9 Git 依赖提示找不到分支或无权限

先检查 url 是否指向已包含 OHOS 适配的仓库:上游 GitHub 仓库的 main 分支尚不包含 ohos/ 目录,直接引用会构建失败。再确认 feat/ohos_file_encryptor_0.0.1 已按第六节提交推送。私有仓库还需在本机配置 Git 认证。

9.10 改了本地 ArkTS,Demo 为什么没变化

先检查 example/pubspec.yaml:Git 依赖读取远程提交,不会自动读取本地插件改动。本地联调切回 path: ../;测试远程版本则先提交推送,再更新依赖并核对 pubspec.lockresolved-ref。原生代码变动后停止应用并重新构建运行,不能只做 Dart 热重载。

9.11 其他 Dart 3 环境下 pub get 报 SDK 约束不满足

仓库 pubspec.yamlenvironment.sdk 仍是上游的 >=2.18.1 <3.0.0,这是 Dart 2 时代的约束。本套 OHOS 工具链(Dart 3.12.2)可以完成依赖解析,并按约束选择较旧的依赖版本;换到标准 Dart 3 工具链时,pub 会因当前 SDK 不满足 <3.0.0 上限而拒绝解析,报错类似 “The current Dart SDK version does not meet the constraint”。

处理方式:把上限放宽,例如改为 sdk: ">=2.18.1 <4.0.0",并同步修改 example/pubspec.yaml,然后执行 flutter pub get 重新生成两份 lock 文件。放宽后依赖会解析到新版本,提交前用 flutter pub deps 核对依赖树变化。

相关链接

Logo

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

更多推荐