flutter windows、macos使用多窗口方法
最近研究flutter在Windows和MacOs操作系统上使用多窗口方法,总结一下开发心得。
众所周知,flutter使用skia将像素点,通过opengl,software,metal等方式渲染到一个窗口上,不像原生开发的可以指定控件添加到具体窗口内。
在flutter pub仓库管理中,看了几个大神写的多窗口方案,无一例外都是通过加载多个flutter engine实例,即一个窗口一个flutter engine实例创建多窗口,
通过底层(c++,swift)使用EventSink实现两个窗口之间通讯,使用这种方式无需改动flutter engine源码,集成多窗口plugin开发即可,
但这个方案同样存在几个问题:
1.加载新窗口时间过长:创建多个窗口时,需要加载一个新的dart vm环境,同时创建一个新的flutter engine对象,创建新的窗口需要等待几秒,如果在7代i5的设备上,
甚至长达10秒左右。
2.窗口之间数据共享复杂:创建新的窗口,主窗口需要将数据传输到新窗口,一旦两个窗口之间数据通讯越来越多,对于后期开发来说,简直是个灾难。
3.增加内存开销:前面说了,每个窗口之间创建需要重新创建一个dart vm,就是要加载一个新的dart源码文件,一个空的flutter demo的源码so文件,
就有10多m,加载到内存中占多100m。
针对以上几个问题,我下载了flutter engine的源码进行小修改,使其更加简单开发。
先看看dart中我的创建新窗口思路源码:
class MyWindow { final WindowID id; final String title; late SingletonFlutterWindow subwindow; double width; double height; int x; int y; late WindowEventListener listener; MyWindow( {required this.title, required this.id, this.width = 1280, this.height = 720, this.x = 0, this.y = 0, bool bMaximize = false}) { MultiWindow.create( title: title, id: id.index, width: width, height: height, x: x, y: y, bMaximize: bMaximize) .then((value) { if (value == true) { Timer.run( () { //要等窗口大小回来 onCreateWindowSuccess(); }, ); listener.addEventListener(); } }); listener = WindowEventListener(id.index); } void onCreateWindowSuccess() { subwindow = SingletonFlutterWindow(this.id.index, PlatformDispatcher.instance); WidgetsFlutterBinding widgetsBinding = WidgetsFlutterBinding(bindingWindow: subwindow); WindowManager.instance.setCurWidgetBinding(widgetsBinding); widgetsBinding.scheduleAttachRootWidget(build()); widgetsBinding.scheduleWarmUpFrame(); } Widget build() { return Container(); } }
新窗口继承MyWindow,
import 'package:flutter/material.dart'; import 'package:flutter_app/UI/BaseWindow/MyWindow.dart'; class DemoWindow extends MyWindow { String key = ""; DemoWindow( {required String title, required WindowID id, required double width, required double height, required int x, required int y, required bool bMaximize}) : super( title: title, id: id, width: width, height: height, x: x, y: y, bMaximize: bMaximize) { key = "chart${id.index}"; } @override Widget build() { return MaterialApp(home: DemoWindowHome(this)); } } class DemoWindowHome extends StatefulWidget { DemoWindow window; DemoWindowHome(this.window); @override State<DemoWindowHome> createState() => _DemoWindowHomeState(); } class _DemoWindowHomeState extends State<DemoWindowHome> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.blue, body: Center( child: Column( children: [ Container( width: 200, height: 100, color: Colors.red, child: TextField()), MaterialButton( onPressed: () {}, child: Text("sec wind"), ) ], ))); } }
只要调用
DemoWindow newWindow = DemoWindow();
这样在一个dart创建中,就能创建一个新的窗口了,并且可以使用现有窗口的环境的数据,而且打开新的窗口的时间更原生几乎一样。
我将flutter engine源码下载下来后,按照以下思路进行更改:
1.每个flutter window需要绑定有一个整形id,用于指定窗口事件分发(鼠标,键盘,渲染等)。
2.需要一个WindowManager来管理所有窗口的事件分发。
3.一个窗口一个WidgetBinding对象,管理一个窗口的手势识别,控件布局,焦点管理,键盘通知等。
4.多个窗口同时渲染时,需要将渲染消息入队,通过前后顺序一个个渲染。
不过这个方法也有一个弊端:那就是多个窗口打开时不能使用较长或循环动画系统,这是为什么呢?
我们先大致了解一下flutter的渲染原理,当一个stateful widget调用setState时,会标记state为dirty,并且通知c++的光栅线程调用dart的handleBeginFrame来布局绘制,
上面说了要把需要渲染的窗口id入队,进行排队渲染,如果一个窗口里面有个AnimationController动画系统一直循环播放来通知底层渲染,其他窗口就无法将通知渲染事件
入队,这会导致一个窗口画面停止了。
如果你的项目动画系统不多,可以使用这个方案实现多窗口。
我已经把多窗口方案应用到线上项目中,有兴趣的小伙伴可以跳转交易侠官网(www.jiaoyixia.com)下载对应的系统版本来体验。
如果大家有更好的解决方案,也可以留言提出。
转载请注明出处,from博客园HemoJohn,有偿指导加q:980550823