news 2026/10/10 5:56:46

flet-desktop 解析:Flutter 桌面客户端如何把 Flet 应用变成原生窗口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
flet-desktop 解析:Flutter 桌面客户端如何把 Flet 应用变成原生窗口
  • 前端
  • 跨平台
  • 桌面应用
  • 移动开发

【免费下载链接】flet

Build realtime web, mobile and desktop apps in Python only. No frontend experience required.

项目地址:https://gitcode.com/gh_mirrors/fl/flet
点击查看免费下载

导读

flet-desktop是 Flet 生态中专用于桌面平台的运行时包,本质是一个「编译好的 Flutter Flet 桌面客户端」的分发载体。它负责在 Windows、macOS、Linux 上为你的 Python Flet 应用拉起一个原生窗口:从下载/解压 Flutter 客户端二进制、缓存到本地,到按平台构造启动命令,再到为打包应用设置任务栏身份。读完本文,你将完整掌握flet-desktop的包结构、安装时机、运行时缓存与指纹机制、三平台启动策略、关键环境变量,以及它与flet run、flet pack的协作方式。

包概览:一个「装着 Flutter 客户端」的 Python 包

关联文档 README.md 对它的定义只有一句话:"This package contains a compiled Flutter Flet desktop client"。这背后对应的是 Flet 的独特架构——你在 Python 里写 UI 逻辑,但真正渲染到窗口里的,是官方用 Flutter 预编译好的客户端二进制。

包的实际代码结构只有三个文件(见 sdk/python/packages/flet-desktop):

文件职责
src/flet_desktop/init.py客户端定位、下载、解压、缓存、平台启动的核心逻辑
src/flet_desktop/version.py版本号占位文件(构建时注入实际版本)
src/flet_desktop/win_taskbar.pyWindows 任务栏身份(AppUserModel)属性写入

包元数据定义在 pyproject.toml:

[project] name = "flet-desktop" version = "0.1.0" description = "Flet Desktop client in Flutter" requires-python = ">=3.10" dependencies = [ "flet", "rich >=13.0.0" ]

值得注意的三点:

  • requires-python = ">=3.10":README 徽章中的python >=3.10即来源于此,低于 3.10 的环境无法安装。
  • 依赖flet与rich:前者保证桌面客户端与 SDK 配套,后者用于首次下载时在终端渲染进度条(见init.py 中的from rich.progress import Progress)。
  • version.py里的version = "":该占位值在 CI 发布构建时会被替换为与 Flet SDK 一致的版本号,这是后面「版本对齐」机制的基础。

安装方式与版本对齐:桌面客户端不是默认装上的

flet-desktop是flet的可选依赖,并不会随pip install flet自动安装。在 flet/pyproject.toml 中:

[project.optional-dependencies] all = ["flet-cli", "flet-web", "flet-desktop"] cli = ["flet-cli"] desktop = ["flet-desktop"] web = ["flet-web"]

因此两种手动安装方式:

# 方式一:只装桌面客户端 pip install "flet[desktop]" # 方式二:全量安装 pip install "flet[all]"

不过绝大多数用户不需要手动安装。flet-cli在需要时会自动检测并安装它。核心逻辑在 flet/utils/pip.py 的ensure_flet_desktop_package_installed():

  1. 尝试import flet_desktop.version;
  2. 若模块缺失,或flet_desktop.version.version与当前 SDK 的flet.version.flet_version不一致,则判定为「版本不匹配」;
  3. 通过install_flet_package("flet-desktop")安装与 SDK 完全同版本的包(安装时优先尝试pip,失败则回退uv pip,除非设置了UV环境变量则顺序相反)。

这种「按需自动安装 + 版本强对齐」的策略,从源码结构看是为了避免 Python SDK 与 Flutter 客户端二进制之间出现协议/版本错配——桌面窗口通信依赖两端严格同版本。

运行时获取与缓存:一次性下载,永久本地复用

flet-desktop包本身通常不携带客户端二进制(get_package_bin_dir()返回的app/目录默认为空),真正的 Flutter 客户端在首次运行时才被获取。这由 ensure_client_cached() 完成。

平台制品文件名

get_artifact_filename() 决定下载哪个产物:

平台制品文件名
Windowsflet-windows.zip
macOSflet-macos.tar.gz
Linuxflet-linux-{distro}[-light]-{arch}.tar.gz(如flet-linux-ubuntu22.04-light-x86_64.tar.gz)

Linux 的distro并非读系统发行版,而是按 glibc 版本选型。源码内置了_GLIBC_DISTRO_TABLE对照表(init.py):

最低 glibcdistro id
2.28debian10
2.31ubuntu20.04
2.35ubuntu22.04
2.36debian12
2.39ubuntu24.04

运行时通过ctypes调用 libc 的gnu_get_libc_version探测本机 glibc 版本(__get_system_glibc_version),选取「需求 glibc ≤ 系统 glibc」中最新的构建目标;检测失败则回退到最老的debian10并告警。

缓存目录与指纹

客户端解压后存放在版本化缓存目录(__get_client_storage_dir):

~/.flet/client/flet-desktop-{flavor}-{version}[-{fingerprint}]

fingerprint是客户端归档内容 SHA-256 的前 12 位十六进制。它的价值在于:被flet pack打过补丁(自定义图标/元数据)的客户端会获得独立的缓存目录,不会与普通客户端或其他应用的补丁客户端互相覆盖(见 write_archive_fingerprint 中 pack 侧写入的<archive>.sha256伴生文件,内容为<hash> <size>,运行时通过 size 校验伴生文件是否过期,过期则重新哈希)。

缓存目录还带有一套垃圾回收(__gc_stale_client_dirs):对超过 30 天未被使用(通过.last-used标记记录)的同前缀指纹兄弟目录,先改名为.trash-*再删除——改名失败(Windows 上文件正被占用)则安全跳过,同时清扫上次中断遗留的.trash-*残骸。

下载与原子解压

首次运行会打印Preparing Flet v{ver} for the first use. This is a one-time operation...,然后:

  1. 以flet_desktop.version.version构造下载 URL,可用FLET_CLIENT_URL环境变量整体覆盖(__download_flet_client);
  2. 用rich进度条显示下载进度,先落盘到临时目录;
  3. 解压到临时目录(Windows 用zipfile、其余用tarfile,均走safe_*_extractall安全解压),再整体rename为缓存目录——原子重命名保证「半解压的缓存」永远不会被当成有效缓存复用;
  4. 并发冲突时(两个进程同时解压同一归档),获胜者的缓存目录被视为等价,失败方清理自己的临时目录后直接复用。

三平台启动策略:build 产物 > 环境变量 > 缓存

真正拉起桌面窗口的函数是 open_flet_view()(同步版)与 open_flet_view_async()(异步版)。两者都调用核心的 __locate_and_unpack_flet_view(),其客户端解析优先级固定为:

  1. 当前工作区flet build的产物:检查build/windows、build/macos、build/linux目录。找到后打印警告(__log_build_client),提示「正在使用上次flet build构建的客户端;如需使用标准客户端请移动/重命名/删除 build 目录」——原因是旧构建可能缺少后来新增的扩展,导致应用调用扩展超时;
  2. FLET_VIEW_PATH环境变量(开发者模式):指向自定义客户端目录;
  3. ~/.flet/client/下的缓存/下载客户端:即上文ensure_client_cached()的结果。

Windows

启动命令为[flet.exe, page_url, pid_file],即客户端可执行文件 + 应用页面地址 + 一个临时 PID 文件路径。

macOS

先调用find_macos_app_bundle()定位目录下的.app包,然后执行open <app路径> -n -W --args <page_url> <pid_file>——-n强制新实例、-W等待窗口退出。

Linux

[flet, page_url, pid_file]直接执行缓存目录flet/flet下的可执行文件。

进程生命周期管理

启动前会在系统临时目录生成随机命名的 PID 文件,Flutter 客户端启动后把自己的 PID 写入该文件。flet run结束时由 close_flet_view(pid_file) 读取 PID 并发送SIGKILL终止窗口进程、删除 PID 文件。同步启动时还会传递assets_dir(作为额外参数)与hidden(写入环境变量FLET_HIDE_WINDOW_ON_START=true,实现「窗口隐藏启动」)。

关键环境变量一览

综合init.py、run.py 与 PyInstaller 运行时钩子:

环境变量作用默认值
FLET_CLIENT_URL整体覆盖客户端下载 URL基于版本的默认 Release 地址
FLET_DESKTOP_FLAVOR客户端风味,full/lightLinux 为light,其余平台full
FLET_LINUX_DISTRO强制指定 Linux 构建目标 distro id按 glibc 探测
FLET_VIEW_PATH开发者模式自定义客户端目录无
FLET_APP_IDLinux 窗口身份(WM_CLASS / app_id)无(未设置则用二进制名)
FLET_APP_USER_MODEL_IDWindows 任务栏 AppUserModelID无
FLET_APP_RELAUNCH_COMMAND/_DISPLAY_NAME/_ICONWindows 任务栏重启命令/名称/图标由 AUMID 派生
FLET_HIDE_WINDOW_ON_START窗口隐藏启动hidden参数为真时写入

其中FLET_DESKTOP_FLAVOR的解析顺序值得展开(__get_desktop_flavor):

  1. 环境变量FLET_DESKTOP_FLAVOR(仅接受full/light);
  2. 项目根目录pyproject.toml的[tool.flet].desktop_flavor;
  3. 兜底:Linux 上默认light,其他平台默认full。

「light」风味从文件名与缓存目录命名看,是面向 Linux 的精简构建变体(通常不带 WebView 等重组件),这解释了 Linux 客户端归档为何带-light后缀。

与 flet run 集成:热重载模式下如何拉起窗口

flet run的桌面启动链路在 run.py 中:

  • handle()中根据模式选包(L221-L226):--web/--ios/--android只确保安装flet-web,只有默认桌面模式才调用ensure_flet_desktop_package_installed();
  • 应用进程启动后,print_output监听其 stdout 中的页面 URL 前缀行(PAGE_URL_{timestamp}),解析出page_url;
  • 桌面模式下启动后台线程执行open_flet_view_and_wait()(L581-L602):调用flet_desktop.open_flet_view(page_url, assets_dir, hidden)打开窗口并阻塞等待其退出,窗口关闭后向应用进程发SIGTERM(2 秒超时后SIGKILL),最后设置终止事件结束整个 run 循环;
  • 结束时若存在 PID 文件,调用flet_desktop.close_flet_view()兜底清理。

对应的测试在 test_run_desktop_dependency.py,通过sys.meta_path屏蔽flet_desktop模块模拟其缺失,验证了:--web/--ios/--android三种服务模式均不触发flet-desktop安装,只有桌面模式安装;窗口打开后无论是否正常退出都会调用close_flet_view。

与 flet pack 集成:打包应用的任务栏与桌面身份

flet pack命令在 pack.py 中同样先执行ensure_flet_desktop_package_installed(),再用 PyInstaller 把「你的 Python 应用 + 一份补丁过的 Flet 客户端归档」打成独立可执行文件(copy_flet_bin将缓存客户端复制进 bundle,归档通过write_archive_fingerprint写入指纹伴生文件)。

打包应用在桌面环境的「身份」问题,是flet-desktop最精细的部分——因为窗口进程是共享的、预编译的flet客户端,所有打包应用默认会以 "flet" 的身份出现在任务栏/应用坞。

Windows:任务栏 AppUserModel 属性写入

win_taskbar.py 用纯ctypes直接调用 Win32/COM API,向客户端窗口的IPropertyStore写入System.AppUserModel.*属性。PyInstaller 运行时钩子(pyi_rth_localhost_fletd.py)预先设置了FLET_APP_USER_MODEL_ID(指向打包后的 exe 路径)及重启命令/名称/图标;flet_desktop启动客户端后(__apply_taskbar_props,init.py)在后台线程轮询等待客户端创建顶层窗口(窗口类FLUTTER_RUNNER_WIN32_WINDOW,与 client/windows/runner/win32_window.cpp 注册的类名一致),然后写入 AUMID、重启命令、显示名与图标,使任务栏名称、图标、跳转列表与固定(pin)全部指向宿主 exe。实现中通过持有SYNCHRONIZE句柄轮询进程存活,防止 PID 被系统回收后误写他进程。

Linux:argv[0] 技巧实现窗口身份

Linux 下 GTK 从g_application_run中basename(argv[0])推导prgname,进而生成 X11 的WM_CLASS与 Wayland 的app_id。因此 __linux_identity_args 采用一个巧妙的方案:用FLET_APP_ID作为argv[0]重启同一个二进制(executable=仍指向真实文件),让窗口以应用自己的名字出现在任务栏,从而匹配.desktop入口的StartupWMClass。运行时钩子在没有显式--bundle-id时,会读取 bundle 内的flet_app_id文件或回退到可执行文件基名来设置FLET_APP_ID。

小结

flet-desktop虽然 README 只有寥寥数行,却是 Flet「Python 写 UI、Flutter 渲染窗口」架构中承上启下的关键一环。它的工程细节——按 glibc 选型的 Linux 构建矩阵、基于内容指纹的版本化缓存与垃圾回收、三平台差异化的启动命令、以及通过argv[0]/AppUserModelID赋予共享客户端独立桌面身份的技巧——共同保证了「写 Python 应用 → 秒开原生窗口」这一体验的稳定性。结合 flet-desktop 源码、flet-cli run/pack 命令 与 CLI 测试,你可以完全还原从flet run到原生窗口出现的完整链路。

  • 前端
  • 跨平台
  • 桌面应用
  • 移动开发

【免费下载链接】flet

Build realtime web, mobile and desktop apps in Python only. No frontend experience required.

项目地址:https://gitcode.com/gh_mirrors/fl/flet
点击查看免费下载

相关推荐

上一篇:如何5分钟创建专业移动页面:零代码H5编辑器完整指南
下一篇:华为光猫配置洞察终极方案:从黑箱运维到透明管理的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 5:54:18

strace高级技巧与生产实战:从系统调用定位线上问题

上周线上有个服务接口偶发超时&#xff0c;日志里只能看到“上游组件超时”&#xff0c;CPU、内存全正常&#xff0c;看监控也找不到异常。我挂上 strace 抓了不到 10 分钟&#xff0c;就从系统调用时间戳里找到了真正的等待点。群里一个同事问了一句&#xff1a;“strace 还能…

作者头像 李华
网站建设 2026/10/10 5:53:00

kshell:为散落一地的AI编程会话建一个本地中央车站

装了一堆 AI 编程工具之后&#xff0c;真正让人抓狂的已经不是“哪个更好用”&#xff0c;而是会话散落一地——今天这个问题是在工具 A 里问的&#xff0c;那个报错是在工具 B 里解决的&#xff0c;一周以后想翻记录&#xff0c;手忙脚乱也找不到。我自己被这个状态折磨了快两…

作者头像 李华
网站建设 2026/10/10 5:51:48

物联网平台源码实战:从MQTT协议选型到海康摄像头接入

做物联网平台源码这类项目&#xff0c;最容易被低估的其实不是业务功能&#xff0c;而是设备接入层的通信协议——TCP/IP、MQTT、HTTP三条链路怎么分工&#xff0c;海康摄像头怎么取流&#xff0c;传感器报文怎么从一堆字节里把有效数据抠出来&#xff0c;这些东西搞不清楚&…

作者头像 李华
网站建设 2026/10/10 5:50:07

TaoToken 实战:让 AI 帮写注释并直接生成代码的配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 5:49:23

PCA9422+PIC32MX构建可编程电源管理子系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 5:49:06

PCA9422搭配STM32F100ZE:完整电源管理方案实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华