Flutter桌面开发必不可少的C++

前言:Flutter作为一门跨端框架,桌面端的支持一直在团队的Roadmap规划中。在2022年第一个Release版本发布后,我也开始使用Flutter开发桌面应用,过程中对Flutter在桌面端支持的力度、生态社区的成熟度有深刻的体会。
当前使用Flutter做桌面开发,开发者必须会C++。如果完全依赖于官方推进,那整个项目一定会踩到各种各样的坑。

一、Flutter如何创建Windows App

1. 生成依赖

创建项目成功后,Flutter pub get会生成Flutter相关的依赖。因为我们已经在ide的Flutter插件中配置了Flutter SDK Path,pub get指令会将必要的文件生成到项目中。

2. 定义CmakeFile

Flutter Windows目录是个很普通的C++项目;通过CMake编译项目,在windows下的CMakeLists.txt中定义了很多全局的宏,包括可执行文件的配置、运行环境等; 然后在runner下的CMakeLists.txt中链接各种依赖库,这就包含了刚才生成的Flutter文件,至此Cpp文件可以正常使用Flutter的Api。

3. C++创建Win32Window

继续往下来到源代码处,main.cpp中声明了flutter::DartProject,传给FlutterWindow对象,FlutterWindow继承自Win32Window。

C++ 复制代码
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
                      _In_ wchar_t *command_line, _In_ int show_command) {

  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
    CreateAndAttachConsole();
  }

  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

  flutter::DartProject project(L"data"); // 创建DartProject

  std::vector<std::string> command_line_arguments =
      GetCommandLineArguments();

  project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); // 设置入口参数

  FlutterWindow window(project); // 传给FlutterWindow对象
  Win32Window::Point origin(10, 10);
  Win32Window::Size size(1280, 720);
  // 注意这里的CreateAndShow,非常重点
  if (!window.CreateAndShow(L"cmake_robot", origin, size)) {
    return EXIT_FAILURE;
  }
  window.SetQuitOnClose(true);

  ::MSG msg;
  while (::GetMessage(&msg, nullptr, 0, 0)) {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
  }

  ::CoUninitialize();
  return EXIT_SUCCESS;
}

window.CreateAndShow会调用Win32官方的CreateWindow接口,创建一个windows窗口,再调用OnCreate方法;

C++ 复制代码
// win32_window.cpp
bool Win32Window::CreateAndShow(const std::wstring& title,
	const Point& origin,
	const Size& size,
	const bool& isToolWindow)
{
	Destroy();

	const wchar_t* window_class =
		WindowClassRegistrar::GetInstance()->GetWindowClass();

	const POINT target_point = { static_cast<LONG>(origin.x),static_cast<LONG>(origin.y) };
   // ----省略好多代码----
   // WinUser.h提供的CreateWindow
	HWND window = CreateWindow(
		window_class, title.c_str(), WS_OVERLAPPED & ~WS_THICKFRAME | WS_MINIMIZEBOX & ~WS_VISIBLE,
		newOrigin.x, newOrigin.y,
		Scale(size.width, scale_factor), Scale(size.height, scale_factor),
		nullptr, nullptr, GetModuleHandle(nullptr), this);

	// 设置应用程序窗口背景颜色为透明
	UpdateWindow(window);
	if (!window)
	{
		return false;
	}
	return OnCreate();
}

注意这里调用的是FlutterWindow重写的OnCreate,把FlutterViewController的视图传给SetChildContent,SetChildContent中通过MoveWindow刷新视图,从而把Flutter的UI绘制上去。

C++ 复制代码
// flutter_windows.cpp
bool FlutterWindow::OnCreate() {
  if (!Win32Window::OnCreate()) {
    return false;
  }

  RECT frame = GetClientArea();

  // The size here must match the window dimensions to avoid unnecessary surface
  // creation / destruction in the startup path.
  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
      frame.right - frame.left, frame.bottom - frame.top, project_);
  // Ensure that basic setup of the controller was successful.
  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
    return false;
  }
  RegisterPlugins(flutter_controller_->engine());
  SetChildContent(flutter_controller_->view()->GetNativeWindow()); // 这里把flutter_controller_的视图传进去
  return true;
}
C++ 复制代码
void Win32Window::SetChildContent(HWND content)
{
	child_content_ = content;
	SetParent(content, window_handle_);
	RECT frame = GetClientArea();

	MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
		frame.bottom - frame.top, true); // MoveWindow也是Win32的接口,传入true表示强制刷新视图

	SetFocus(child_content_);
}

可以看出,整个应用窗口的创建都是C++负责的。Flutter依然是很纯粹的UI框架,通过FlutterViewController把内容更新到Win32Window中,之后就是内部管理UI视图的更新。

二、Flutter提供的能力太过单一

通过分析项目Native的代码,我们已经得出结论:Flutter只负责UI的绘制

1. 基本的窗口操作

然而在业务场景中,桌面应用往往需要考虑很多窗口的交互,比如设置窗口显隐、改变窗口大小等非常常见的操作,遗憾的是:Flutter并没有提供任何操作窗口的Api。

目前只能依赖于pub的开源插件:window_manager来操作窗口,实际使用过程中还是会产生小部分的兼容问题,此时往往需要开发者在C++层面自行解决

2. 不同的应用类型

笔者是OS开发,实际开发的应用类型是比较杂的,比如:全屏应用、悬浮工具、后台应用等;同时我们对窗口启动速度也会有要求,如果等待Flutter窗口显示完毕再做窗口属性的设置,那么用户体验将会大打折扣。

所以我们都是直接操作Win32Window的属性,应用窗口的样式/位置、单例模式等,都在C++层去解决掉,提高性能参数,同时让Flutter更纯粹既然它不适合干窗口的事情,那就安心做好跨端 UI 吧!

3. Pub生态成熟度

这两年pub.dev上适配Windows的插件数量越来越多,这无疑给开发者打了不少强心剂。 但实事求是,21855个插件实际上大部分都无需跟C++层打交道,比如我想获取网卡的Mac地址都找不到适合的插件。再如更新窗口名称、获取USB设备、监听hid设备插拔等普遍的需求,Flutter生态都是不支持的。因此具备编写Flutter Desktop Platform 插件,是必不可少的能力。

三、核心痛点:多窗口、底层服务

1. 多窗口

桌面应用与移动应用的最大区别绝对是:窗口化。作为炙手可热的桌面应用框架,Flutter完全不支持多窗口,这个问题也是被人诟病已久。

在官方最新的Roadmap中,也终于再次提及多窗口的支持,实际上多窗口提了4年之久,2024年有望实现吗~ 另外由于某些众所周知的原因,2023年Flutter桌面没什么大的进展,作为开发者需要自力更生。

我们在梳理Flutter机制后,发现多窗口完全就是多个Win32的窗口,每个窗口对应一个FlutterEngine,各个Engine虽然内存无法实现共享,但也能依附于主进程在多个channel中通信。由此自行开发了多窗口的插件,这一切还是离不开C++的能力。

2. 底层服务

随着Flutter的应用深入,我们开始探索Flutter更多的可能性。比如依赖Flutter engine,扩展dart在跨端服务的可行性。

在Android中,我们创建Android Service,使用FlutterEngineGroup来管理Flutter Engine,通过 DartExecutor执行dart逻辑;

在C++中,我们依然能看到flutter_engine.h的身影,那么它能不能在Window Service中去执行特定的Dart逻辑代码呢?自然是可以的。

由此我们成功实现了Flutter在Android和Windows中,UI和服务双双跨平台,这实际上已经远远超过Flutter的技术范畴。

四、结语

Flutter Windows应用,基于C++编写的Win32Window,在原生窗口上绘制UI,原理上离不开C++的技术;同时在列举的业务场景、框架痛点上也足以说明C++能力的重要性。

作为Flutter开发者,有精力的情况下,一定要多学各个平台的框架和能力,让Flutter、更让自己走的更远!

相关推荐
T0uken2 小时前
【QT Quick】C++交互:与QML类型转换
c++·qt·交互
程序猿阿伟2 小时前
《C++音频降噪秘籍:让声音纯净如初》
开发语言·c++·网络协议
Tech_gis3 小时前
C++ 观察者模式
开发语言·c++·观察者模式
꧁༺❀氯ྀൢ躅ྀൢ❀༻꧂3 小时前
算法与程序课程设计——观光铁路
c语言·c++·算法·课程设计·dijkstra 算法·spfa算法
yngsqq3 小时前
005集—— 用户交互之CAD窗口选择图元实体(CAD—C#二次开发入门)
windows·c#·交互
忘梓.3 小时前
C嘎嘎入门篇:类和对象番外(时间类)
c++·算法
hola1738414393 小时前
Conda答疑
windows·conda
bbqz0073 小时前
逆向WeChat(七)
数据库·c++·微信·逆向·protobuf·sqlcipher·破解密钥·解码protobuf·wechatdb
9毫米的幻想4 小时前
【C++】—— 继承(上)
c语言·开发语言·jvm·c++·学习
m0_683790954 小时前
国庆节刷题
c语言·c++