
简介这套面向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 最小可跑 Demomain.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(textHello 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 能否安装的关键项参数默认值作用titleMy Application桌面图标下显示的应用名package.namemyapp应用短名称参与包名组合package.domainorg.test与 name 组合成完整包名org.test.myappsource.include_extspy,png,jpg,kv,atlas决定哪些文件被打进 APKandroid.api33编译时用的 SDK 版本android.minapi21最低支持的安卓版本低于 21 无法安装android.ndk_api25NDK 兼容 API影响 so 文件的链接目标android.archsarm64-v8a, armeabi-v7a支持的 CPU 架构见 3.2android.accept_sdk_licenseFalse必须改成 True否则 SDK 组件下载中断requirementspython3,kivyPython 依赖清单参数说明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 降到 25MBABI应用二进制接口是最能压缩 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.txtKivy 应用在安卓上是沙箱进程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, encodingutf-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 10API 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(namecjk, fn_regularassets/fonts/AlibabaPuHuiTi-3-55-Regular.ttf) # 然后在 kv 文件或代码里指定字体名 from kivy.uix.label import Label label Label(text中文, font_namecjk)逻辑说明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.*.so12%~15%Python 解释器本体lib/arm64-v8a/libkivy.so8%~10%Kivy 的 C 扩展assets/private.mp38%~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_SDKtargetSdk 符合市场要求商店审核通过应用市场拒绝上架存储权限最小化隐私合规审核要求说明权限用途64 位架构支持新手机可安装华为/小米商店拒绝收录每一项都可以直接对应到buildozer.spec里的某个参数不用改代码。检查完这些把 release APK 用 Git 推送分发然后在真机上完整过一遍核心流程基本就能交付了。本文还有配套的精品资源点击获取