
带个界面就得上 React、Vue不会前端连个页面都做不了我以前也是这么想的直到我遇见了 Flet。这个基于 Flutter 的 Python 框架让我一个纯后端出身、写 Python 爬虫和脚本的人也能轻松搞出能跑在 Android、iOS、Web 甚至桌面端的跨平台应用。今天这篇就从头到尾聊聊我是怎么用 Flet 从零写了一个能打包成 APK 的 App顺便把中间踩过的坑、查过的资料、试过的方案都整理出来给想走这条路的朋友一个参考。这篇文章不假设你有任何前端基础但默认你至少熟悉 Python 的基本语法——比如会写个函数、知道pip装包是怎么回事。我会尽量把每个环节讲透尤其是网上资料比较少的那部分打包 APK、处理 Gradle 报错、多线程更新 UI。如果你正在纠结“用 Python 能不能做 App”这篇文章应该能给你一个比较明确的答案。1. 核心思路与方案选型为什么偏偏是 Flet1.1 先弄明白 Flet 到底是什么Flet 在官方的定位是 “Flutter for Python”它把 Flutter 的 UI 模型映射成了 Python 的控件模型让你用纯 Python 语法描述界面跑起来之后实际上是调用 Flutter 的渲染引擎来绘制。这里有个关键点要理解清楚Flet 不是把 Python 代码翻译成 Java/Kotlin/Swift 原生代码而是让 Python 代码通过 WebSocket 实时和 Flutter 引擎通信把控件树、交互事件、数据更新同步过去。打个比方Flet 就像一个远程遥控器Python 代码是遥控器面板Flutter 引擎是电视屏幕你按遥控器的时候电视就跟着变画面。这个架构最直接的好处是你不直接写 Dart也不用碰 XML 布局文件所有 UI 逻辑全部留在 Python 里这对 Python 开发者来说几乎没有任何额外学习成本。我最早接触到这个项目是在 GitHub 上看到一个简单示例十几行代码就启动了一个可交互的页面。当时我就在想这不就是我一直想要的东西吗后来翻文档才发现它背后是 Flutter顿时就明白为什么UI渲染效果那么流畅了。1.2 和 Electron、Kivy、Tauri 比优势在哪在 Flet 出现之前Python 开发桌面应用最常用的方案是 Tkinter 和 PyQt后来有 Electron 配 Python 后端也有 Kivy 这种主打移动端的跨平台框架。每个方案我都或多或少折腾过说说我的体感Electron Python 后端最难搞的是打包分发给用户Electron 应用体积基本都是 100MB 起步而且内存占用长期居高不下。我做过一个简单的工具型 App打开后内存直接到 300MB用户吐槽不断。Tkinter写起来倒是简单就是界面风格实在太过时了二十一世纪的设计语言它一个都不占现代一点的交互效果基本做不出来。PyQt / PySide功能强大UI 写起来也算顺手但一是授权协议有讲究二是界面和交互往往需要靠 QSS 手动打磨想做得漂亮挺费功夫。Kivy官方主打移动端但环境配置相对复杂在 Android 上的打包流程也比较繁琐社区资料和示例偏少遇到问题解决问题的时间成本很高。Flet 的出现对我来说恰好把这个组合问题给解掉了界面上它继承了 Flutter 的 Material Design 风格视觉效果有保障开发语言是纯 Python对后端和脚本出身的开发者非常友好打包则直接借助 Flutter 的构建管线一套代码目标覆盖 Android、iOS、Web 和桌面端。可以说Flet 把“像写 Python 脚本一样写应用界面”和“像 Flutter 一样高性能渲染”这两件事揉在了一起。2. 环境准备与快速上手先把第一个界面跑起来2.1 Python 环境安装的几个坑既然要用 FletPython 环境是第一步。关于 Python 的安装网上教程很多但我还是踩过几个坑尤其针对 Windows 用户有几点值得特别提一提下载 Python 时安装向导第一屏的Add Python to PATH一定要勾选。很多朋友第一次装完 Python在命令行敲python会看到 “Python was not found; run without arguments to install from the Microsoft Store” 这种提示八成就是没勾这个选项或者装了 Microsoft Store 版本的 Python。装完在命令行验证版本时你会发现python和python3可能不一样。Windows 下通常用pythonmacOS/Linux 下python3更常用。为避免混乱我建议统一用python -V验证自己当前的默认版本。一定要用虚拟环境。我见过太多人在系统全局环境里装了一堆包最后不同项目之间互相打架。Flet 项目建议单独建一个虚拟环境比如python -m venv flet_envWindows 下激活是flet_env\Scripts\activatemacOS/Linux 下是source flet_env/bin/activate。激活后命令行前面会出现(flet_env)的提示这时候装的包都隔离在这个环境里不会污染全局。2.2 安装 Flet 并启动第一个页面装 Flet 很简单激活虚拟环境后pip install flet然后检查安装版本flet --version如果能看到版本号就说明装好了。接着写一个最简单的页面验证一下整套流程import flet as ft def main(page: ft.Page): page.title 我的第一个 Flet 应用 page.add( ft.Text(Hello, Flet!) ) ft.app(targetmain)保存文件为hello.py然后在命令行运行python hello.py这时候会自动弹出一个桌面窗口显示 “Hello, Flet!”。如果你的电脑没有桌面环境比如纯净服务器Flet 也支持只启动 Web 模式flet run --web hello.py这种方式会在本机起一个 HTTP 服务你用浏览器打开http://localhost:8550就能看到界面了。我觉得这个特性特别适合在开发调试阶段快速预览。2.3 用 VSCode 还是 PyCharm有朋友问 Flet 开发到底用什么编辑器我的个人经验是VSCode 和 PyCharm 都能用重点不在编辑器本身而在于 Python 解释器是否选对。VSCode需要先在插件市场装好 Python 扩展然后用命令面板CtrlShiftP选择 Python 解释器最后指定到你创建的flet_env虚拟环境里这样语法提示和自动补全才能正常工作。PyCharm社区版就够用新建项目时可以指定虚拟环境也可以打开现有项目后在 Settings 里把 Project Interpreter 改成flet_env的 python 可执行文件。我平时主力用 VSCode因为启动更快界面也更清爽。但如果你更习惯 PyCharm 的调试功能就直接用 PyCharmFlet 应用本质上就是个 Python 脚本任何能把 Python 跑起来的编辑器都没问题。3. 核心控件与布局实操写一个功能完整的待办清单 App3.1 理解 Flet 的页面和控件层级Flet 有一个很形象的概念page是整个应用的最底层容器它相当于一张画布你往里面 add 的每个控件都是一个元素。为了控制布局Flet 提供了几个常用的布局容器Row让子控件水平排列一行从左到右。Column让子控件垂直排列一列从上到下。Container可以理解成“带样式和间距的盒子”用来控制背景色、边框、圆角、内边距等。Stack则是“绝对定位式”布局子控件可以叠加在同一位置适合做悬浮按钮或浮动提示。这几个控件熟悉之后基本就能拼出大部分界面。我建议新手先别急着做复杂项目先花半小时随便拖几个Text、TextField、ElevatedButton到页面上感受一下布局的变化慢慢就有手感了。Flet 控件的属性命名基本遵循 Flutter 的那一套比如width、height、padding、margin、border_radius、expand只要你见过 Flutter 的 UI 描述一看就能明白。3.2 设计待办清单的数据结构和交互逻辑我做一个待办清单Todo App来演示因为它麻雀虽小、五脏俱全能覆盖输入控件、列表展示、事件处理、状态刷新这几个核心环节正好是新手入门 Flet 最好的范例。首先明确需求顶部有一个输入框和一个“添加”按钮点击按钮后把输入内容加入列表列表每一项前有复选框勾选后内容变成删除线每一项后面有一个删除按钮点击后移除该项对应的数据结构很简单用一个 Python 的list来维护待办事项列表里每一项是一个dict包含文本内容和完成状态todos [ {text: 写文章, done: False}, {text: 买菜, done: True}, ]显示的时候用 for 循环遍历这个列表动态生成一行行的控件塞到Column里。这里要特别说明一下Flet 的“状态刷新”是你修改数据后需要把控件重新 add/update 到页面上页面才会更新。它不是响应式数据绑定驱动这一点和 Vue、React 的思路不一样。3.3 动手实现完整代码示例import flet as ft def main(page: ft.Page): page.title 待办清单 page.horizontal_alignment center page.padding 20 task_input ft.TextField( hint_text输入待办事项..., width300, on_submitlambda e: add_task(e), ) todo_list ft.Column(spacing10) def add_task(e): text task_input.value.strip() if not text: return task_row ft.Row( controls[ ft.Checkbox( on_changelambda event, ttask_row: toggle_done(event, t) ), ft.Text(text, size18), ft.IconButton( iconft.icons.DELETE_OUTLINE, on_clicklambda event, ttask_row: delete_task(t), ), ], spacing10, ) task_row.data {text: text, done: False} todo_list.controls.append(task_row) task_input.value page.update() def toggle_done(event, task_row): # 根据复选框状态切换文本删除线 task_row.controls[1].style ft.TextStyle( decorationft.TextDecoration.LINE_THROUGH if event.control.value else None ) task_row.data[done] event.control.value page.update() def delete_task(task_row): todo_list.controls.remove(task_row) page.update() page.add( ft.Row( controls[ task_input, ft.ElevatedButton(添加, on_clickadd_task), ], spacing10, ), todo_list, ) ft.app(targetmain)这段代码看起来短但已经包含了好几个 Flet 的经典操作TextField的事件回调、IconButton绑定删除操作、用lambda给回调传参、通过修改控件属性和列表来刷新页面。有一个非常容易踩的坑是在add_task里创建task_row之后立刻在Checkbox的on_change里引用了task_row但这时候task_row还没有全部构造完成。所以我在代码里用ttask_row作为 lambda 默认参数把当前行绑定进去这样事件触发时能拿到正确的行对象。3.4 运行并观察 UI 更新的逻辑运行这个脚本你会看到窗口里是一个简简单单的两列布局。输入内容、点击添加后todo_list.controls增加了一个Row然后调用page.update()界面立刻刷新。勾选复选框时event.control.value就是当前复选框的选中状态用这个值去切换删除线。删除按钮则直接按行对象为维度从列表里移除。我刚开始学 Flet 的时候总会下意识把数据和控件分得特别清但其实在这个框架里控件本身就是数据的一部分尤其是你需要在界面里动态增删条目时直接操作Column.controls是最省心的做法。4. 打包 APK把 Python 应用装进手机4.1 Flet 打包的整体流程说清楚Flet 应用最终是构建在 Flutter 之上的所以打包 APK 并不是像 PyInstaller 那样直接把 Python 解释器一起打进去而是借助 Flet 提供的构建工具把 Python 应用托管到一个嵌入式 Python 环境里再通过 Flutter 工程打包。想打包 Android APK电脑上必须装以下东西Python 3.8这是跑 Flet 代码的基础JDK 17Android 打包链路的必要组件Android SDK至少包含 platform-tools 和 build-toolsFlutter SDKFlet 底层调用 Flutter 构建命令如果你从来没碰过 Flutter装 SDK 这块可能会有点懵。我建议按官方文档一步步来先装 JDK再装 Android Studio顺手把 SDK 装了最后装 Flutter 并把它加入 PATH。用命令行执行flutter doctor检查环境是否齐全直到不出现红叉。4.2 实操flet build apk 命令怎么用进入你的 Flet 项目目录运行flet build apk如果一切顺利稍等几分钟第一次打包需要下载依赖会比较慢就能在build\apk\release目录下看到app-release.apk文件。这个文件直接传到手机上就能安装不需要额外签名。但我必须提前打预防针第一次打包大概率不会一遍过绝大多数报错集中在 Flutter 环境和 Gradle 配置上。4.3 热词里那个长长的报错Flutter Gradle 插件问题热搜词里有一条you are applying flutters main gradle plugin imperatively using the apply script我没记错的话这个报错在 Flet 的某些版本和 Flutter 新版本组合时非常常见。原因大概是 Flutter 新版对 Gradle 插件的加载方式做了调整不再推荐在settings.gradle或者build.gradle里用apply命令“命令式”地应用插件而是要求用插件 DSL 的plugins {}块来声明式地加载。遇到这个报错后我的处理思路是找到 Flutter 工程里android/settings.gradle和android/build.gradle看里面是否有apply相关命令。按照 Flutter 官方模板调整成新版声明式写法通常是把apply plugin: com.android.application替换成自定义plugins块。如果不想手动改可以把 Flet 升级到最新版再重新执行flet build apk因为新版本通常已经适配了新版 Flutter SDK 的构建方式。这里有一个很实用的技巧打包前先看 Flet 官方文档里对 Flutter 版本的要求别用最新版的 Flutter因为新 SDK 刚发布时往往会出现兼容性窗口期。我在实际项目中使用的就是官方文档指定的稳定版组合这样打包基本一路顺畅。4.4 iOS 和 Web 打包一个一次性搞定一个需要 macOS除了 AndroidFlet 也支持 iOS 打包和 Web 部署。iOS前提是你有一台 macOS 电脑并安装 Xcode。然后执行flet build ipa能生成 IPA 包模拟器版或真机版。如果你不是 iOS 开发者也没有 Apple 开发者账号签名和上架这块会比较麻烦可以先用模拟器调试。Web执行flet build web会在build/web目录下生成一套纯前端静态文件包括 HTML、CSS 和 JavaScript。你可以把整个目录扔到 Nginx、GitHub Pages 或任意静态服务器上就能得到一个可以在浏览器里访问的网页版应用。访问设备不需要装任何环境打开浏览器就能用这一点在演示和宣传时特别有优势。4.5 多线程更新 UI 的正确姿势热搜词里也有“flutter 多线程”。Flet 里要处理耗时任务如果直接在事件回调里做复杂计算或网络请求页面会卡住体验很差。正确做法是把耗时任务放到单独线程里同时保证 UI 更新在主线程完成。Flet 提供page.run_thread方法它专门用来在线程中执行函数并安全地更新页面。举个实际例子我做过一个从网上爬取数据并展示的小工具思路是import time import flet as ft def main(page: ft.Page): status_text ft.Text(准备中...) page.add(status_text) def heavy_task(): time.sleep(3) status_text.value 计算完成 status_text.update() def start_task(e): status_text.value 运行中... status_text.update() page.run_thread(heavy_task) page.add(ft.ElevatedButton(开始, on_clickstart_task)) ft.app(targetmain)这个写法看起来很简单但有一个很容易被忽略的点线程函数内部去修改控件属性后必须调用update()或让页面整体刷新否则界面不会发生变化。而且注意page.run_thread内部实际上处理了线程和 UI 间的调度比你自己直接写threading.Thread要安全得多。如果非得自己开threading.Thread那你在线程里更新 UI 时一定要小心尽量避免并发修改同一个控件以免出现乱七八糟的界面状态。5. 常见问题与排查技巧实录从入门到打包的速查表5.1 我在实际项目中踩过的高频问题这里整理一份 Flet 开发过程中的问题速查表都是我在真实项目里遇到过、并且从网上翻资料验证过的不是纸上谈兵问题现象可能原因解决办法ModuleNotFoundError: No module named flet没安装 Flet 或没激活虚拟环境执行pip install flet确认命令行前缀有(flet_env)启动页面后窗口不显示桌面环境不支持改用flet run --web hello.py点击按钮没有反应回调函数没加event参数on_click回调固定有一个参数建议写成def handler(e):界面刷新后控件空白新控件没进行update()手动调用page.update()或控件自身的update()flutter doctor提示 Android licenses not accepted还没接受 Android SDK 协议执行flutter doctor --android-licenses打包时提示 Gradle 下载超时网络访问 Gradle 仓库不稳定根据报错配置镜像仓库或重试等待时间加长flet build apk报 Java 版本不对JDK 版本不兼容切换为 JDK 17并在环境变量中确认java -version打包后 Apk 体积过大包含了多架构的 Flutter 引擎可在构建配置里限制targetPlatforms比如只保留arm64-v8a5.2 网络问题和依赖下载问题的排查思路国内开发者在执行flutter build或flet build apk时最常见的卡点就是网络尤其是首次构建要下载大量的 Gradle 依赖和 Flutter 引擎产物。如果你遇到下载缓慢或超时可以先确认网络是否稳定然后考虑检查是否需要配置镜像仓库。Flutter 官方文档提供了中国地区下载 Flutter SDK 的镜像地址Android 的 Gradle 仓库也可以通过配置全局init.gradle或项目内build.gradle来指向可用镜像。设置更长超时时间或增加重试次数。Gradle 构建时偶尔会因为某个依赖没拉全而失败多试几次有时就能过。如果构建总是卡在同一个依赖上可以手动下载该依赖并放到本地的 Gradle 缓存目录。这个操作比较麻烦但确实是解决“死局”的最后手段。5.3 调试思路学会看 Flet 的日志Flet 应用的运行日志是比较友好的出错时多数情况会直接打印 stack trace。遇到问题先别急着搜网养成看日志的习惯看报错的第一行和最后一行那通常是最真实的错误原因。如果报错和按钮回调有关去检查回调函数里是不是有变量引用不对或者控件对象被重复替换。如果报错和页面刷新有关检查是否在控件创建后忘了update()。我记得有一次我写一个表单点击“提交”后页面没有任何反馈费了半天劲才发现是回调函数签名少了一个参数导致根本没有调用成功。从那以后凡是页面无反应的我都先怀疑回调函数。5.4 当你卡住时哪里找帮助最高效Flet 的社区目前还算活跃但中文资料相对少。我常用的求助路径Flet 官方文档虽然是英文但结构清晰控件参考也很全遇到某个控件不知道怎么用直接去查对应页面。GitHub Issues特别是报错信息里包含 Flet 或 Flutter 关键词时GitHub 上基本都能搜到类似问题里面往往有官方维护者的回复。我遇到过的 Gradle 报错就是在 Issue 里找到的解决方案。Stack Overflow搜索 “flet” 关键词已经有不少问答了查问题时记得把关键词从宽到窄都试一遍。Flutter 相关社区由于 Flet 底层是 Flutter很多 UI 布局、打包报错的处理经验其实是通用的。你用 Flutter 的关键词去搜常常能获得比 Flet 更丰富的资料。6. 小技巧与我的心得体会6.1 把这个项目继续扩展的方向待办清单只是起点你完全可以在 Flet 基础上继续做更多应用做一个个人记账本用图表展示每月的收支变化做一个定时提醒工具用page.run_thread动态更新页面做一个内部工具平台把公司常用的运维脚本、爬虫工具聚合成一个带界面的 Web 服务做一个考勤打卡页面连上后端 API 后就能在手机和浏览器上同时使用Flet 的 Web 版本尤其适合快速交付内部工具后端逻辑用 Python前端界面用 Flet部署到服务器后团队成员只要打开浏览器就能用不用装任何客户端。我做过最顺手的一个内部小工具从想法到上线只花了一个晚上用户体验虽然谈不上惊艳但胜在实用和轻量。6.2 我的真实体验别再纠结“不懂前端”这件事回到题目那句话“不懂前端也能写 App”这句话在我身上基本是成立的。我写了几年 Python 脚本和爬虫对 JavaScript 和 CSS 始终提不起兴趣但 Flet 让我第一次感到“做界面也还挺轻松的”。尤其是它的热重载效果改完代码页面能实时刷新开发节奏比我想象中快很多。如果你也想尝试跨界做点带界面的工具我的建议是先从最笨的办法开始照着文档写一个最小示例然后一点点往里面加功能。别一开始就规划庞大架构先让一个按钮真正“点得动”再考虑更复杂的事。这样每完成一个微小闭环都会增加一点信心而自信心往往是学习新东西最重要也最容易忽略的因素。6.3 最后一个提醒留意 Flet 与 Flutter 版本的兼容性最后再分享一个我自己用真金白银换来的经验Flet 版本和 Flutter 版本之间是有对应关系的升级别太激进。我遇到过一次升级 Flet 后原本好好的项目突然打不了包的情况查了一整天才发现是它内部指定的 Flutter 版本和本地 Flutter SDK 对不上。后面学乖了每次升级前先看 release notes升级完立刻跑一遍flet build apk验证不给自己留过夜的问题。如果你也是刚入坑 Flet愿你第一次打包就顺利生成那个属于自己的 APK。真遇到报错也别慌照着日志一行一行排查所有的坑都会变成你以后的经验。