前言

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

doc_text 的 Dart 层和 flutter_web_auth 有一个明显的区别:它用了 Flutter 官方推荐的 Platform Interface 模式,把代码拆成了三个文件。flutter_web_auth 只有一个 55 行的文件,doc_text 虽然也不复杂,但架构更"正规"。这篇把这个三层架构拆开来看。

一、三个文件的职责

1.1 文件清单

lib/
├── doc_text.dart                    # 面向用户的 API
├── doc_text_platform_interface.dart  # 平台抽象层
└── doc_text_method_channel.dart      # MethodChannel 实现

1.2 职责划分

请添加图片描述

1.3 调用链

用户代码
  ↓
DocText().extractTextFromDoc(filePath)
  ↓
DocTextPlatform.instance.extractTextFromDoc(filePath)
  ↓
MethodChannelDocText.extractTextFromDoc(filePath)
  ↓
methodChannel.invokeMethod('extractTextFromDoc', {'filePath': filePath})
  ↓
原生层 onMethodCall

二、DocText 类:用户入口

2.1 完整代码

import 'doc_text_platform_interface.dart';

class DocText {
  Future<String?> extractTextFromDoc(String filePath) {
    return DocTextPlatform.instance.extractTextFromDoc(filePath);
  }
}

2.2 设计分析

特点 说明
非 static 需要 DocText() 实例化
单方法 只有 extractTextFromDoc
委托模式 直接转发给 DocTextPlatform.instance
无业务逻辑 纯粹的门面(Facade)

2.3 与 flutter_web_auth 的对比

// flutter_web_auth:static 方法,不需要实例化
final result = await FlutterWebAuth.authenticate(url: url, callbackUrlScheme: scheme);

// doc_text:实例方法,需要 new
final text = await DocText().extractTextFromDoc(filePath);
维度 doc_text flutter_web_auth
调用方式 实例方法 static 方法
是否需要实例化
状态管理 无状态(每次 new 都一样) 无状态
设计风格 Platform Interface 标准 简洁直接

💡 DocText 虽然是实例方法,但实际上是无状态的——每次 DocText() 创建的对象都一样,没有任何成员变量。用 static 方法也完全可以,但 Platform Interface 模式通常用实例方法。

三、DocTextPlatform:平台抽象层

3.1 完整代码

import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'doc_text_method_channel.dart';

abstract class DocTextPlatform extends PlatformInterface {
  DocTextPlatform() : super(token: _token);

  static final Object _token = Object();

  static DocTextPlatform _instance = MethodChannelDocText();

  static DocTextPlatform get instance => _instance;

  static set instance(DocTextPlatform instance) {
    PlatformInterface.verifyToken(instance, _token);
    _instance = instance;
  }

  Future<String?> extractTextFromDoc(String filePath) {
    throw UnimplementedError('platformVersion() has not been implemented.');
  }
}

3.2 token 验证机制

static final Object _token = Object();

// 构造函数中传入 token
DocTextPlatform() : super(token: _token);

// set instance 时验证 token
static set instance(DocTextPlatform instance) {
  PlatformInterface.verifyToken(instance, _token);
  _instance = instance;
}
步骤 说明
1. 创建 _token 一个唯一的 Object 实例
2. 构造函数传入 子类必须通过 super(token:) 传入
3. verifyToken 替换 instance 时验证 token 匹配

3.3 为什么需要 token

// 防止这种情况:
class FakeDocText implements DocTextPlatform {  // ❌ 不允许 implements
  // 恶意实现...
}
DocTextPlatform.instance = FakeDocText();  // verifyToken 会失败

// 正确的方式:
class CustomDocText extends DocTextPlatform {  // ✅ 必须 extends
  // 合法的自定义实现
}

📌 token 机制防止通过 implements 绕过平台接口。只有通过 extends 继承的子类才能正确传递 token,确保平台实现的安全性。

3.4 默认实例

static DocTextPlatform _instance = MethodChannelDocText();

默认使用 MethodChannelDocText,也就是通过 MethodChannel 与原生层通信。如果将来有 FFI 实现或 Web 实现,可以替换这个默认实例。

四、MethodChannelDocText:通道实现

4.1 完整代码

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'doc_text_platform_interface.dart';

class MethodChannelDocText extends DocTextPlatform {
  
  final methodChannel = const MethodChannel('doc_text');

  
  Future<String?> extractTextFromDoc(String filePath) async {
    final String? extractedText = await methodChannel.invokeMethod<String>(
      'extractTextFromDoc',
      {'filePath': filePath},
    );
    return extractedText;
  }
}

4.2 关键细节

细节 说明
const MethodChannel('doc_text') 通道名称,两端必须一致
@visibleForTesting 标记为测试可见,方便 mock
invokeMethod<String> 泛型指定返回类型为 String
{'filePath': filePath} 参数以 Map 形式传递

4.3 参数传递格式

// Dart 层发送
methodChannel.invokeMethod<String>(
  'extractTextFromDoc',           // 方法名
  {'filePath': '/path/to/doc'},   // 参数 Map
);

// 原生层接收
onMethodCall(call: MethodCall, result: MethodResult): void {
  const args = call.args as Map<string, Object>;
  const filePath = args.get("filePath") as string;
}

4.4 返回值处理

// Dart 层
final String? extractedText = await methodChannel.invokeMethod<String>(...);

// 原生层
result.success(text);   // text 是 string → Dart 收到 String
result.success(null);   // null → Dart 收到 null
result.error(...)       // → Dart 抛出 PlatformException
原生返回 Dart 接收
result.success("hello") "hello"
result.success(null) null
result.error("UNAVAILABLE", ...) PlatformException

五、Platform Interface 模式的价值

5.1 为什么 Flutter 官方推荐这个模式

不用 Platform Interface:
DocText → MethodChannel → 原生层
(紧耦合,难以测试,难以扩展)

用 Platform Interface:
DocText → DocTextPlatform(抽象) → MethodChannelDocText → 原生层
                                  → WebDocText → Web 实现
                                  → FfiDocText → FFI 实现
                                  → MockDocText → 测试 Mock

5.2 可扩展性

// 未来如果要支持 Web 平台
class WebDocText extends DocTextPlatform {
  
  Future<String?> extractTextFromDoc(String filePath) async {
    // 使用 JavaScript 库解析
    return js.context.callMethod('parseDoc', [filePath]);
  }
}

// 在 Web 入口注册
void registerWith(Registrar registrar) {
  DocTextPlatform.instance = WebDocText();
}

5.3 可测试性

// 测试时 Mock 平台实现
class MockDocText extends DocTextPlatform {
  
  Future<String?> extractTextFromDoc(String filePath) async {
    return "Mocked text content";
  }
}

void main() {
  setUp(() {
    DocTextPlatform.instance = MockDocText();
  });

  test('extractTextFromDoc returns text', () async {
    final docText = DocText();
    final result = await docText.extractTextFromDoc('test.docx');
    expect(result, 'Mocked text content');
  });
}

💡 Platform Interface 模式的核心价值是解耦。即使当前只有 MethodChannel 一种实现,这个架构也为未来的扩展留好了位置。

六、通道名称的一致性

6.1 Dart 层

final methodChannel = const MethodChannel('doc_text');

6.2 原生层

this.channel = new MethodChannel(binding.getBinaryMessenger(), "doc_text");

6.3 一致性检查

位置 通道名称 必须一致
Dart: MethodChannelDocText 'doc_text'
ArkTS: DocTextPlugin "doc_text"
pubspec.yaml: pluginClass DocTextPlugin 类名一致

6.4 常见错误

MissingPluginException: No implementation found for method 
extractTextFromDoc on channel doc_text

如果看到这个错误,检查:

  1. 通道名称两端是否一致
  2. pluginClass 是否正确
  3. 是否运行了 flutter pub get

七、与其他插件 Dart 层的对比

7.1 三种 Dart 层设计模式

插件 模式 文件数 特点
doc_text Platform Interface 3 标准、可扩展
flutter_web_auth 单文件 static 1 简洁、直接
secure_application Widget + State UI 组件型

7.2 选择建议

场景 推荐模式
简单的方法调用 单文件 static(如 flutter_web_auth)
需要多平台实现 Platform Interface(如 doc_text)
UI 组件型插件 Widget + State(如 secure_application)
需要测试 Mock Platform Interface

八、Dart 层的完整数据流

8.1 正常流程

DocText().extractTextFromDoc("/data/test.docx")
    ↓
DocTextPlatform.instance.extractTextFromDoc(...)
    ↓
MethodChannelDocText.extractTextFromDoc(...)
    ↓
methodChannel.invokeMethod<String>('extractTextFromDoc', {'filePath': ...})
    ↓
[MethodChannel 序列化 → 原生层]
    ↓
DocTextPlugin.onMethodCall(call, result)
    ↓
this.extractTextFromDoc(filePath)
    ↓
result.success("提取的文本内容")
    ↓
[原生层 → MethodChannel 反序列化]
    ↓
返回 "提取的文本内容"

8.2 错误流程

DocText().extractTextFromDoc("/data/nonexistent.doc")
    ↓
... → 原生层
    ↓
result.error("ERROR", "文件不存在", null)
    ↓
[原生层 → MethodChannel]
    ↓
抛出 PlatformException(code: "ERROR", message: "文件不存在")

总结

doc_text 的 Dart 层采用了 Flutter 官方推荐的 Platform Interface 模式:

  1. 三层架构:DocText → DocTextPlatform → MethodChannelDocText
  2. token 验证:防止通过 implements 绕过平台接口
  3. 可扩展:未来可以添加 Web、FFI 等实现
  4. 可测试:通过替换 instance 实现 Mock
  5. 通道名称'doc_text',两端必须一致

下一篇我们看 Android 端的 Apache POI 实现——理解了 Android 怎么做的,才能更好地理解 OpenHarmony 为什么要手写。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

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

更多推荐