
引言
Kivy 是一个强大的 Python 框架,用于快速开发跨平台应用程序,尤其是图形用户界面(GUI)。通过 Buildozer 工具,Kivy 开发者可以方便地将 Python 应用程序打包成 Android 平台的 APK 文件。然而,在打包过程中,开发者可能会遇到各种编译错误,其中 pyjnius 相关的 clang 错误是较为常见且棘手的问题,尤其是在处理 Python C 扩展时。本文将深入分析这类错误,并提供一套系统的解决方案。
错误分析:深入理解 Pyjnius 编译失败
在 Kivy 项目导出 APK 的过程中,当执行 buildozer -v android debug 命令时,如果遇到类似于以下日志中的 clang 错误,通常意味着 pyjnius 库在 Android 目标环境下的编译遇到了问题:
clang-14: error: no such file or directory: 'jnius/jnius.c' error: command '/home/abr/.buildozer/android/platform/android-ndk-r25b/toolchains/llvm/prebuilt/linux-x86_64/bin/clang' failed with exit code 1
和更关键的:
jnius/jnius.c:54433:5: error: expression is not assignable
++Py_REFCNT(o);
^ ~~~~~~~~~~~~这些错误信息揭示了几个关键问题点:
- 拼写错误:首先,需要检查执行命令时是否存在拼写错误。例如,将 buildozer 误写为 biuldozer 将直接导致命令无法识别,从而中断整个打包流程。
- jnius/jnius.c 文件缺失:pyjnius 是 Kivy 在 Android 上与 Java 代码交互的关键库。它是一个 Python C 扩展,需要通过 Cython 将 .pyx 源文件转换为 .c 文件,然后使用 Android NDK 中的 clang 编译器进行编译。首次构建 pyjnius 时,jnius.c 文件可能尚未生成,导致第一次编译失败是预期行为。但如果 Cython 化之后仍然找不到或编译失败,则表明 Cython 转换或后续 C 编译过程存在问题。
- expression is not assignable 错误 (++Py_REFCNT(o);):这是最核心的编译错误。Py_REFCNT 是 Python 对象引用计数的宏。在 Python 3.9 及更高版本中,为了防止不安全的直接内存操作,PyObject 结构中的 ob_refcnt 字段被标记为只读,不再允许通过 ++ 或 -- 等操作符直接修改。取而代之的是 Py_INCREF() 和 Py_DECREF() 等函数。 当 pyjnius 的 C 代码(通常由 Cython 从 .pyx 文件生成)试图直接修改 Py_REFCNT 时,如果 Buildozer 内部为 Android 目标环境配置的 Python 版本(或其 C API 头文件)的行为类似于 Python 3.9+,即使本地系统 Python 版本是 3.8.10,也会触发此 "expression is not assignable" 错误。这通常意味着 pyjnius 的版本与 Buildozer 正在使用的 Android 目标 Python 版本或 NDK/Clang 版本之间存在兼容性问题。
解决方案与最佳实践
解决这类问题需要系统性地检查 Buildozer 配置和环境。
步骤一:检查并纠正 Buildozer 命令
确保您使用的 Buildozer 命令拼写正确无误。 错误示例:
biuldozer -v android debug
正确命令:
buildozer -v android debug
这是一个基础但经常被忽视的错误点。
步骤二:审查 buildozer.spec 配置文件
buildozer.spec 文件是 Buildozer 的核心配置文件,它定义了应用程序的元数据、依赖项、Android 工具链版本等。
-
核心:requirements 部分 此部分列出了您的 Kivy 应用及其依赖库。确保所有必要的依赖都已列出,并且版本兼容。pyjnius 是其中的关键。
[app] # ... 其他应用设置 ... requirements = python3,kivy,pyjnius,hostpython3,sdl2,setuptools,cython # 如果遇到 Pyjnius 编译问题,可以尝试指定一个已知兼容的版本 # requirements = python3,kivy,pyjnius==1.4.0,hostpython3,sdl2,setuptools,cython # ...
-
NDK/SDK 版本配置 Buildozer 使用 Android NDK (Native Development Kit) 和 SDK (Software Development Kit) 来编译原生代码和构建 APK。不兼容的 NDK/SDK 版本可能导致编译错误。
[buildozer] # ... # Android 工具链版本配置 android.ndk = 25b # 推荐尝试 NDK 25b 或 23b,避免使用最新的 NDK 版本可能带来的兼容性问题 android.sdk = 29 # 根据目标 Android 版本设置 SDK API 级别 android.api = 29 # 目标 API 级别 android.minapi = 21 # 最小支持 API 级别
- android.ndk: 针对 Py_REFCNT 错误,有时较新的 NDK 版本会引入更严格的 C/C++ 标准或与旧版 pyjnius 不兼容的头文件。尝试指定一个较旧但稳定的 NDK 版本(例如 23b 或 21b)可能会解决问题。NDK 25b 是一个相对稳定的版本,但如果问题依旧,可以尝试更早的版本。
- android.sdk / android.api: 这些设置定义了 Buildozer 下载和使用的 Android SDK 版本以及您的应用的目标 API 级别。确保它们与您的应用需求和 Buildozer 的兼容性列表相符。
步骤三:清理 Buildozer 缓存
在修改 buildozer.spec 或尝试不同解决方案后,务必清理 Buildozer 的构建缓存,以确保所有更改都能生效,并强制 Buildozer 重新下载或编译依赖项。
buildozer -v android clean
执行此命令后,再次尝试打包:
buildozer -v android debug
步骤四:环境兼容性与依赖项管理
Python 版本与 C API 兼容性 虽然 Buildozer 会为 Android 目标环境管理一个独立的 Python 环境,但本地系统上的 Cython 版本也可能影响生成的 C 代码。Py_REFCNT 错误通常指向 pyjnius 的 C 代码与目标 Python C API 之间存在不兼容。确保您没有手动覆盖 Buildozer 内部的 Python 版本。 如果指定了特定版本的 pyjnius 仍然失败,可以尝试在 buildozer.spec 中添加 android.python_version = 3.8 (或 Buildozer 支持的其他版本) 来明确目标 Python 版本,但这通常不是必需的,Buildozer 会根据 requirements 自动处理。
-
Cython 版本pyjnius 依赖 Cython 将 .pyx 文件转换为 .c 文件。如果 Buildozer 使用的 Cython 版本过旧或过新,可能导致生成的 .c 文件不兼容目标 Python 环境。Buildozer 通常会自行管理 Cython,但如果怀疑是此问题,可以尝试:
- 确保 requirements 中包含 cython。
- 更新 Buildozer 本身(见下一步)。
高级故障排除建议
如果上述步骤未能解决问题,可以考虑以下高级建议:
-
更新 Buildozer:确保您使用的 Buildozer 版本是最新的稳定版。新版本通常会修复旧版本存在的兼容性问题。
pip install --upgrade buildozer
- 详细日志分析:在 buildozer -v android debug 命令中,-v 参数提供了详细的日志输出。仔细阅读日志,特别是 [DEBUG] 和 [ERROR] 行附近的输出,可能会发现更具体的错误信息或线索。
- 手动检查 Buildozer 目录:导航到 Buildozer 的构建目录(通常在项目根目录下的 .buildozer/android/platform/),检查 pyjnius 相关的构建产物。例如,查看 jnius/jnius.c 文件是否存在,以及其内容是否与预期的 Python C API 兼容。
注意事项
- 跨平台编译的复杂性:将 Python 应用打包到 Android 涉及多个工具链和环境的协调,出现问题是常态。
- 耐心与迭代尝试:解决这类问题通常需要多次尝试和调整配置。每次更改后,记得执行 buildozer -v android clean。
- 社区资源:如果问题依然无法解决,查阅 Kivy 和 Buildozer 的官方文档、GitHub Issue 页面或相关社区论坛,可能会找到类似的案例和解决方案。
总结
Kivy 项目导出 APK 时遇到的 pyjnius 编译错误,特别是 clang 报告的 expression is not assignable,通常源于 Buildozer 配置、依赖项版本不匹配或 Android 工具链兼容性问题。通过仔细检查 buildozer.spec 文件中的 requirements 和 Android NDK/SDK 配置,纠正命令拼写错误,并进行彻底的缓存清理,大多数这类问题都能得到有效解决。理解错误背后的技术原理,结合系统的故障排除方法,是确保 Kivy 应用顺利部署到 Android 平台的关键。










