news 2026/9/20 2:52:18

用Python+Flet开发跨平台App:从零编写界面到打包APK全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Python+Flet开发跨平台App:从零编写界面到打包APK全攻略

带个界面就得上 React、Vue,不会前端连个页面都做不了?我以前也是这么想的,直到我遇见了 Flet。这个基于 Flutter 的 Python 框架,让我一个纯后端出身、写 Python 爬虫和脚本的人,也能轻松搞出能跑在 Android、iOS、Web 甚至桌面端的跨平台应用。今天这篇就从头到尾聊聊,我是怎么用 Flet 从零写了一个能打包成 APK 的 App,顺便把中间踩过的坑、查过的资料、试过的方案都整理出来,给想走这条路的朋友一个参考。

这篇文章不假设你有任何前端基础,但默认你至少熟悉 Python 的基本语法——比如会写个函数、知道pip装包是怎么回事。我会尽量把每个环节讲透,尤其是网上资料比较少的那部分:打包 APK、处理 Gradle 报错、多线程更新 UI。如果你正在纠结“用 Python 能不能做 App”,这篇文章应该能给你一个比较明确的答案。

1. 核心思路与方案选型:为什么偏偏是 Flet

1.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 环境安装的几个坑

既然要用 Flet,Python 环境是第一步。关于 Python 的安装,网上教程很多,但我还是踩过几个坑,尤其针对 Windows 用户,有几点值得特别提一提:

  1. 下载 Python 时,安装向导第一屏的Add Python to PATH一定要勾选。很多朋友第一次装完 Python,在命令行敲python会看到 “Python was not found; run without arguments to install from the Microsoft Store” 这种提示,八成就是没勾这个选项,或者装了 Microsoft Store 版本的 Python。

  2. 装完在命令行验证版本时你会发现,pythonpython3可能不一样。Windows 下通常用python,macOS/Linux 下python3更常用。为避免混乱,我建议统一用python -V验证自己当前的默认版本。

  3. 一定要用虚拟环境。我见过太多人在系统全局环境里装了一堆包,最后不同项目之间互相打架。Flet 项目建议单独建一个虚拟环境,比如:

python -m venv flet_env

Windows 下激活是flet_env\Scripts\activate,macOS/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(target=main)

保存文件为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 扩展,然后用命令面板(Ctrl+Shift+P)选择 Python 解释器,最后指定到你创建的flet_env虚拟环境里,这样语法提示和自动补全才能正常工作。
  • PyCharm:社区版就够用,新建项目时可以指定虚拟环境,也可以打开现有项目后在 Settings 里把 Project Interpreter 改成flet_env的 python 可执行文件。

我平时主力用 VSCode,因为启动更快,界面也更清爽。但如果你更习惯 PyCharm 的调试功能,就直接用 PyCharm,Flet 应用本质上就是个 Python 脚本,任何能把 Python 跑起来的编辑器都没问题。

3. 核心控件与布局实操:写一个功能完整的待办清单 App

3.1 理解 Flet 的页面和控件层级

Flet 有一个很形象的概念:page是整个应用的最底层容器,它相当于一张画布,你往里面 add 的每个控件都是一个元素。为了控制布局,Flet 提供了几个常用的布局容器:

  • Row:让子控件水平排列,一行从左到右。
  • Column:让子控件垂直排列,一列从上到下。
  • Container:可以理解成“带样式和间距的盒子”,用来控制背景色、边框、圆角、内边距等。
  • Stack:则是“绝对定位式”布局,子控件可以叠加在同一位置,适合做悬浮按钮或浮动提示。

这几个控件熟悉之后,基本就能拼出大部分界面。我建议新手先别急着做复杂项目,先花半小时随便拖几个TextTextFieldElevatedButton到页面上,感受一下布局的变化,慢慢就有手感了。

Flet 控件的属性命名基本遵循 Flutter 的那一套,比如widthheightpaddingmarginborder_radiusexpand,只要你见过 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="输入待办事项...", width=300, on_submit=lambda e: add_task(e), ) todo_list = ft.Column(spacing=10) def add_task(e): text = task_input.value.strip() if not text: return task_row = ft.Row( controls=[ ft.Checkbox( on_change=lambda event, t=task_row: toggle_done(event, t) ), ft.Text(text, size=18), ft.IconButton( icon=ft.icons.DELETE_OUTLINE, on_click=lambda event, t=task_row: delete_task(t), ), ], spacing=10, ) 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( decoration=ft.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_click=add_task), ], spacing=10, ), todo_list, ) ft.app(target=main)

这段代码看起来短,但已经包含了好几个 Flet 的经典操作:TextField的事件回调、IconButton绑定删除操作、用lambda给回调传参、通过修改控件属性和列表来刷新页面。有一个非常容易踩的坑是:在add_task里创建task_row之后,立刻在Checkboxon_change里引用了task_row,但这时候task_row还没有全部构造完成。所以我在代码里用t=task_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 17,Android 打包链路的必要组件
  • Android SDK,至少包含 platform-tools 和 build-tools
  • Flutter SDK,Flet 底层调用 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 flutter's main gradle plugin imperatively using the apply script

我没记错的话,这个报错在 Flet 的某些版本和 Flutter 新版本组合时非常常见。原因大概是 Flutter 新版对 Gradle 插件的加载方式做了调整,不再推荐在settings.gradle或者build.gradle里用apply命令“命令式”地应用插件,而是要求用插件 DSL 的plugins {}块来声明式地加载。

遇到这个报错后,我的处理思路是:

  1. 找到 Flutter 工程里android/settings.gradleandroid/build.gradle,看里面是否有apply相关命令。
  2. 按照 Flutter 官方模板调整成新版声明式写法,通常是把apply plugin: 'com.android.application'替换成自定义plugins块。
  3. 如果不想手动改,可以把 Flet 升级到最新版,再重新执行flet build apk,因为新版本通常已经适配了新版 Flutter SDK 的构建方式。

这里有一个很实用的技巧:打包前先看 Flet 官方文档里对 Flutter 版本的要求,别用最新版的 Flutter,因为新 SDK 刚发布时往往会出现兼容性窗口期。我在实际项目中使用的就是官方文档指定的稳定版组合,这样打包基本一路顺畅。

4.4 iOS 和 Web 打包:一个一次性搞定,一个需要 macOS

除了 Android,Flet 也支持 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_click=start_task)) ft.app(target=main)

这个写法看起来很简单,但有一个很容易被忽略的点:线程函数内部去修改控件属性后,必须调用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-v8a

5.2 网络问题和依赖下载问题的排查思路

国内开发者在执行flutter buildflet build apk时,最常见的卡点就是网络,尤其是首次构建要下载大量的 Gradle 依赖和 Flutter 引擎产物。如果你遇到下载缓慢或超时,可以先确认网络是否稳定,然后考虑:

  • 检查是否需要配置镜像仓库。Flutter 官方文档提供了中国地区下载 Flutter SDK 的镜像地址,Android 的 Gradle 仓库也可以通过配置全局init.gradle或项目内build.gradle来指向可用镜像。
  • 设置更长超时时间或增加重试次数。Gradle 构建时偶尔会因为某个依赖没拉全而失败,多试几次有时就能过。
  • 如果构建总是卡在同一个依赖上,可以手动下载该依赖并放到本地的 Gradle 缓存目录。这个操作比较麻烦,但确实是解决“死局”的最后手段。

5.3 调试思路:学会看 Flet 的日志

Flet 应用的运行日志是比较友好的,出错时多数情况会直接打印 stack trace。遇到问题先别急着搜网,养成看日志的习惯:

  1. 看报错的第一行和最后一行,那通常是最真实的错误原因。
  2. 如果报错和按钮回调有关,去检查回调函数里是不是有变量引用不对,或者控件对象被重复替换。
  3. 如果报错和页面刷新有关,检查是否在控件创建后忘了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。真遇到报错也别慌,照着日志一行一行排查,所有的坑都会变成你以后的经验。

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

QQ空间历史说说导出完整指南:把全部说说变成Excel和本地图

QQ空间历史说说导出完整指南:把全部说说变成Excel和本地图 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 很多人的QQ空间说说能翻到中学时代,但真正想找某一条时…

作者头像 李华
网站建设 2026/9/20 2:46:31

付费墙与内容访问限制:规则内解锁文章全文的实用策略

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

作者头像 李华
网站建设 2026/9/20 2:46:23

MySQL UNION ALL 用法详解:结果集合并、去重与性能优化技巧

1. 先把UNION ALL的定位搞清楚:纵向拼接,不是横向拼接1.1 一句话说清它在做什么mysql里做结果合并,大家最常用到的就是UNION ALL。它的作用可以用一句话概括:把多个SELECT查询结果按行上下堆在一起,拼成一个更大的结果…

作者头像 李华