引言
Qt 一直把 Code less. Create more. Deploy everywhere 当做其宣传语. 笔者一直都在维护一个基于 Qt for Python 的项目, 很早之前就听说了 PySide6 项目可以部署到 Android 平台上1, 但是对系统环境要求十分苛刻, 互联网上也缺少相关文档, 就一直没有尝试此技术. 好在现在微软的 WSL 技术成熟, PySide6 也在 6.8.0 版本之后提供了预编译的轮子2, 遂开始了今天的探索.
搭建 WSL 环境
笔者使用的系统是 Windows 11, Qt for Python 的交叉编译要求的系统平台是 Linux1 或者 macOS2, 所以需要搭建 WSL 环境, 这里笔者推荐的是 Ubuntu 24.04 LTS, 理由如下:
PySide6-Android-Deploy底层依赖Buildozer, 而Buildozer的官方文档重点给的是Ubuntu平台的教程, 使用Ubuntu可以避免不必要的麻烦.- 笔者原来常用的是
WSL Arch,WSL本来安装多个子系统又极其方便, 安装新的Ubuntu系统还避免了环境问题.
使用 WSL 2 而非 WSL 1 以避免不必要的麻烦
务必将程序源码放置在 WSL 分区 中, 不要在 Windows 分区上进行编译
Notes for WSL users
运行以下命令以安装 Ubuntu 24.04 LTS.
wsl --install -d Ubuntu运行失败可以先尝试运行以下两个命令4
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestartdism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart配置基础编译环境
换源并更新软件源
将镜像源切换到国内镜像源, 这里推荐 北京外国语大学开源软件镜像站
, 请选用 DEB822 格式.
sudo apt updatesudo apt full-upgrade -ysudo apt autoremove -y安装系统依赖5 6
笔者这里使用了 Buildozer 的官方文档5 中推荐的 OpenJDK 17
sudo apt install -y \ git zip unzip openjdk-17-jdk python3-pip python3-virtualenv \ autoconf automake libtool pkg-config zlib1g-dev \ libncurses5-dev libncursesw5-dev libtinfo6 \ cmake libffi-dev libssl-dev autopoint gettext安装 Python 环境
笔者自己项目推荐的 Python 版本是 3.11, Qt 官方预编译的 PySide6 for Android 也仅支持 cp3.11 而 Ubuntu 24.04 LTS 官方仓库默认没有 3.11, 遂使用 pyenv 安装 Python.
curl -fsSL https://pyenv.run | bashcat >> ~/.bashrc <<'EOF'export PYENV_ROOT="$HOME/.pyenv"[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"eval "$(pyenv init - bash)"EOFsource ~/.bashrcpyenv install 3.11pyenv global 3.11python --version切换系统 Java 版本
Ubuntu 24.04 LTS 似乎自带了较新版本的 JDK, 需要手动切换
sudo update-alternatives --config javasudo update-alternatives --config javac然后选带 java-17-openjdk / javac-17-openjdk 的那一项
环境自检
python3 --versionpip3 --versionjava -versiongit --versioncmake --version新建 Python 虚拟环境
建议将虚拟环境放在项目文件夹之外.3
mkdir ~/venvpython -m venv ~/venvsource ~/venv/bin/activatepython -m pip install -U pip setuptools wheel配置 Python 虚拟环境5
source ~/venv/bin/activatepip install buildozer setuptools cython==0.29.34下载 Android NDK 和 SDK
下载 Android NDK 和 SDK 最简单的方法是通过 Qt for Python 仓库中的一个脚本3
cd ~git clone https://code.qt.io/pyside/pyside-setupcd pyside-setuppip install -r tools/cross_compile_android/requirements.txtpython tools/cross_compile_android/main.py --download-only --skip-update --auto-accept-licenseAndroid NDK 和 SDK 会被下载到 ~/.pyside6_android_deploy/ 中
获取适用于 Android 平台的 PySide6 / Shiboken6 轮子
有三种方式可以获取适用于 Python 的 Qt Android 轮子文件3
- Qt for Python downloads page 或 Qt for Python CI Snapshot
qtpip download PySide6 --android --arch aarch64(可用的架构包括aarch64和x86_64, 仅适用于Qt Commercial用户)- 为 Android 交叉编译适用于 Python 的 Qt 轮子
笔者这里下载了 PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl 和 shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl
mkdir ~/android-wheelscd ~/android-wheelswget https://download.qt.io/official_releases/QtForPython/pyside6/PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whlwget https://download.qt.io/official_releases/QtForPython/shiboken6/shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl至此, 编译环境配置完毕~
最小 Qt for Android Bootstrap
最小启动脚本
笔者在 ~/project 目录创建了 main.py 脚本
import sys
from PySide6.QtWidgets import QApplication, QLabel
if __name__ == "__main__": app = QApplication(sys.argv) label = QLabel("Qt for Android bootstrap") label.show() sys.exit(app.exec())初始化 PySide6-Android-Deploy Spec 文件
cd ~/projectsource ~/venv/bin/activatepyside6-android-deploy \ --init \ --name "FirstQtBootstrap" \ --wheel-pyside=/home/xiaoyouchr/android-wheels/PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl \ --wheel-shiboken=/home/xiaoyouchr/android-wheels/shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl \ --sdk-path=/home/xiaoyouchr/.pyside6_android_deploy/android-sdk \ --ndk-path=/home/xiaoyouchr/.pyside6_android_deploy/android-ndk/android-ndk-r27c由于不能使用 ~, 请将 /home/xiaoyouchr/ 改成你的家目录.
应用名此时不应带有空格, 笔者在后文会提供 PATCH pyside6-android-deploy 脚本从而使应用名可以带有空格.
cd ~/projectsource ~/venv/bin/activatepyside6-android-deploy \ --config-file /home/xiaoyouchr/project/pysidedeploy.spec \ --wheel-pyside=/home/xiaoyouchr/android-wheels/PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl \ --wheel-shiboken=/home/xiaoyouchr/android-wheels/shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl \ --sdk-path=/home/xiaoyouchr/.pyside6_android_deploy/android-sdk \ --ndk-path=/home/xiaoyouchr/.pyside6_android_deploy/android-ndk/android-ndk-r27c \ --keep-deployment-files \ -vcd ~/projectls发现 APK 已经生成到项目中啦! 插入手机, 安装部署产物~
cd ~/project~/.pyside6_android_deploy/android-sdk/platform-tools/adb install ./FirstQtBootstrap-0.1-arm64-v8a-debug.apk排查问题
哈哈, 如果你是一步一步按着教程走的, 那么不出意外地应该运行闪退咯~
插入手机, 老实抓日志.
~/.pyside6_android_deploy/android-sdk/platform-tools/adb logcat | grep -i -E "FATAL EXCEPTION|AndroidRuntime|Qt|libc|python|shiboken|PySide|dlopen|UnsatisfiedLinkError|crash"注意力惊人, 你在日志中敏锐的观察到:
04-14 16:38:55.135 5948 5976 E AndroidRuntime: java.lang.UnsatisfiedLinkError: dlopen failed: library "libpython3.11.so" not found: needed by /data/app/~~J1G-gyOzPqX3lOSadYNICQ==/org.firstqtbootstrap.firstqtbootstrap-d25iVR_yhfWeWlvi7dSXHg==/lib/arm64/libshiboken6.abi3.so in namespace clns-9之后你运行了
unzip -l FirstQtBootstrap-0.1-arm64-v8a-debug.apk | grep -i "libpython3"发现
-rwxr-xr-x 1 xiaoyouchr xiaoyouchr 5764168 Apr 14 16:22 libpython3.14.so怎么回事呢, 为什么有 libpython3.14.so, 但没有 libpython3.11.so 呢?
PATCH pyside6-android-deploy 脚本
每次调用 pyside6-android-deploy 都会覆写 buildozer.spec 文件, 而修改 buildozer.spec 后直接手动调用 python -m buildozer android debug 又要自己手动处理复杂的依赖问题, 于是~
修改 ~/venv/lib/python3.11/site-packages/PySide6/scripts/deploy_lib/android/buildozer.py 的奇技淫巧的想法油然而生.
使用你常用的编辑器打开 ~/venv/lib/python3.11/site-packages/PySide6/scripts/deploy_lib/android/buildozer.py
之后找到这行
self.set_value("app", "requirements", "python3,shiboken6,PySide6")将其改成
self.set_value("app", "requirements", "python3==3.11.15,hostpython3==3.11.15,shiboken6,PySide6")增加上 hostpython==3.11.15 的原因是如果不加就会
[ERROR]: Build failed: python3 should have same version as hostpython3, 3.11.14 != 3.14.2还注意到 buildozer.py 中有如下代码
# add p4a branch# by default the master branch is used# https://github.com/kivy/python-for-android/commit/b92522fab879dbfc0028966ca3c59ef46ab7767d# has not been merged to master yet. So, we use the develop branch for now# TODO: remove this once the above commit is merged to masterself.set_value("app", "p4a.branch", "develop")显然此 Commit 已经被合并到了主分支, 为了避免不必要的风险, 建议将这行代码也注释掉.
笔者还发现了这段代码上次被维护是两年前, 最后维护者是 adrianghc, 这位大佬曾为 QtAsyncIO 的推近付出过巨大的精力. 可惜最后还是从 Qt 公司离职了. 这段代码的维护工作最终也不了了之~
重新运行部署脚本并在 Android 设备上安装
cd ~/projectsource ~/venv/bin/activatepyside6-android-deploy \ --init \ --name "FirstQtBootstrap" \ --wheel-pyside=/home/xiaoyouchr/android-wheels/PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl \ --wheel-shiboken=/home/xiaoyouchr/android-wheels/shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl \ --sdk-path=/home/xiaoyouchr/.pyside6_android_deploy/android-sdk \ --ndk-path=/home/xiaoyouchr/.pyside6_android_deploy/android-ndk/android-ndk-r27cpyside6-android-deploy \ --config-file /home/xiaoyouchr/project/pysidedeploy.spec \ --wheel-pyside=/home/xiaoyouchr/android-wheels/PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl \ --wheel-shiboken=/home/xiaoyouchr/android-wheels/shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl \ --sdk-path=/home/xiaoyouchr/.pyside6_android_deploy/android-sdk \ --ndk-path=/home/xiaoyouchr/.pyside6_android_deploy/android-ndk/android-ndk-r27c \ --keep-deployment-files \ -v~/.pyside6_android_deploy/android-sdk/platform-tools/adb install ./FirstQtBootstrap-0.1-arm64-v8a-debug.apk看到以下画面, 最小 Bootstrap 运行成功了~

迁移你的 PySide6 应用到 Android 平台
压缩包体
注意到我们的最小 Bootstrap 在安装后占用存储空间高达 381MB, 这显然是不可接受的.
cd ~/projectunzip -l FirstQtBootstrap-0.1-arm64-v8a-debug.apk发现很多没有用到的依赖也被打包进去了
... 201504 1981-01-01 01:01 lib/arm64-v8a/libQt6Nfc_arm64-v8a.so 51360 1981-01-01 01:01 lib/arm64-v8a/libQt6OpenGLWidgets_arm64-v8a.so 518176 1981-01-01 01:01 lib/arm64-v8a/libQt6OpenGL_arm64-v8a.so 603040 1981-01-01 01:01 lib/arm64-v8a/libQt6PdfQuick_arm64-v8a.so 98296 1981-01-01 01:01 lib/arm64-v8a/libQt6PdfWidgets_arm64-v8a.so 4382256 1981-01-01 01:01 lib/arm64-v8a/libQt6Pdf_arm64-v8a.so...需要手动修改 ~/venv/lib/python3.11/site-packages/PySide6/scripts/deploy_lib/android/recipes/PySide6/__init__.tmpl.py, 让其仅复制必要的依赖即可.
使应用名支持空格
找到 buildozer.py 中的
self.set_value("app", "title", pysidedeploy_config.title)self.set_value("app", "package.name", pysidedeploy_config.title)将 title 改成你的应用名称, 比如
self.set_value("app", "title", "Ghost Downloader 3")self.set_value("app", "package.name", pysidedeploy_config.title)调试指南
在进行迁移工作的过程中, 闪退是常有的事情, 我们可以在启动程序前运行
adb logcat -cadb logcat | rg -i "FATAL EXCEPTION|AndroidRuntime|Qt|libc|python|shiboken|PySide|dlopen|UnsatisfiedLinkError|Permission|SecurityException|EACCES|denied|storage|crash > crash.log"打开你的 PySide6 on Android 程序, 在崩溃后立即按下 Ctrl + C 结束 adb logcat.
之后使用你常用的编辑器打开 crash.log, 搜索字符串 python :, 就能看到 Python 在 stdout 和 stderr 输出的日志, 之后一步一步解决即可~
导入必要依赖
你的 PySide6 肯定使用了其他的依赖, 请你使用你常用的编辑器打开 ~/venv/lib/python3.11/site-packages/PySide6/scripts/deploy_lib/android/buildozer.py
之后找到这行
self.set_value("app", "requirements", "python3==3.11.15,hostpython3==3.11.15,shiboken6,PySide6")在里面加上你需要安装的依赖
结语
到此为止, 你已经成功地将你的 PySide6 程序部署到了 Android 设备上了.
感谢阅读~
Footnotes
部分信息可能已经过时