Flutter/OpenHarmony 三方库适配实战:flutter_subscreen_plugin 双屏通道
Flutter/OpenHarmony 三方库适配实战:flutter_subscreen_plugin 双屏通道
收银机、POS 终端和客显设备经常采用“主屏操作 + 副屏展示”的组合。原库
flutter_subscreen_plugin 在 Android 上通过第二 Flutter Engine 和
Presentation 承载副屏,同时用两条方法通道让主屏和副屏交换数据。
这次适配的版本是 1.3.2-ohos.1。适配目标不是把 Android 的
Presentation 机械翻译成 ArkTS,而是先建立稳定的 OHOS 插件入口和可验证
的通道契约:
- 保留 Dart 层的静态 API、方法名和参数结构。
- 在 OHOS HAR 中注册
FlutterSubscreenPlugin。 - 用
display.getAllDisplays()做真实多显示器检测。 - 让主通道、副通道的
success、error、notImplemented状态可以
回到 Dart。 - 不在没有第二窗口能力时伪造副屏窗口。
一、项目信息与适配边界
| 项目 | 内容 |
|---|---|
| 仓库名称 | flutter_subscreen_plugin |
| 仓库地址 | https://atomgit.com/oh-flutter/flutter_subscreen_plugin |
| 适配版本 | 1.3.2-ohos.1 |
| 主通道 | screen_plugin_main_channel |
| 副通道 | screen_plugin_sub_channel |
| OHOS 原生入口 | FlutterSubscreenPlugin |
| 当前能力检测 | display.getAllDisplays() |
| 当前真实副屏窗口 | 未接入,待后续 OHOS 多屏窗口/FlutterView 验证 |
原库的核心流程可以抽象为:
Main Flutter Engine
|
| screen_plugin_main_channel
v
Native plugin bridge
|
| screen_plugin_sub_channel
v
Sub Flutter Engine
Android 的副屏不是普通 Dialog,而是绑定到外接 Display 的第二 Flutter
Engine。OHOS 上需要单独确认多屏窗口、FlutterView、窗口生命周期和触摸
事件模型,因此本次先完成可编译、可测试的通道兼容层。
二、先确认这个库为什么需要适配
原库的 lib/src/sub_screen_plugin.dart 只依赖 Flutter 的
MethodChannel,没有平台专属 Dart 代码:
static MethodChannel _mainChannel = MethodChannel(_mainChannelName)
..setMethodCallHandler(_onMainChannelMethodHandler);
static MethodChannel? _subChannel;
这说明业务层已经具备跨平台基础,但 isMultipleScreen、
doubleScreenShow 等方法必须由原生实现。根 pubspec.yaml 原本只有
Android 插件声明,缺少 OHOS 入口。因此适配的关键不是重写 Dart 类,而是
让 Flutter OHOS 工具链找到原生类,并让 ArkTS 使用和 Dart 相同的通道契约。
三、让 Flutter OHOS 找到插件
根 pubspec.yaml 保留 Android 配置,只增加 OHOS 声明:
version: 1.3.2-ohos.1
homepage: https://atomgit.com/oh-flutter/flutter_subscreen_plugin
flutter:
plugin:
platforms:
android:
package: com.rex.flutter_subscreen_plugin
pluginClass: FlutterSubscreenPlugin
ohos:
pluginClass: FlutterSubscreenPlugin
OHOS 模块使用 HAR 类型:
{
"module": {
"name": "flutter_subscreen_plugin",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
入口文件只导出插件类:
import FlutterSubscreenPlugin
from './src/main/ets/components/plugin/FlutterSubscreenPlugin';
export default FlutterSubscreenPlugin;
这三个配置缺一不可:pubspec.yaml 负责让 Flutter 发现插件,
oh-package.json5 和 build-profile.json5 负责让 Hvigor 构建 HAR,
index.ets 负责让原生入口被导出。
四、ArkTS 插件的生命周期
插件同时实现 FlutterPlugin、MethodCallHandler 和 AbilityAware:
const MAIN_CHANNEL = 'screen_plugin_main_channel';
const SUB_CHANNEL = 'screen_plugin_sub_channel';
export default class FlutterSubscreenPlugin
implements FlutterPlugin, MethodCallHandler, AbilityAware {
private mainChannel: MethodChannel | null = null;
private subChannel: MethodChannel | null = null;
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.mainChannel = new MethodChannel(
binding.getBinaryMessenger(),
MAIN_CHANNEL,
);
this.subChannel = new MethodChannel(
binding.getBinaryMessenger(),
SUB_CHANNEL,
);
this.mainChannel.setMethodCallHandler(this);
this.subChannel.setMethodCallHandler({
onMethodCall: (call: MethodCall, result: MethodResult): void => {
this.forwardToMain(call, result);
},
});
}
onDetachedFromEngine(binding: FlutterPluginBinding): void {
this.mainChannel?.setMethodCallHandler(null);
this.subChannel?.setMethodCallHandler(null);
this.mainChannel = null;
this.subChannel = null;
}
}
onAttachedToEngine 创建通道并注册处理器,onDetachedFromEngine 必须
清空处理器。如果只把通道置空而不解绑处理器,Engine 重建后可能出现旧
Handler 仍然接收消息的情况。
五、显示器检测不能伪造
Android 原库通过 MediaRouter 找到 presentationDisplay。OHOS 当前
兼容层使用系统 display API 的真实显示器列表:
private async hasExternalDisplay(): Promise<boolean> {
const displays = await display.getAllDisplays();
return displays.length > 1;
}
supportDoubleScreen 和 doubleScreenShow 共用这个检测逻辑:
case 'supportDoubleScreen':
this.respondWithDisplay(result);
return;
case 'doubleScreenShow':
this.respondWithDisplay(result);
return;
respondWithDisplay 通过回调把异步结果送回 Flutter:
private respondWithDisplay(result: MethodResult): void {
this.hasExternalDisplay().then((hasDisplay: boolean) => {
result.success(hasDisplay);
}).catch((error: Error) => {
result.error('subscreen_error', error.message, null);
});
}
这里刻意没有写 result.success(true),也没有在 Dart 层根据平台判断直接
返回支持。单屏虚拟机上,isMultipleScreen 应该返回 false,这是能力
边界,不是插件故障。
六、双通道如何转发消息
业务方法名和参数来自原库,OHOS 不再解释一套新的协议。主通道收到未知方法
时转发给副通道:
private forwardToSub(call: MethodCall, result: MethodResult): void {
const channel = this.subChannel;
if (channel === null) {
result.success(false);
return;
}
channel.invokeMethod(call.method, call.args, this.bridgeResult(result));
}
副通道收到消息时反向转发给主通道:
private forwardToMain(call: MethodCall, result: MethodResult): void {
const channel = this.mainChannel;
if (channel === null) {
result.success(false);
return;
}
channel.invokeMethod(call.method, call.args, this.bridgeResult(result));
}
当前 Flutter OHOS SDK 的 MethodCall 字段是 args,MethodChannel
的 invokeMethod 是回调式异步 API。转发时不能写
await channel.invokeMethod(...),也不能假设它返回 Promise。结果桥接
需要显式保留三种状态:
private bridgeResult(result: MethodResult): MethodResult {
return {
success: (value: Any): void => {
result.success(value);
},
error: (
errorCode: string,
errorMessage: string,
errorDetails: Any,
): void => {
result.error(errorCode, errorMessage, errorDetails);
},
notImplemented: (): void => {
result.notImplemented();
},
};
}
这一点是本次适配中非常容易踩坑的地方:如果转发时总是
result.success(true),Flutter 会看不到原生端真实的异常;如果忘记
notImplemented,未实现方法也可能被误认为成功。
七、Dart API 保持原样
业务代码仍然可以这样发送消息:
await SubScreenPlugin.sendMsgToViceScreen(
'text',
params: {'num': '1024'},
);
副屏页面监听副通道:
SubScreenPlugin.viceStream.listen((MethodCall call) {
debugPrint('${call.method}: ${call.arguments}');
});
副屏回传主屏时,调用原有接口:
await SubScreenPlugin.sendMsgToMainScreen(
'text',
params: {'status': 'ready'},
);
SubScreenPlugin.mainStream.listen((MethodCall call) {
debugPrint('${call.method}: ${call.arguments}');
});
适配没有新增 SubScreenPluginOhos,也没有要求业务页面写 OHOS 分支。
这样 Android 和 OHOS 使用同一套 Dart 调用方式,后续真正接入 OHOS 第二
窗口时,业务层只需要继续监听已有的 Stream。
八、示例工程的兼容性修正
原示例使用 Flutter 2 时代的 Key key 构造参数和 window API。在当前
Flutter OHOS 工具链上会触发空安全编译错误。适配后的入口改为:
void main() {
final String routeName = PlatformDispatcher.instance.defaultRouteName;
if (routeName == 'subMain') {
runApp(const SubApp());
} else {
runApp(const MaterialApp(home: MainApp()));
}
}
页面构造参数改为 super.key,示例测试也改成验证真实页面:
testWidgets('renders the main-screen controls', (WidgetTester tester) async {
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
const MethodChannel('screen_plugin_main_channel'),
(MethodCall call) async => false,
);
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
const MethodChannel('screen_plugin_sub_channel'),
(MethodCall call) async => false,
);
await tester.pumpWidget(const MainApp());
await tester.pump();
expect(find.text('主屏'), findsOneWidget);
expect(find.text('发送数据给副屏'), findsOneWidget);
});
测试不验证未显示的“未查询到可用副屏”弹窗,而是验证页面已经渲染出主屏
操作入口;这样测试不会把一个被隐藏的 SnackBar 当成必须出现的 UI。
OHOS 虚拟机主屏页面已正常渲染,截图如下:

截图来自 emulator 7.0.0.107、系统 OHOS OpenHarmony-7.0.0.105,页面显示
“主屏”“接收到的副屏数据为:null”以及发送、开启和关闭副屏按钮。普通单屏
模拟器没有真实副屏,因此文章不伪造第二 Flutter Engine 或副屏数据。
九、构建与测试
本次执行的验证命令:
flutter pub get
flutter analyze
cd example
flutter pub get
flutter analyze
flutter test
flutter build hap --debug --no-codesign
验证结果:
| 检查项 | 结果 |
|---|---|
根包 flutter analyze | 通过,No issues found |
示例 flutter analyze | 通过,No issues found |
示例 flutter test | 通过,1 个测试 |
flutter build hap --debug --no-codesign | 通过 |
| Hap 产物 | example/build/ohos/hap/entry-default-unsigned.hap |
| OHOS 虚拟机启动 | 通过,主屏页面正常显示,进程稳定存活 |
| 插件注册 | 通过,FlutterSubscreenPlugin 已注册 |
| 主副通道 | 通过,两个方法通道均已设置处理函数 |
| 单屏能力检测 | 通过,不伪造副屏窗口 |
| 真实副屏窗口和触摸路由 | 未实现,待后续接入 OHOS 多屏能力 |
ArkTS 编译过程中出现过三个真实 API 错误:MethodCall.arguments 不存在、
invokeMethod 不能使用 .then()、async 方法必须返回
Promise<T>。这些错误分别改成 call.args、回调式 MethodResult 转发
和非 async 的同步转发函数后,Hap 构建通过。
十、后续接入真实 OHOS 副屏窗口
当前代码已经明确了原生层的扩展位置。接入真实窗口时,建议按以下顺序:
- 在目标 OHOS SDK 中确认多屏窗口和 FlutterView 的创建接口。
- 在
doubleScreenShow中选择外接display,创建副屏窗口。 - 将副屏页面路由固定为
subMain,并处理主副 Engine 的生命周期。 - 将副屏触摸、返回键和窗口关闭事件转发到对应 Flutter Engine。
- 用真实客显设备验证显示时序、拔插显示器、熄屏恢复和消息队列。
在这些能力接入前,doubleScreenShow 只返回显示器检测结果;文档、测试和
验证记录都保持这一边界,不把“通道可编译”描述成“双屏窗口已经可用”。
十一、总结
这次适配把一个 Android 双 Flutter Engine 插件拆成了稳定的跨平台契约和
可替换的 OHOS 原生能力:
- Dart API、方法名和参数结构保持不变。
- OHOS HAR 入口、Hvigor 配置和 ArkTS 生命周期完成注册。
- 双通道消息可以转发,错误和未实现状态不会被吞掉。
- 多显示器检测使用
display.getAllDisplays()的真实结果。 - 示例工程升级到当前 Flutter 空安全 API,并加入可重复的 widget 测试。
- Hap 构建、模拟器主屏启动、插件注册和双通道注册均已验证;真实副屏窗口
和触摸路由仍待多屏设备验证。
后续最值得关注的不是继续增加 Dart 分支,而是确定目标 OHOS 设备的多屏
窗口模型,并把第二 FlutterView 的生命周期接入已经稳定的双通道桥接。
欢迎加入 CPF-Flutter 鸿蒙社区,一起交流 Flutter 三方库适配、工程实践和
虚拟机/真机验证经验:
https://atomgit.com/CPF-Flutter
更多推荐



所有评论(0)