- 前端
- 跨平台
- 桌面应用
- 移动开发
【免费下载链接】flet
Build realtime web, mobile and desktop apps in Python only. No frontend experience required.
导读
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.py | Windows 任务栏身份(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():
- 尝试
import flet_desktop.version; - 若模块缺失,或
flet_desktop.version.version与当前 SDK 的flet.version.flet_version不一致,则判定为「版本不匹配」; - 通过
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() 决定下载哪个产物:
| 平台 | 制品文件名 |
|---|---|
| Windows | flet-windows.zip |
| macOS | flet-macos.tar.gz |
| Linux | flet-linux-{distro}[-light]-{arch}.tar.gz(如flet-linux-ubuntu22.04-light-x86_64.tar.gz) |
Linux 的distro并非读系统发行版,而是按 glibc 版本选型。源码内置了_GLIBC_DISTRO_TABLE对照表(init.py):
| 最低 glibc | distro id |
|---|---|
| 2.28 | debian10 |
| 2.31 | ubuntu20.04 |
| 2.35 | ubuntu22.04 |
| 2.36 | debian12 |
| 2.39 | ubuntu24.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...,然后:
- 以
flet_desktop.version.version构造下载 URL,可用FLET_CLIENT_URL环境变量整体覆盖(__download_flet_client); - 用
rich进度条显示下载进度,先落盘到临时目录; - 解压到临时目录(Windows 用
zipfile、其余用tarfile,均走safe_*_extractall安全解压),再整体rename为缓存目录——原子重命名保证「半解压的缓存」永远不会被当成有效缓存复用; - 并发冲突时(两个进程同时解压同一归档),获胜者的缓存目录被视为等价,失败方清理自己的临时目录后直接复用。
三平台启动策略:build 产物 > 环境变量 > 缓存
真正拉起桌面窗口的函数是 open_flet_view()(同步版)与 open_flet_view_async()(异步版)。两者都调用核心的 __locate_and_unpack_flet_view(),其客户端解析优先级固定为:
- 当前工作区
flet build的产物:检查build/windows、build/macos、build/linux目录。找到后打印警告(__log_build_client),提示「正在使用上次flet build构建的客户端;如需使用标准客户端请移动/重命名/删除 build 目录」——原因是旧构建可能缺少后来新增的扩展,导致应用调用扩展超时; FLET_VIEW_PATH环境变量(开发者模式):指向自定义客户端目录;~/.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/light | Linux 为light,其余平台full |
FLET_LINUX_DISTRO | 强制指定 Linux 构建目标 distro id | 按 glibc 探测 |
FLET_VIEW_PATH | 开发者模式自定义客户端目录 | 无 |
FLET_APP_ID | Linux 窗口身份(WM_CLASS / app_id) | 无(未设置则用二进制名) |
FLET_APP_USER_MODEL_ID | Windows 任务栏 AppUserModelID | 无 |
FLET_APP_RELAUNCH_COMMAND/_DISPLAY_NAME/_ICON | Windows 任务栏重启命令/名称/图标 | 由 AUMID 派生 |
FLET_HIDE_WINDOW_ON_START | 窗口隐藏启动 | hidden参数为真时写入 |
其中FLET_DESKTOP_FLAVOR的解析顺序值得展开(__get_desktop_flavor):
- 环境变量
FLET_DESKTOP_FLAVOR(仅接受full/light); - 项目根目录
pyproject.toml的[tool.flet].desktop_flavor; - 兜底: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.
相关推荐
从 0.1.4 到 1.0.0:Flet Flutter 客户端运行时(flet Dart 包)演进全解读
从 0.1.4 到 1.0.0:Flet Flutter 客户端运行时(flet Dart 包)演进全解读 Flet 是一个"只用 Python 就能构建实时
前端跨平台桌面应用移动开发Flet Windows 应用打包完整指南:使用 flet build windows 生成原生 .exe
Flet Windows 应用打包完整指南:使用 flet build windows 生成原生 .exe 本篇技术指南讲解如何用 Flet 官方 CLI 的
前端跨平台桌面应用移动开发Flet 桌面应用打包指南:使用 flet pack 生成带自定义图标的独立可执行程序
Flet 桌面应用打包指南:使用 flet pack 生成带自定义图标的独立可执行程序 flet pack 是 Flet 官方 CLI 提供的桌面打包命令,它封
前端跨平台桌面应用移动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考