1. 移植前的灵魂拷问:你要的是“能跑”还是“能上架”
我接到的需求其实很常见:手里有一个用Python开发的内部工具,平时在电脑上跑得很舒服,但领导突然说“把它搬到Android上,我想在手机上直接用”。这个工具大概长这样——从Excel读取库存数据,按安全库存公式算补货建议,再输出一个报告,主流程不到1000行,依赖也简单,就是openpyxl和几个标准库。换成你,你第一反应是什么?我当时的想法很直接:Python能写到Android上吗?搜索一圈,号称能“运行Python”的方案多得让人眼花缭乱。真上手之后才明白,所谓Python项目移植Android,首先要回答的不是“用什么工具”,而是“你要的到底是哪一种使用形态”。
这里面的差异非常大。同样是“在Android上运行Python代码”,实际上最典型的只有三类:一是把Python整个Progressive Web App一样做成独立APK,用户装一个应用就能用;二是把Python作为“算法引擎”嵌入到已有的Android原生项目里,UI用Java/Kotlin来写;三是干脆不打包,直接在手机的终端环境里跑Python脚本,用浏览器或者一个壳来当界面。这三个目标对应的技术路线几乎不互通,选错了方向,后面几周基本就是反复推倒重来。
所以我在一开始强烈建议你问自己四个问题。第一,这个工具要不要给普通人用?如果给普通用户,必须做成独立可安装的App,Termux这种终端方案直接出局。第二,UI复杂度高不高?是按钮加列表就够,还是需要自定义画布、手势、复杂布局?这直接影响选Kivy还是选BeeWare。第三,有没有已经存在的Android原生项目?如果有,而且你只是想复用一段Python计算逻辑,那最合理的答案几乎必然是Chaquopy。第四,性能要求如何?纯Python打包的方案,性能虽然日常够用,但做大计算量、多线程渲染时会明显吃力。
我把这四层需求整理成一张对照表,方便你对照自己的项目现状来看:
| 使用形态 | 典型场景 | 优先考虑方案 |
|---|---|---|
| 独立App,从零开发 | 内部工具、表单应用、工具型产品 | Kivy / BeeWare |
| 嵌入现有原生App | 已有Android工程,只想复用Python算法 | Chaquopy |
| 临时原型、自用脚本 | 个人验证、演示给同事看 | Termux + Flask |
| 跨平台桌面+移动 | 同一套代码还要跑Windows/macOS | BeeWare 或 Kivy |
这个表不是万能的,但它能帮你先卡掉一大半错误方向。我这次就是拿同一个“库存补货建议工具”挨个跑了四条路:Kivy + Buildozer打成独立APK,BeeWare + Toga做成原生控件界面,Chaquopy嵌入到一个建好的空Android工程里,以及Termux + Flask配合WebView壳来跑。下面每一节我都会给出实测过程、构建数据以及我踩过的坑,最后再给一张完整对比表。你直接照着抄作业就行。
2. 方案一:Kivy + Buildozer,把纯Python项目直接打成APK
2.1 为什么先选Kivy:一套GUI代码全平台复用
Kivy是Python生态里最成熟的一套GUI框架,它最大的特点是界面的每个控件都是自己用OpenGL绘制的,不依赖Android原生控件。这意味着同一套代码在Windows、Linux、macOS、Android、iOS上都能跑,视觉表现一致。代价就是界面看起来不那么“Android”,控件和系统原生的交互习惯有割裂感。但对一个内部库存工具来说,界面丑一点完全不是问题,能把Excel逻辑跑起来才是关键。
我的演示项目叫stock_tool,核心计算逻辑从Excel读数据、算安全和补货量,最后生成一张“需要补货的商品清单”。在桌面上它原本是一个命令行脚本。移植到Kivy,我需要给这个逻辑套一层简单的UI:一个“加载Excel”按钮,一个显示补货建议的列表,顶部留一个状态栏。这层UI用Kivy写大概100行就够,真正的时间全花在打包环境上。
2.2 Buildozer打包的完整流程:环境、配置、构建
先说环境。Buildozer官方支持Linux和macOS,Windows用户必须走WSL或虚拟机,我这边是在Ubuntu 22.04上完成的,Python版本用的3.10。首先安装基础依赖,然后装Buildozer:
sudo apt update sudo apt install -y git zip unzip openjdk-17-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev pip3 install --user buildozer kivy然后是项目目录结构。Kivy项目要能够被Buildozer识别,主程序一般叫main.py,Python逻辑单独放一个包目录。我的stock_tool目录长这样:
stock_tool/ ├── main.py ├── stock_logic/ │ ├── __init__.py │ └── calc.py └── buildozer.specmain.py里写Kivy的App子类,计算逻辑全部调用stock_logic里的纯函数。Buildozer打包时,requirements就是告诉它要往APK里塞哪些Python依赖。我的配置是这样的:
# buildozer.spec 关键片段 [app] title = StockTool package.name = stocktool package.domain = org.example source.dir = . source.include_exts = py,png,jpg,kv,atlas version = 0.1 requirements = python3,kivy,openpyxl android.api = 33 android.minapi = 21 android.archs = arm64-v8a, armeabi-v7a android.accept_sdk_license = True这里有个特别容易踩的坑:如果你的逻辑里用了ssl、sqlite3这类标准库模块,注意有些在Android上不是默认带进APK的。我们在计算里不用外部数据库,但如果你用了sqlite3,requirements里得显式加上openssl或者相关的配方,否则运行时直接ModuleNotFoundError。第一次构建时Buildozer会下载Android SDK、NDK、python-for-android等一大堆工具,网速差的话等20到40分钟非常正常,耐心点,别中途关掉。
构建命令只有一行:
buildozer -v android debug构建成功后会生成bin/StockTool-0.1-arm64-v8a_armeabi-v7a-debug.apk,把这个APK发到手机上就能装。需要注意的是,debug签名的APK在部分国产手机上安装时会提示“未知来源”,这是正常的,关掉安装保护就行。
2.3 实测结果与三个我不得不写下来的坑
我的库存工具用Kivy方案打包后,实测数据如下:APK体积约35MB,冷启动到看到主界面约2.1秒,操作过程中内存峰值约120MB。对一个内部工具来说,这个表现可以接受。但构建过程里我栽了三个跟头,每一个都值得单独写一笔。
第一个坑是中文显示。Kivy默认字体在Android上不支持中文,运行起来所有中文全部变成方块。解决方法是准备一个中文字体文件,比如DroidSansFallback.ttf,放到项目目录,然后在main.py里通过Label的font_name参数指定:
Label(text="补货建议", font_name="data/fonts/DroidSansFallback.ttf")第二个坑是openpyxl的体积。我一开始把openpyxl和pandas都放进了requirements,结果APK直接干到60MB,而且pandas在Android上的python-for-android配方很重,编译时间翻倍。后来发现这个工具只需要读Excel,openpyxl就够,去掉pandas后APK降到35MB。所以打Android包时,依赖越少越好,能用标准库解决绝不引入第三方包。
第三个坑是Android架构的选择。如果你只配置了arm64-v8a,在旧手机上会直接装不上或崩溃。建议保留arm64-v8a和armeabi-v7a两个架构,代价是APK稍微大一点,但兼容性好很多。
Kivy这条路的优缺点我后来总结得很清楚:优点是纯Python、跨平台、社区资料多,适合工具型小App;缺点是构建时间长、界面非原生、打包体积偏大,而且一旦用了比较冷门的Python包,大概率要折腾配方。如果你的需求是“快速把现有Python脚本变成独立App”,这是最不容易翻车的一条路。
3. 方案二:BeeWare/Briefcase + Toga,界面更接近原生的新选择
3.1 BeeWare的定位:把Python翻译成平台原生控件
BeeWare和Kivy的思路完全不一样。Kivy是自己画界面,而BeeWare坚持“用Python写一套API,在Android上映射成Android原生控件,在macOS上映射成AppKit控件”。也就是说,你写的Toga按钮,在Android手机上最终是被系统渲染成原生Button的。这个特性让它对“Android原生化”有天然吸引力。
但Toga目前的问题也很明显:控件数量比Kivy少很多。像表格、树形控件这些Toga虽然有,但功能相对简陋,复杂交互需要自己造轮子。我的库存工具界面只要一个按钮和一个列表,用Toga来做就很合适,这也算是我选它做实测的原因。
3.2 Briefcase命令走一遍:从新建工程到打包APK
BeeWare的工程管理和打包工具叫Briefcase。它的使用体验比Buildozer更“正规”,每一步都有明确的子命令。先安装:
pip install briefcase然后新建项目,它会交互式问你项目名、应用名、包名等。我这边为了和Kivy版本对比,项目名也叫StockTool:
briefcase newBriefcase会生成一个标准Python项目结构,并且自动创建一个Toga窗口程序。核心代码在src/stocktool/app.py里,我改成了和Kivy版一样的库存计算界面:
import toga from toga.style import Pack from toga.style.pack import COLUMN, ROW from .stock_logic.calc import calc_suggestion def build(app): box = toga.Box(style=Pack(direction=COLUMN)) label = toga.Label("补货建议列表", style=Pack(padding=10)) items = toga.ListView(style=Pack(flex=1)) button = toga.Button("加载Excel", on_press=lambda widget: load_items(items), style=Pack(padding=10)) box.add(label) box.add(items) box.add(button) return box然后执行Android构建命令链:
briefcase create android briefcase build android briefcase run android第一次运行create时,Briefcase会下载Android SDK和Gradle依赖,网络不好又是个漫长的等待。构建完成后,运行APK会自动安装到连接的设备或模拟器上。如果你想产出可分发APK,用:
briefcase package android这个命令会在dist目录下生成正式安装包。整体流程比Buildozer更“标准化”,报错信息也更友好,但底层依赖Gradle,在中国网络环境下经常卡在Gradle Distribution下载,解决方案是手动把Gradle zip下载后放到~/.gradle/wrapper/dists对应目录里,再重新跑build。
3.3 和Kivy的实测对比:界面确实原生,但控件太少
我的Toga版本实测数据:APK体积约42MB,冷启动约2.8秒,内存峰值约100MB。启动比Kivy略慢半秒,但界面上的按钮、列表都是Android原生风格,观感清爽,不会像Kivy那样一眼看上去是“游戏引擎界面”。
Toga最大的坑是“看起来能用,用起来要命”。我的列表展示需要点击按钮后动态更新数据,Kivy只需要改ListAdapter的data属性,而Toga的ListView更新机制比较原始,费了好大劲才刷新成功。期间还遇到Toga的某个控件在Android API 33上有点击无响应问题,查Issue才知道是已知bug,升级到最新版才解决。如果你的界面就是表单加列表,BeeWare能给你很好的体验;但如果是复杂布局、手势、自绘控件,现阶段我建议绕道。
另外,Toga的历史比Kivy短,社区示例少,遇到问题很多时候只能读源码。这个学习成本在项目排期里必须算进去。我的建议是:界面简单且极度看重原生感,选BeeWare;界面复杂或对开发速度有要求,选Kivy。
4. 方案三:Chaquopy,把Python引擎塞进Android原生项目
4.1 什么时候该用Chaquopy而不是整个App都用Python
如果你的项目不是“把一个Python工具变成App”,而是“我已经有一个Android原生App,只想把某个Python算法模块移植进去”,那么Kivy和BeeWare几乎可以直接划掉。这两者的思路都是整个应用用Python编写,强行改造原生项目等于重写。这时候你需要的是Chaquopy。
Chaquopy是一个Gradle插件,它把完整的Python运行环境打包进Android应用,并提供了Java/Kotlin与Python之间的互调接口。最典型的场景是:你有一个训练好的机器学习模型,或者一段复杂的文本处理算法,用Python写很顺手,但不想用Java重写一遍。用Chaquopy,你就可以在原生Activity的点击事件里直接调用Python函数,拿到计算结果后继续用原生UI展示。
4.2 集成步骤:Gradle配置与Python互调代码
Chaquopy的集成非常依赖Android Studio,因为它是Gradle生态的一部分。我用的是Android Studio Hedgehog版本,AGP版本8.2。首先在项目的settings.gradle里加入仓库和插件地址:
pluginManagement { repositories { maven { url "https://chaquo.com/maven" } google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositories { maven { url "https://chaquo.com/maven" } } }然后在app模块的build.gradle里应用插件并配置:
plugins { id 'com.android.application' id 'com.chaquo.python' } android { defaultConfig { ndk { abiFilters 'arm64-v8a', 'armeabi-v7a' } } } chaquopy { defaultConfig { version = '3.10' pip { install 'openpyxl' } sourceSets { main { python { srcDir = 'src/main/python' } } } } }Python源码放在src/main/python目录下。我依然是放同一个库存计算逻辑模块。Java这边调用就非常简单了:
import com.chaquo.python.Python; import com.chaquo.python.PyObject; Python py = Python.getInstance(); PyObject module = py.getModule("stock_logic.calc"); PyObject result = module.callAttr("calc_suggestion", excelPath); String suggestion = result.toString();这里有几个必须注意的点。第一,Python.start需要在Application或Activity的onCreate里先调用,否则getInstance会报错。第二,任何耗时操作都不能放在主线程,Python计算虽然大部分时候很快,但万一Excel文件很大,一样会卡住UI。第三,Chaquopy对minSdkVersion有要求,太低的版本不支持,实测至少要API 21起步。
4.3 性能与包体积实测:Python嵌入到底值不值
我把同一个Excel文件分别放在Java层用原生方式解析、以及通过Chaquopy调用Python解析,对比结果更直观。Chaquopy的冷启动加载Python运行时约0.8秒,之后的函数调用单次约50毫秒,这个性能对绝大多数业务逻辑完全够用。代价是APK体积增加了约18MB,这是Python运行时的必要开销。如果项目本身就五六MB,增加18MB可能有点肉疼,但如果你的核心算法用Java重写需要两周,这个体积换开发效率,我认为非常值。
实测中最容易踩坑的是pip依赖。Chaquopy要求安装的Python包必须能在Android的ABI上编译运行。纯Python包大多没问题,但带C扩展的包就要看运气了。我在测试过程中尝试装过一个第三方库,Gradle构建直接报找不到合适的预编译wheel,最后放弃了那个依赖,改用纯Python实现。所以用Chaquopy前,最好先看看核心逻辑用的第三方包是否支持Android。
另外,Chaquopy不是让你用来写UI的。它的UI还需要用Java/Kotlin写,Python只作为算法引擎存在。如果你想着“那我全部逻辑用Python写,UI用Kotlin画”,那确实可以,但别把Python当主语言来架构整个App,否则跨语言通信代码会变得非常难维护。
5. 方案四:Termux + Flask + WebView,不打包也能交付的“旁门左道”
5.1 Termux本质:Android上的Linux用户空间,不是虚拟机
在所有方案里,Termux是门槛最低的一个。它不是模拟器,也不是虚拟机,而是在Android的应用层直接提供了一个Linux用户空间环境,你可以通过包管理器安装Python、Git、openssh等工具。打开Termux后跑这样几行命令,就能得到一个完整的Python运行环境:
pkg update && pkg upgrade pkg install python pip install flask openpyxl对很多Python开发者来说,这可能是最快能在手机上跑起自己脚本的方法。但代价也很明显——普通用户大概率不知道Termux是什么,更不可能自己去装包、运行命令行。所以Termux方案只适合自己用、演示给同事看,或者做临时原型验证,绝对不适合作为产品交付。
5.2 用Flask起本地服务,再用WebView壳包一层
我的做法是让Python脚本变成Flask服务,跑在手机本地端口上,然后通过浏览器访问。具体来说,在stock_tool目录下写一个app.py:
from flask import Flask, render_template_string, request from stock_logic.calc import calc_suggestion app = Flask(__name__) @app.route("/") def index(): return render_template_string("<form...><button type='submit'>加载Excel</button></form>") @app.route("/calc", methods=["POST"]) def calc(): result = calc_suggestion("data/inventory.xlsx") return str(result) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)然后在Termux里运行:
python app.py在手机浏览器里打开localhost:5000就能看到页面。如果你希望它看起来像一个App,可以再用Android Studio花几分钟建一个极简WebView壳,加载http://localhost:5000,并处理一下网络权限和页面自适应。我实测这个方案:Flask服务启动约3秒,内存约占60MB,简单计算接口耗时20毫秒。由于页面是Web渲染,UI想做成什么样都行,这是它最大的优势。
5.3 这个方案的两大硬伤,必须提前说清
硬伤一是服务生命周期。Flask进程在Termux里跑着,只要手机息屏或者系统把Termux进程杀了,服务就断了。你需要保持Termux在前台或者用termux-wake-lock保证持续运行,这会增加耗电;就算保住进程,普通用户也不会愿意为了用你的工具,得先忍受一个终端黑框常驻在后台。
硬伤二是分发路径。你只能把Termux APK、你的Python代码、还有安装步骤一起发给对方,对方要按步骤装、跑、开浏览器。这已经不是“交付App”,而是“教用户搭服务器”。所以我在用完之后,只把它当作快速验证工具——先把业务逻辑跑通、UI原型定下来,然后再决定用Kivy还是Chaquopy做正式版。如果你真的只是想自用,它无可替代地方便。
6. 横向对比与最终选型建议
6.1 四个方案关键指标汇总
四条路都跑完之后,我把关键指标整理成下面这张表,你可以直接用来对照选择。
| 对比维度 | Kivy + Buildozer | BeeWare + Toga | Chaquopy | Termux + Flask |
|---|---|---|---|---|
| App形态 | 独立APK | 独立APK | 嵌入原生App | 无APK,浏览器/WebView |
| UI风格 | 自绘,非原生 | 原生控件 | 原生控件(Java/Kotlin) | Web页面 |
| APK体积 | 约35MB | 约42MB | 原APK + 18MB | 不涉及 |
| 冷启动耗时 | 约2.1秒 | 约2.8秒 | Python引擎约0.8秒 | 服务启动约3秒 |
| 构建复杂度 | 高,依赖Buildozer/NDK | 中,依赖Gradle | 中,依赖Gradle/Chaquopy | 低,无需构建 |
| 第三方Python包支持 | 一般,要配配方 | 一般 | 依赖包能否编译到Android | 基本都行 |
| 适合用户 | 内部工具/个人开发者 | 界面简单的工具型App | 已有原生App想复用算法 | 自用原型/演示 |
6.2 按你的项目现状做选择
如果你是从零开始、没有Android原生开发背景、目标就是把Python脚本变成独立App,我个人最推荐Kivy。它的社区文档最全,遇到问题基本能搜到答案,坑虽然多但都被前人踩得差不多了。如果界面元素很少,只有几个按钮和列表,而且你希望它看起来更“正统Android”,可以优先试BeeWare,但要做好控件功能欠缺导致临时改方案的心理准备。
如果你已经有一个Android Studio工程,只是想把某个Python算法模块嵌进去,不要犹豫,直接用Chaquopy。它不会帮你的UI做任何加分项,但它是唯一能让原生项目和Python代码优雅共存的方案。相比之下Kivy和BeeWare都要求你的整个App以Python为核心,这在现有原生项目里几乎不可行。
如果你只是临时给一个小工具做验证,或者这个工具只给你自己用,Termux + Flask绝对是最节省时间的。你可以在1小时内从零跑通全部业务逻辑,然后等需求稳定了再去做正式打包。我自己现在遇到任何Python工具的“Android化”需求,第一件事都是先在手机Termux里把脚本跑起来,再考虑要不要打包。
6.3 心得:提前把核心逻辑和UI彻底分离,是唯一能让你少加班的“银弹”
这四条路线跑下来,我最大的感触是:无论你最终选了哪个方案,在动手之前,一定要把Python代码拆成“纯计算逻辑”和“界面胶水层”两部分。我因为一开始偷懒,把文件读取、按钮点击、结果展示全部写在一个脚本里,导致后来试Chaquopy时不得不花一个晚上把计算函数从命令行参数耦合中拆出来。如果一开始就定义好calc_suggestion(excel_path)这样一个与UI无关的函数,三条路线的迁移都会轻松很多。
具体建议是:把所有业务代码写成一个纯Python包,输入输出尽量用基本类型(str、int、list、dict),不要依赖任何GUI框架的类。这样Kivy、BeeWare、Chaquopy甚至Flask都能直接import同一个模块。我在最终版里就是把stock_logic这个包原封不动地用在了四条路径上,接不同UI时只改界面代码,不用动算法。
最后再分享一个小技巧:任何方案在正式投入前,先花半天时间做一个“最小冒烟测试”——就是写一个HelloWorld级别的小App,完整跑一遍你预想的打包环境和运行流程,再开始迁移完整代码。因为Buildozer下载环境、Chaquopy配置Gradle这些环节很容易卡住,如果等到完整代码写完再处理,你会同时面对“代码bug”和“构建bug”两堆问题。先跑通空壳,再往里填业务逻辑,整个过程会顺畅得多。Python项目移植Android,本来就没有一步到位的万能方案,但只要你按使用形态选对路线,再留好拆分的余地,这条路远比想象中靠谱。