简介:这套面向Android开发者的Python转APK打包工具源码,基于python-for-android框架,可将Python程序编译为独立安卓应用,适用于Kivy跨平台项目及其他Python移动端改造场景。资源共588个文件,压缩包仅1.87MB,包含265个py源码、94个patch补丁、29个java桥接文件以及构建配置所需的mk、gradle、xml等,覆盖从解释器编译、依赖库配置到APK生成的完整流程。已有756人学习下载。通过研读这份项目,读者可以理解python-for-android的双阶段工作方式:先构建可定制的Python环境,再生成独立Android工程并产出不同名称、图标、代码的APK,适合想要深入掌握移动端打包原理或二次开发打包工具的初中级开发者。
1. Python 应用变 APK:不是转译,是打包
先说结论:把 Python 应用变成安卓 APK,不是把 Python 代码翻译成 Java 或 Kotlin,而是把 Python 解释器和你写的源码一起塞进 APK,在安卓上启动一个嵌入式解释器来跑你的程序。这是当前唯一成熟、可上线的路线。你写好的爬虫脚本、PDF 处理工具、深度学习推理服务,只要 UI 层用对框架,就能在一个 APK 里原样跑起来。本文会按一条完整可复现的链路来讲:先用 Kivy 做界面层,再用 Buildozer 打包出 APK,然后处理签名、架构裁剪和真机验证。适合两类人:一是想把自己现成 Python 工具变成手机 App 的开发者,二是在 Android Studio 里写原生应用、想内嵌 Python 引擎做热更新或算法集成的安卓开发。整个流程里,坑集中在环境、依赖和权限三块,下文逐个拆开。
2. Kivy 与 Buildozer 环境搭建:把 Py 项目变成安卓工程的第一步
2.1 为什么桌面端的 Tkinter、PyQt 不能直接用
很多人的第一个问题是:我写的 Tkinter 程序,能不能直接打成 APK?答案是不能。Tkinter 依赖系统级 Tk 库,安卓上没有这套原生控件;PyQt 虽然维护过安卓分支,但 Qt for Android 的构建流程和 Python 打包没打通,你要手动处理一堆 so 文件,基本等于重新学一套交叉编译。Kivy 是纯 Python 实现的 UI 框架,内部通过 OpenGL ES 自绘全部控件,不依赖系统组件,因此能被 python-for-android(简称 p4a)完整打包进 APK。选型理由很直接:Kivy 是唯一能让你用纯 Python 写界面、还能走成熟打包链路的方案。
提示:如果你只是想把 Python 逻辑嵌进现有安卓 App,界面用原生开发,那选 Chaquopy 更合适,它不做 UI,只把 Python 引擎以依赖方式加进 Gradle 工程。本文按 Kivy 全 Python 路线展开。
2.2 按 python 安装教程在 Ubuntu 上准备构建环境
打包 APK 不要在 Windows 上做。Buildozer 依赖 Linux 工具链,Windows 下只能通过 WSL 2 跑;macOS 能构建,但坑比 Ubuntu 多。我一般准备一台 Ubuntu 22.04 的机器或云服务器,先按常规 python 安装教程把 Python 3.8~3.11 装好,然后安装 Buildozer 和 Cython:
# 建议在虚拟环境里操作,避免污染系统 Python python3 -m venv venv source venv/bin/activate # 安装 buildozer 和 cython pip install --upgrade buildozer cython # 安装系统级依赖,缺一不可 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 libltdl-dev逻辑说明:buildozer 本身只是调度器,真正干活的是 python-for-android,它要做三件事:下载安卓 SDK/NDK、交叉编译 Python 解释器、把 Kivy 和你的代码打包成 APK。openjdk-17-jdk用于 Gradle 编译;libffi-dev和libssl-dev是 Python 交叉编译时的硬依赖,缺失会报No such file or directory这类奇怪错误。libtinfo5在 Ubuntu 22.04 上默认仓库没有,需要手动下载 deb 包安装,否则 curses 相关模块编译失败。
2.3 最小可跑 Demo:main.py 与 buildozer.spec 的首次生成
写一个最小 Kivy 应用,确认 UI 层能启动:
# main.py from kivy.app import App from kivy.uix.label import Label class DemoApp(App): def build(self): # Label 是 Kivy 自绘控件,不依赖系统原生组件 return Label(text="Hello from Python on Android") if __name__ == "__main__": DemoApp().run()逻辑说明:这个文件是整个 APK 的入口。p4a 在安卓端启动一个PythonActivity,由它初始化 Python 解释器,然后执行main.py。所以main.py必须放在项目根目录,且类名是什么都行,Buildozer 不关心类名,只认文件。
同目录下初始化 Buildozer 配置:
# 生成 buildozer.spec buildozer init # 第一次构建 debug 包,-v 输出完整日志 buildozer -v android debug首次构建会下载 NDK 和 SDK,时间很长,建议先确认网络稳定。构建产物在bin/目录下,名如demoapp-0.1-arm64-v8a-debug.apk。
2.4 requirements 里怎么写:不只是你自己的模块
buildozer.spec里最重要的配置是requirements。它声明的是“需要在安卓上重新编译/安装的 Python 包”,不是 pip 的 requirements.txt。比如你用到了requests,要在这一行写上:
requirements = python3,kivy,requests逻辑说明:这里python3是必须的,kivy由 p4a 特殊处理(它不是走 pip 安装,而是用 p4a 自己的配方编译)。纯 Python 包可以直接写名字,p4a 会走 pip;带 C 扩展的包则要确认有没有对应配方,比如numpy、pillow有官方配方,冷门 C 包大概率失败。
3. buildozer.spec 参数清单与 ABI 裁剪:直接决定 APK 能不能装、够不够小
3.1 buildozer.spec 生成的默认配置,逐项看
buildozer init生成的 spec 文件有上百行,但真正要动的就十几个。以下是默认配置中影响 APK 能否安装的关键项:
| 参数 | 默认值 | 作用 |
|---|---|---|
title | My Application | 桌面图标下显示的应用名 |
package.name | myapp | 应用短名称,参与包名组合 |
package.domain | org.test | 与 name 组合成完整包名org.test.myapp |
source.include_exts | py,png,jpg,kv,atlas | 决定哪些文件被打进 APK |
android.api | 33 | 编译时用的 SDK 版本 |
android.minapi | 21 | 最低支持的安卓版本,低于 21 无法安装 |
android.ndk_api | 25 | NDK 兼容 API,影响 so 文件的链接目标 |
android.archs | arm64-v8a, armeabi-v7a | 支持的 CPU 架构,见 3.2 |
android.accept_sdk_license | False | 必须改成 True,否则 SDK 组件下载中断 |
requirements | python3,kivy | Python 依赖清单 |
参数说明:android.minapi和android.ndk_api这两个值建议保持默认。minapi设太低(比如 16)会导致部分现代 Python 包的 C 代码编译失败;ndk_api是 so 文件链接时的目标 API 级别,乱调会出现运行时dlopen failed错误。android.accept_sdk_license记得先改成True,然后就不用在交互里按y了。
3.2 ABI 裁剪实录:从 60MB 降到 25MB
ABI(应用二进制接口)是最能压缩 APK 体积的杠杆。默认打arm64-v8a和armeabi-v7a两种架构,APK 体积是两份 so 的叠加。现在市面上手机 99% 都是 64 位,且国内应用市场从 2023 年起强制要求支持 64 位,所以舍掉armeabi-v7a是合理的:
# buildozer.spec 中修改 android.archs = arm64-v8a只保留 arm64-v8a 后,Python 解释器、Kivy 的 so 文件、OpenSSL 等体积全部减半。实操中还要区分你是在真机调试还是发布:调试期用buildozer android debug armeabi-v7a这种临时指定架构的命令也行,但正式包统一走 arm64-v8a。
注意:如果项目里有第三方 so 文件(比如自己编译的 OpenCV),必须保证 so 的架构和
android.archs一致,否则安装后会在启动阶段崩溃,logcat 里能看到dlopen failed: cannot locate symbol。
3.3 图标、启动图和屏幕方向
# buildozer.spec 中修改 orientation = portrait fullscreen = 0 icon.filename = %(source.dir)s/assets/icon.png presplash.filename = %(source.dir)s/assets/presplash.png参数说明:orientation = portrait锁定竖屏,适合工具类 App,避免横竖屏切换导致 Kivy 布局重绘。presplash是 APK 启动时显示的静态图,尺寸建议 512x512 以上。图标不要用带透明通道的 PNG,部分安卓桌面会把透明区域渲染成黑色。
3.4 依赖崩溃排错:看到这几行日志别再重装环境
打包时报错最多的两类:一是 Cython 版本不匹配,报Cython.Compiler.Errors.CompileError;二是 p4a 在编译某个依赖时找不到头文件。常规做法是清理后重试:
# 清理 p4a 构建缓存,--clear 会重下依赖 buildozer android clean buildozer -v android debug如果还是报同一处错误,优先怀疑requirements里的包没有对应配方,而不是环境问题。去 python-for-android 的配方目录里查一下有没有这个包,没有的话要么换包,要么自己写配方,不要硬编译。
4. 完整项目代码里的安卓差异:文件路径、权限、字库与生命周期
4.1 应用内路径与外部存储:别再用./data.txt
Kivy 应用在安卓上是沙箱进程,os.getcwd()返回的是 APK 解压目录,那部分目录是只读的。写文件必须用应用私有目录或外部存储:
# storage.py import os from android.storage import primary_external_storage_path, app_storage_path # 应用私有目录,卸载即删除,无需权限 private_dir = app_storage_path() # 外部公共目录,比如 Download,需要存储权限 public_dir = primary_external_storage_path() # 实际用法示例 db_path = os.path.join(private_dir, "app.db") with open(db_path, "w", encoding="utf-8") as f: f.write("hello android")逻辑说明:app_storage_path()是 p4a 提供的接口,映射到/data/user/0/org.test.myapp/下。primary_external_storage_path()映射到手机的/storage/emulated/0/。从 Android 10(API 29)开始,分区存储成为强制要求,直接往公共目录写文件需要申请WRITE_EXTERNAL_STORAGE权限,且写法受限,所以最好把数据库、配置类文件都放私有目录。
在buildozer.spec里要显式声明权限:
android.permissions = INTERNET, WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGEINTERNET是必须加的,不然后面requests请求会静默失败。这里注意:Kivy 的urllib或requests在安卓上不声明INTERNET权限不会报错,只是请求超时,排查起来非常费时间。
4.2 Kivy 中文字体:默认字体不覆盖 CJK 字符
Kivy 自带字体不含中文字形,界面里出现中文会显示为方框。要在构建时就把字体重命名打进 APK:
# main.py 中追加 from kivy.core.text import LabelBase # 字体文件放在 assets/fonts/ 下,会被打进 APK LabelBase.register(name="cjk", fn_regular="assets/fonts/AlibabaPuHuiTi-3-55-Regular.ttf") # 然后在 kv 文件或代码里指定字体名 from kivy.uix.label import Label label = Label(text="中文", font_name="cjk")逻辑说明:Kivy 的font_name支持注册名,如上代码把cjk映射到字体文件。字体不能放在source.include_exts不包含的目录里——虽然ttf扩展名默认打包,但建议显式加到source.include_exts中,即改成py,png,jpg,kv,atlas,ttf。字体文件动辄十几 MB,建议用子集化字体,只保留用到的几千个常用字,体积能压到 2~3 MB。
4.3 生命周期:切后台后 Python 进程还在吗
安卓系统会在内存不足时杀掉后台进程,Kivy 应用切到后台,on_pause()被调用,如果此方法返回 True,应用会尝试保留状态;返回 False 或不写,应用可能被直接回收。数据处理类应用要注意在on_pause里保存中间结果:
# main.py class DemoApp(App): def on_pause(self): # 保存正在计算的中间数据 self.save_state() # 返回 True 代表可被恢复,False 代表拒绝暂停 return True def on_resume(self): self.load_state()逻辑说明:on_pause里不能做耗时操作,系统只给几秒时间。所以保存逻辑要精简,把大数据写库这类操作放到子线程。另外,Kivy 的Clock事件在后台会被挂起,回到前台后继续执行,不要依赖它做精确计时。
4.4 完整项目源码的目录组织
一个能长期维护的项目,目录结构应该清晰到新人能一眼看懂:
my_android_app/ ├── main.py # 入口文件,必须是这个名字 ├── main.kv # Kivy 布局描述 ├── buildozer.spec # 构建配置 ├── store/ # 业务逻辑,纯 Python │ ├── db.py │ └── api.py ├── ui/ # 界面代码 │ └── screens.py ├── assets/ # 静态资源 │ ├── fonts/ │ ├── images/ │ └── presplash.png ├── libs/ # 如果有第三方 so,放这里 │ └── arm64-v8a/ └── scripts/ # 自动化脚本 └── build.sh逻辑说明:source.include_exts只打包指定扩展名文件,所以libs/下的.so文件不会被打进去,需要在 spec 里额外写source.include_patterns = libs/*.so。ui/和store/拆分是让后续 apk 反编译时,你的业务代码和界面代码分开存放,便于定位问题。
5. 从 Py 代码到 APK 包:源码打包、签名、多架构输出与自动发布
5.1 打包流程与包名规范:package.name 与 package.domain 决定一切
Android 包名在安装后就无法修改,所以第一步就要定好。规范是企业域名反写加产品名:
# buildozer.spec package.name = mytool package.domain = com.example最终包名是com.example.mytool。包名冲突会导致覆盖安装失败,报INSTALL_FAILED_UPDATE_INCOMPATIBLE。如果你之前用 debug 包安装过,后续装 release 包要先卸载,因为签名不同。
5.2 Debug 包签名机制:直接装到手机上的那份 APK
执行buildozer android debug时,p4a 自动用一个调试密钥签名 APK,这个密钥在~/.android/debug.keystore里,固定不变。所以 debug 包可以随便装,卸载重装不会报签名冲突。把 APK 传到手机安装:
# 手机开启 USB 调试后,直接安装并运行 adb install -r bin/mytool-0.1-arm64-v8a-debug.apk adb shell am start -n com.example.mytool/org.kivy.android.PythonActivity逻辑说明:Kivy 应用的主 Activity 是 p4a 定义的PythonActivity,启动它等同于启动 Python 解释器。日常调试用adb logcat -s python看 Python 层日志,-s python是过滤标签,Kivy 的 print 输出都会打在这个标签下。
5.3 Release 包签名、Git 推送与版本迭代
release 包要自己的签名密钥。常见做法是生成一个专用的 keystore,长期保留,因为应用升级必须用同一把钥匙:
# 生成独立签名密钥,有效期建议 30 年以上 keytool -genkey -v -keystore mytool.keystore -alias mytool -keyalg RSA -keysize 2048 -validity 10950然后在buildozer.spec里填入签名信息:
android.release_artifact = mytool-release.apk p4a.release_keyalias = mytool p4a.release_keystore = %(source.dir)s/mytool.keystore p4a.release_keystore_password = 你的密码 p4a.release_keyalias_password = 你的密码之后buildozer android release就会产出签名好的 APK。
关于发布,Git 推送是一个非常实用的分发方式:把 APK 推到自建服务器或有版本管理 API 的对象存储上,App 内写一个启动时检查版本的逻辑,比对服务端的版本号字段,高于本地就提示下载。这样就不需要每次打包都走应用市场和审核流程,适合企业内部工具类 App。
5.4 APK 体积拆解:看到底什么占了空间
# 查看 APK 内各文件大小,定位体积大头 unzip -l bin/mytool-0.1-arm64-v8a-release.apk | sort -k1 -rn | head -20实际拆解出来的空间分布一般是这样的:
| 文件/目录 | 体积占比 | 说明 |
|---|---|---|
lib/arm64-v8a/libpython3.*.so | 12%~15% | Python 解释器本体 |
lib/arm64-v8a/libkivy.so | 8%~10% | Kivy 的 C 扩展 |
assets/private.mp3 | 8%~20% | p4a 压缩打包的 Python 源码与依赖 |
assets/fonts/ | 10%~30% | 中文字体型文件,可优化空间最大 |
res/ | 3%~5% | 应用图标与启动图 |
private.mp3是 p4a 的障眼法,内部是压缩过的 Python 代码包,安卓的assets目录只读不解压,应用启动时在内存里解密读取。看到这个文件不要惊讶,不要去改名。
6. APK 打包后怎么验证:logcat、反编译自查与上架前检查清单
6.1 用 logcat 验证 Python 进程真的在跑
很多 APK 安装后闪退,黑屏几秒就退出。这时候看 logcat 比猜有效得多:
# 清空旧日志,再启动应用 adb logcat -c adb shell am start -n com.example.mytool/org.kivy.android.PythonActivity adb logcat -s python:V *:E输出里出现I/python: Android kivy bootstrap done说明解释器已成功启动,接下来如果报ModuleNotFoundError,就是requirements漏包了;如果报KeyError: 'user'之类,就是权限配置问题。*:E能把 C 层崩溃信息也捞出来,定位 so 文件dlopen失败很关键。
6.2 反编译自查:检查 APK 里有没有泄露密钥和多余代码
发布前要反向检查一下 APK。操作是用 APK 反编译工具打开你打好的包,重点看两处:一是assets/private.mp3解包后搜password、api_key、secret字样,确认没有硬编码凭据;二是看lib/arm64-v8a/下是不是只有你声明的架构的 so。
提示:反编译检查只做自查用途。Python 源码编译程度有限,敏感逻辑最好放到服务端,不要在客户端藏密钥。
6.3 上架前的六项自检
| 检查项 | 目标 | 失败表现 |
|---|---|---|
| 包名唯一性 | 各市场全局唯一 | 上架后覆盖安装失败 |
| 签名文件备份 | 升级可用同一签名 | 版本更新提示签名不一致 |
android.minapi覆盖目标机型 | 安装到旧手机不报错 | INSTALL_FAILED_OLDER_SDK |
| targetSdk 符合市场要求 | 商店审核通过 | 应用市场拒绝上架 |
| 存储权限最小化 | 隐私合规 | 审核要求说明权限用途 |
| 64 位架构支持 | 新手机可安装 | 华为/小米商店拒绝收录 |
每一项都可以直接对应到buildozer.spec里的某个参数,不用改代码。检查完这些,把 release APK 用 Git 推送分发,然后在真机上完整过一遍核心流程,基本就能交付了。
本文还有配套的精品资源,点击获取