<?xml version="1.0" encoding="utf-8"?>
        <feed xmlns="http://www.w3.org/2005/Atom">
        <title>晓游的博客</title>
        <subtitle>晓游の博客</subtitle>
        <link href="https://www.xychr.com/" rel="alternate" type="text/html"/>
        <link href="https://www.xychr.com/atom.xml" rel="self" type="application/atom+xml"/>
        <id>https://www.xychr.com/</id>
        <updated>2026-05-27T08:17:09.247Z</updated>
        <language>zh</language>
        <entry>
            <title>基于 ESP-IDF 的 Rust + Slint + ESP32-S3 快速上手</title>
            <link href="https://www.xychr.com/posts/rust_slint_esp32s3/rust_slint_esp32s3/" rel="alternate" type="text/html"/>
            <id>https://www.xychr.com/posts/rust_slint_esp32s3/rust_slint_esp32s3/</id>
            <published>2026-04-21T00:00:00.000Z</published>
            <updated>2026-04-21T00:00:00.000Z</updated>
            <summary>本文记录了我在 ESP32-S3-BOX 上开发 Rust 和 Slint 的初体验，使用 VSCode 和 ESP-IDF 进行开发。</summary>
            <content type="html"><![CDATA[<h2>引言</h2>
<h2>为什么选择 <code>ESP-IDF</code> ?</h2>
<h2>使用的系统环境</h2>
<ul>
<li>操作系统: macOS 15.7.4</li>
<li>开发版: 正点原子 ESP32 AI BOX1</li>
</ul>
<blockquote>
<p>[!TIP]
我们以后将频繁参考的正点原子官方资料
http://www.openedv.com/docs/boards/esp32/ATK-DNESP32S3BVXX.html</p>
</blockquote>
<h2>配置开发环境</h2>
<h3>安装 Rust</h3>
<blockquote>
<p>[!TIP]
官方更推荐使用 <code>Rustup</code> 安装 <code>Rust</code>, 而非通过第三方包管理工具 <code>brew</code>.
<code>ESP-IDF-Template</code> 也更推荐使用 <code>Rustup</code> 进行安装 [^1]
https://rust-lang.org/tools/install/</p>
</blockquote>
<pre><code>curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
</code></pre>
<p>随后运行 <code>source "$HOME/.cargo/env"</code> 或重启你的 Shell, 校验安装:</p>
<pre><code>rustup update
</code></pre>
<h3>给 Rust 包管理工具 Cargo 换源</h3>
<p>国内特殊的网络环境需要换源才能实现高速的依赖下载, 笔者这里推荐 <code>北京外国语大学开源软件镜像站</code>, 详见:</p>
<ul>
<li>https://mirrors.bfsu.edu.cn/help/crates.io-index/</li>
<li>https://mirrors.bfsu.edu.cn/help/crates.io-index.git/</li>
</ul>
<h3>安装 ESP-IDF 及其工具链</h3>
<p>根据 <a href="https://github.com/esp-rs/esp-idf-template#prerequisites">ESP-IDF-Template 中 Prerequisites 部分</a></p>
<blockquote>
<p>In particular, do NOT clone, install and activate the ESP-IDF which is mentioned further down this page, as it is not necessary at all (though supported, but not for a beginner setup)</p>
</blockquote>
<p>作为初学者的我们应让 <code>ESP-IDF-SYS</code> 来管理 <code>ESP-IDF</code>, 而非自己手动安装</p>
<h4>安装系统级依赖</h4>
<pre><code>brew install libgcrypt glib pixman sdl2 libslirp dfu-util cmake
</code></pre>
<h4>安装 Python</h4>
<p>笔者电脑里面有一套 Python 开发环境, 所以使用 <code>pyenv</code> 来管理 Python 环境来避免环境紊乱</p>
<pre><code>brew install pyenv
</code></pre>
<p>若重启 Shell 后无法检测到 <code>pyenv</code> 命令, 请参考 <a href="https://github.com/pyenv/pyenv#b-set-up-your-shell-environment-for-pyenv">PyENV 官方文档</a></p>
<pre><code>pyenv install -l  # 查看最新版本
pyenv install 3.14  # 安装目前的最新版本
pyenv global 3.14
pyenv rehash
python --version  # 校验安装
</code></pre>
<blockquote>
<p>[!TIP]
<code>python --version</code> 版本不对? 看看 <code>pyenv versions</code> 命令结果输出的版本由何设置, 或尝试 <code>rm ~/.python-version</code>.</p>
</blockquote>
<h4>安装 ESP Rust 相关工具</h4>
<p>根据 <a href="https://github.com/esp-rs/esp-idf-template#prerequisites">ESP-IDF-Template 中 Prerequisites 部分</a></p>
<pre><code>cargo install cargo-generate
cargo install ldproxy
cargo install espup # ESP32-S3 属于 Xtensa，要用 espup 来装 Espressif Rust 工具链
cargo install espflash
cargo install cargo-espflash
</code></pre>
<p>根据 <a href="https://github.com/esp-rs/espup/README.md">ESP-RS</a>, 仅安装最小依赖</p>
<pre><code>espup install --targets esp32s3 --std
. "$HOME/export-esp.sh"
</code></pre>
<p>推荐将环境加载持久化进 Shell, 将 <code>~/.zshrc</code> 改成你的 <code>Shell 用户配置文件</code></p>
<pre><code>echo '. "$HOME/export-esp.sh"' &gt;&gt; ~/.zshrc
</code></pre>
<h3>安装 VSCode 插件</h3>
<p>在安装 VSCode 插件之前, 笔者更推荐安装以下的 <code>Rust 的额外 lint 工具</code> 和 <code>官方格式化工具</code></p>
<pre><code>rustup component add clippy rustfmt
</code></pre>
<p>运行以下命令或手动安装以下两个 VSCode 插件</p>
<pre><code>code --install-extension rust-lang.rust-analyzer
code --install-extension Slint.slint
</code></pre>
<p>推荐添加以下两个 VSCode 配置, 它做了两件事</p>
<ul>
<li>将检查命令从 <code>cargo check</code> 改为 <code>cargo clippy</code></li>
<li>在保存时自动格式化代码</li>
</ul>
<pre><code>{
  "rust-analyzer.check.command": "clippy",
  "[rust]": {
    "editor.formatOnSave": true
  }
}
</code></pre>
<h2>搭建最小 Bootstrap</h2>
<h3>创建 ESP-IDF 项目</h3>
<p>用官方模板生成项目, 在项目文件夹中运行如下命令, 根据 <a href="https://github.com/esp-rs/esp-idf-template">ESP-IDF-TEMPLATE</a></p>
<pre><code>cargo generate esp-rs/esp-idf-template cargo
</code></pre>
<p>我选择的选项如下</p>
<pre><code>➜  CLionProjects cargo generate esp-rs/esp-idf-template cargo
⚠️   Favorite `esp-rs/esp-idf-template` not found in config, using it as a git repository: https://github.com/esp-rs/esp-idf-template.git
🤷   Project Name: esp32-s3-slint-rs
🔧   Destination: /Users/xiaoyouchr/CLionProjects/esp32-s3-slint-rs ...
🔧   project-name: esp32-s3-slint-rs ...
🔧   Generating template ...
✔ 🤷   Which MCU to target? · esp32s3
✔ 🤷   Configure advanced template options? · true
✔ 🤷   ESP-IDF version (master = UNSTABLE) · v5.5.3  # 不建议选择 master 分支, 不稳定
✔ 🤷   Use latest GIT versions of the esp-idf-* crates instead of released crates.io versions? · false   # 不建议从 Git 仓库拉取最新版本, 不稳定
✔ 🤷   Installation location of managed ESP-IDF · workspace
✔ 🤷   Configure project to use Dev Containers (VS Code and GitHub Codespaces)? · false
✔ 🤷   Configure project to support Wokwi simulation with Wokwi VS Code extension? · false   # Wokwi is World's most advanced ESP32 simulator
✔ 🤷   Add CI files for GitHub Action? · true    # GitHub 自动构建工作流
</code></pre>
<h3>安装 Slint 依赖</h3>
<p>根据 <code>https://docs.slint.dev/latest/docs/rust/slint/docs/mcu/</code></p>
<pre><code>cargo add slint@1.16 --no-default-features --features "compat-1-2 unsafe-single-threaded libm renderer-software"
cargo add --build slint-build@1.16
</code></pre>
<h3>最小 Slint UI</h3>
<p>根据 <code>https://docs.slint.dev/latest/docs/rust/slint/docs/mcu/</code></p>
<p><code>ui/app.slint</code></p>
<pre><code>export component App inherits Window {
    width: 320px;
    height: 240px;

    Rectangle {
        background: #1f2937;

        Text {
            x: 16px;
            y: 16px;
            text: "ESP32-S3 + Slint + Rust";
            color: white;
            font-size: 18px;
        }

        Text {
            x: 16px;
            y: 48px;
            text: "compile-only skeleton";
            color: #cbd5e1;
            font-size: 14px;
        }
    }
}
</code></pre>
<p>然后编辑 <code>build.rs</code>, 将 <code>ui/app.slint</code> 生成成 <code>Rust</code> 代码, 供 <code>slint::include_modules!();</code> 引入.</p>
<p><code>build.rs</code></p>
<pre><code>fn main() {
    embuild::espidf::sysenv::output();

    let config = slint_build::CompilerConfiguration::new()
    .with_style("fluent".into())
    .embed_resources(slint_build::EmbedResourcesKind::EmbedForSoftwareRenderer);
    slint_build::compile_with_config("ui/app.slint", config).unwrap();
}
</code></pre>
<p>随后运行 <code>cargo build</code>, 编译一下 <code>Slint</code> 文件, 防止在编辑 <code>ui/main.rs</code> 时报错.</p>
<h3>最小主函数</h3>
<p>根据 <code>https://docs.slint.dev/latest/docs/rust/slint/docs/mcu/#the-platform-trait</code></p>
<p><code>ui/main.rs</code></p>
<pre><code>slint::include_modules!();

use std::boxed::Box;
use std::rc::Rc;
use std::time::{Duration, Instant};

use slint::platform::software_renderer::MinimalSoftwareWindow;
use slint::platform::{Platform, PlatformError};

struct EspPlatform {
    window: Rc&lt;MinimalSoftwareWindow&gt;,
    start: Instant,
}

impl Platform for EspPlatform {
    fn create_window_adapter(
        &amp;self,
    ) -&gt; Result&lt;Rc&lt;dyn slint::platform::WindowAdapter&gt;, PlatformError&gt; {
        // Since on MCUs, there can be only one window, just return a clone of self.window.
        // We'll also use the same window in the event loop.
        Ok(self.window.clone())
    }

    fn duration_since_start(&amp;self) -&gt; Duration {
        self.start.elapsed()
    }
}

fn main() {
    // It is necessary to call this function once. Otherwise, some patches to the runtime
    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
    esp_idf_svc::sys::link_patches();

    // Bind the log crate to the ESP Logging facilities
    esp_idf_svc::log::EspLogger::initialize_default();

    log::info!("Booting...");

    let window = MinimalSoftwareWindow::new(Default::default());
    window.set_size(slint::PhysicalSize::new(320, 240));

    slint::platform::set_platform(Box::new(EspPlatform {
        window: window.clone(),
        start: Instant::now(),
    }))
    .expect("failed to set Slint platform");

    let _ui = App::new().expect("failed to create Slint UI");

    log::info!("Slint initialized");

    loop {
        slint::platform::update_timers_and_animations();
        window.draw_if_needed(|_renderer| {
            // TODO: render to LCD
        });
        std::thread::sleep(Duration::from_millis(16));
    }
}
</code></pre>
<h3>第一次烧录</h3>
<p>插入 ESP32-S3-BOX 后运行 <code>cargo run</code>, 查看控制台日志, 发现</p>
<pre><code>I (480) esp32_s3_slint_rs: Booting...
I (490) esp32_s3_slint_rs: Slint initialized
</code></pre>
<p>烧录成功~</p>
<h2>正式点亮屏幕</h2>
<p>//</p>
<h3>驱动 Xl9555</h3>
<h3>驱动屏幕</h3>
<h2>加入触屏功能</h2>
<h3>增强 Xl9555 驱动</h3>
<h3>驱动触摸</h3>
<h3>在 Slint 中处理触摸信号</h3>
<h2>优化性能</h2>
<h3>超频 I80 总线</h3>
<h3>边渲染边上屏</h3>
<h3>异步处理触摸信号</h3>
<h2>驱动 Wi-Fi</h2>
<h3>增强 Xl9555 以驱动 I²C 总线</h3>
<h3>扫描 Wi-Fi</h3>
<h3>连接 Wi-Fi</h3>
<h2>其他说明</h2>
<h3>提高波特率以加快烧录速度</h3>
<h3>提高栈大小以及如何避免栈溢出</h3>
<h3>Windows 下长路径问题</h3>
]]></content>
            <author>
                <name>晓游</name>
            </author>
            <category term="Development"></category>
            <category term="Development / Rust"></category>
            <category term="Rust" label="Rust"></category>
            <category term="Slint" label="Slint"></category>
            <category term="ESP32-S3" label="ESP32-S3"></category>
            </entry>
        <entry>
            <title>在 Android 设备上部署 PySide6 程序</title>
            <link href="https://www.xychr.com/posts/pyside6_on_android/pyside6_on_android/" rel="alternate" type="text/html"/>
            <id>https://www.xychr.com/posts/pyside6_on_android/pyside6_on_android/</id>
            <published>2026-04-18T00:00:00.000Z</published>
            <updated>2026-04-18T00:00:00.000Z</updated>
            <summary>本文记录了我在 Android 设备上部署 PySide6 程序的过程，涵盖了环境搭建、代码编写和运行结果等方面的内容。</summary>
            <content type="html"><![CDATA[<h2>引言</h2>
<p>Qt 一直把 <code>Code less. Create more. Deploy everywhere</code> 当做其宣传语. 笔者一直都在维护一个<a href="https://github.com/XiaoYouChR/Ghost-Downloader-3">基于 <code>Qt for Python</code> 的项目</a>, 很早之前就听说了 <code>PySide6</code> 项目可以部署到 Android 平台上[^1], 但是对系统环境要求十分苛刻, 互联网上也缺少相关文档, 就一直没有尝试此技术. 好在现在微软的 <code>WSL</code> 技术成熟, <code>PySide6</code> 也在 <code>6.8.0</code> 版本之后提供了预编译的轮子[^5], 遂开始了今天的探索.</p>
<blockquote>
<p>[!TIP]
自 <code>PySide6</code> 6.7.3 之后[^5], <code>pyside6-android-deploy</code> 可以在 <code>macOS</code> 上运行.[^6]</p>
</blockquote>
<h2>搭建 <code>WSL</code> 环境</h2>
<p>笔者使用的系统是 <code>Windows 11</code>, <code>Qt for Python</code> 的交叉编译要求的系统平台是 <code>Linux</code>[^1] 或者 <code>macOS</code>[^5], 所以需要搭建 <code>WSL</code> 环境, 这里笔者推荐的是 <code>Ubuntu 24.04 LTS</code>, 理由如下:</p>
<ul>
<li><code>PySide6-Android-Deploy</code> 底层依赖 <code>Buildozer</code>, 而 <a href="https://buildozer.readthedocs.io/en/latest/installation/"><code>Buildozer</code> 的官方文档</a>重点给的是 <code>Ubuntu</code> 平台的教程, 使用 <code>Ubuntu</code> 可以避免不必要的麻烦.</li>
<li>笔者原来常用的是 <code>WSL Arch</code>, <code>WSL</code> 本来安装多个子系统又极其方便, 安装新的 <code>Ubuntu</code> 系统还避免了环境问题.</li>
</ul>
<blockquote>
<p>[!IMPORTANT]
使用 <code>WSL 2</code> 而非 <code>WSL 1</code> 以避免不必要的麻烦
务必将程序源码放置在 <em>WSL 分区</em> 中, 不要在 Windows 分区上进行编译
<a href="https://buildozer.readthedocs.io/en/latest/installation/#notes-for-wsl-users">Notes for WSL users</a></p>
</blockquote>
<p>运行以下命令以安装 <code>Ubuntu 24.04 LTS</code>.</p>
<pre><code>wsl --install -d Ubuntu
</code></pre>
<p>运行失败可以先尝试运行以下两个命令[^2]</p>
<pre><code>dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
</code></pre>
<h2>配置基础编译环境</h2>
<h3>换源并更新软件源</h3>
<p>将镜像源切换到国内镜像源, 这里推荐 <a href="https://mirrors.bfsu.edu.cn/help/ubuntu/">北京外国语大学开源软件镜像站
</a>, 请选用 <code>DEB822 格式</code>.</p>
<pre><code>sudo apt update
sudo apt full-upgrade -y
sudo apt autoremove -y
</code></pre>
<h3>安装系统依赖[^3] [^4]</h3>
<p>笔者这里使用了 <code>Buildozer</code> 的官方文档[^3] 中推荐的 <code>OpenJDK 17</code></p>
<pre><code>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
</code></pre>
<h3>安装 Python 环境</h3>
<p>笔者自己项目推荐的 Python 版本是 <code>3.11</code>, <code>Qt</code> 官方预编译的 <code>PySide6 for Android</code> 也仅支持 <code>cp3.11</code> 而 <code>Ubuntu 24.04 LTS</code> 官方仓库默认没有 <code>3.11</code>, 遂使用 <code>pyenv</code> 安装 <code>Python</code>.</p>
<pre><code>curl -fsSL https://pyenv.run | bash
cat &gt;&gt; ~/.bashrc &lt;&lt;'EOF'
export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] &amp;&amp; export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init - bash)"
EOF
source ~/.bashrc
</code></pre>
<pre><code>pyenv install 3.11
pyenv global 3.11
python --version
</code></pre>
<h3>切换系统 Java 版本</h3>
<p><code>Ubuntu 24.04 LTS</code> 似乎自带了较新版本的 JDK, 需要手动切换</p>
<pre><code>sudo update-alternatives --config java
sudo update-alternatives --config javac
</code></pre>
<p>然后选带 <code>java-17-openjdk</code> / <code>javac-17-openjdk</code> 的那一项</p>
<h3>环境自检</h3>
<pre><code>python3 --version
pip3 --version
java -version
git --version
cmake --version
</code></pre>
<h3>新建 Python 虚拟环境</h3>
<blockquote>
<p>[!IMPORTANT]
建议将虚拟环境放在项目文件夹之外.[^6]</p>
</blockquote>
<pre><code>mkdir ~/venv
python -m venv ~/venv
source ~/venv/bin/activate
python -m pip install -U pip setuptools wheel
</code></pre>
<h3>配置 Python 虚拟环境[^3]</h3>
<pre><code>source ~/venv/bin/activate
pip install buildozer setuptools cython==0.29.34
</code></pre>
<h3>下载 Android NDK 和 SDK</h3>
<p>下载 Android NDK 和 SDK 最简单的方法是通过 <code>Qt for Python</code> 仓库中的一个脚本[^6]</p>
<pre><code>cd ~
git clone https://code.qt.io/pyside/pyside-setup
cd pyside-setup
pip install -r tools/cross_compile_android/requirements.txt
python tools/cross_compile_android/main.py --download-only --skip-update --auto-accept-license
</code></pre>
<p><code>Android NDK 和 SDK</code> 会被下载到 <code>~/.pyside6_android_deploy/</code> 中</p>
<h3>获取适用于 Android 平台的 PySide6 / Shiboken6 轮子</h3>
<p>有三种方式可以获取适用于 Python 的 Qt Android 轮子文件[^6]</p>
<ol>
<li><a href="https://download.qt.io/official_releases/QtForPython/">Qt for Python downloads page</a> 或 <a href="https://download.qt.io/snapshots/ci/pyside/dev/latest/">Qt for Python CI Snapshot</a></li>
<li><code>qtpip download PySide6 --android --arch aarch64</code> (可用的架构包括 <code>aarch64</code> 和 <code>x86_64</code>, 仅适用于 <code>Qt Commercial</code> 用户)</li>
<li><a href="https://doc.qt.io/qtforpython-6/deployment/deployment-pyside6-android-deploy.html#cross-compile-qt-for-python-wheels-for-android">为 Android 交叉编译适用于 Python 的 Qt 轮子</a></li>
</ol>
<p>笔者这里下载了 <a href="https://download.qt.io/official_releases/QtForPython/pyside6/PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl">PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl</a> 和 <a href="https://download.qt.io/official_releases/QtForPython/shiboken6/shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl">shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl</a></p>
<pre><code>mkdir ~/android-wheels
cd ~/android-wheels
wget https://download.qt.io/official_releases/QtForPython/pyside6/PySide6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl
wget https://download.qt.io/official_releases/QtForPython/shiboken6/shiboken6-6.10.3-6.10.3-cp311-cp311-android_aarch64.whl
</code></pre>
<p>至此, 编译环境配置完毕~</p>
<h2>最小 Qt for Android Bootstrap</h2>
<h3>最小启动脚本</h3>
<p>笔者在 <code>~/project</code> 目录创建了 <code>main.py</code> 脚本</p>
<pre><code>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())
</code></pre>
<h3>初始化 PySide6-Android-Deploy Spec 文件</h3>
<pre><code>cd ~/project
source ~/venv/bin/activate
pyside6-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
</code></pre>
<blockquote>
<p>[!TIP]
由于不能使用 <code>~</code>, 请将 <code>/home/xiaoyouchr/</code> 改成你的<code>家目录</code>.</p>
</blockquote>
<blockquote>
<p>[!IMPORTANT]
应用名此时不应带有空格, 笔者在后文会提供 PATCH <code>pyside6-android-deploy</code> 脚本从而使应用名可以带有空格.</p>
</blockquote>
<pre><code>cd ~/project
source ~/venv/bin/activate
pyside6-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
</code></pre>
<pre><code>cd ~/project
ls
</code></pre>
<p>发现 APK 已经生成到项目中啦! 插入手机, 安装部署产物~</p>
<pre><code>cd ~/project
~/.pyside6_android_deploy/android-sdk/platform-tools/adb install ./FirstQtBootstrap-0.1-arm64-v8a-debug.apk
</code></pre>
<h3>排查问题</h3>
<p>哈哈, 如果你是一步一步按着教程走的, 那么不出意外地应该运行闪退咯~
插入手机, 老实抓日志.</p>
<pre><code>~/.pyside6_android_deploy/android-sdk/platform-tools/adb logcat | grep -i -E "FATAL EXCEPTION|AndroidRuntime|Qt|libc|python|shiboken|PySide|dlopen|UnsatisfiedLinkError|crash"
</code></pre>
<p>注意力惊人, 你在日志中敏锐的观察到:</p>
<pre><code>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
</code></pre>
<p>之后你运行了</p>
<pre><code>unzip -l FirstQtBootstrap-0.1-arm64-v8a-debug.apk | grep -i "libpython3"
</code></pre>
<p>发现</p>
<pre><code>-rwxr-xr-x 1 xiaoyouchr xiaoyouchr  5764168 Apr 14 16:22 libpython3.14.so
</code></pre>
<p>怎么回事呢, 为什么有 <code>libpython3.14.so</code>, 但没有 <code>libpython3.11.so</code> 呢?</p>
<h3>PATCH <code>pyside6-android-deploy</code> 脚本</h3>
<p>每次调用 <code>pyside6-android-deploy</code> 都会覆写 <code>buildozer.spec</code> 文件, 而修改 <code>buildozer.spec</code> 后直接手动调用 <code>python -m buildozer android debug</code> 又要自己手动处理复杂的依赖问题, 于是~
修改 <code>~/venv/lib/python3.11/site-packages/PySide6/scripts/deploy_lib/android/buildozer.py</code> 的奇技淫巧的想法油然而生.</p>
<p>使用你常用的编辑器打开 <code>~/venv/lib/python3.11/site-packages/PySide6/scripts/deploy_lib/android/buildozer.py</code></p>
<p>之后找到这行</p>
<pre><code>self.set_value("app", "requirements", "python3,shiboken6,PySide6")
</code></pre>
<p>将其改成</p>
<pre><code>self.set_value("app", "requirements", "python3==3.11.15,hostpython3==3.11.15,shiboken6,PySide6")
</code></pre>
<blockquote>
<p>[!TIP]
增加上 <code>hostpython==3.11.15</code> 的原因是如果不加就会</p>
<pre><code>[ERROR]: Build failed: python3 should have same version as hostpython3, 3.11.14 != 3.14.2
</code></pre>
</blockquote>
<p>还注意到 <code>buildozer.py</code> 中有如下代码</p>
<pre><code># 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 master
self.set_value("app", "p4a.branch", "develop")
</code></pre>
<p>显然此 Commit 已经被合并到了主分支, 为了避免不必要的风险, 建议将这行代码也注释掉.
笔者还发现了这段代码上次被维护是两年前, 最后维护者是 <a href="https://github.com/adrianghc">adrianghc</a>, 这位大佬曾为 <code>QtAsyncIO</code> 的推近付出过巨大的精力. 可惜最后还是从 Qt 公司离职了. 这段代码的维护工作最终也不了了之~</p>
<h3>重新运行部署脚本并在 Android 设备上安装</h3>
<pre><code>cd ~/project
source ~/venv/bin/activate
pyside6-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
pyside6-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
</code></pre>
<p>看到以下画面, 最小 Bootstrap 运行成功了~</p>
<p><img src="https://www.xychr.com/_astro/First_Bootstrap.rB32-8k6_Z1y4ms3.webp" alt="First_Bootstrap.jpg" /></p>
<h2>迁移你的 PySide6 应用到 Android 平台</h2>
<h3>压缩包体</h3>
<p>注意到我们的最小 Bootstrap 在安装后占用存储空间高达 381MB, 这显然是不可接受的.</p>
<pre><code>cd ~/project
unzip -l FirstQtBootstrap-0.1-arm64-v8a-debug.apk
</code></pre>
<p>发现很多没有用到的依赖也被打包进去了</p>
<pre><code>...
   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
...
</code></pre>
<p>需要手动修改 <code>~/venv/lib/python3.11/site-packages/PySide6/scripts/deploy_lib/android/recipes/PySide6/__init__.tmpl.py</code>, 让其仅复制必要的依赖即可.</p>
<h3>使应用名支持空格</h3>
<p>找到 <code>buildozer.py</code> 中的</p>
<pre><code>self.set_value("app", "title", pysidedeploy_config.title)
self.set_value("app", "package.name", pysidedeploy_config.title)
</code></pre>
<p>将 <code>title</code> 改成你的应用名称, 比如</p>
<pre><code>self.set_value("app", "title", "Ghost Downloader 3")
self.set_value("app", "package.name", pysidedeploy_config.title)
</code></pre>
<h3>调试指南</h3>
<p>在进行迁移工作的过程中, 闪退是常有的事情, 我们可以在启动程序前运行</p>
<pre><code>adb logcat -c
adb logcat | rg -i "FATAL EXCEPTION|AndroidRuntime|Qt|libc|python|shiboken|PySide|dlopen|UnsatisfiedLinkError|Permission|SecurityException|EACCES|denied|storage|crash &gt; crash.log"
</code></pre>
<p>打开你的 PySide6 on Android 程序, 在崩溃后立即按下 <code>Ctrl + C</code> 结束 <code>adb logcat</code>.
之后使用你常用的编辑器打开 <code>crash.log</code>, 搜索字符串 <code>python     :</code>, 就能看到 Python 在 <code>stdout</code> 和 <code>stderr</code> 输出的日志, 之后一步一步解决即可~</p>
<h3>导入必要依赖</h3>
<p>你的 <code>PySide6</code> 肯定使用了其他的依赖, 请你使用你常用的编辑器打开 <code>~/venv/lib/python3.11/site-packages/PySide6/scripts/deploy_lib/android/buildozer.py</code>
之后找到这行</p>
<pre><code>self.set_value("app", "requirements", "python3==3.11.15,hostpython3==3.11.15,shiboken6,PySide6")
</code></pre>
<p>在里面加上你需要安装的依赖</p>
<h2>结语</h2>
<p>到此为止, 你已经成功地将你的 PySide6 程序部署到了 Android 设备上了.
感谢阅读~</p>
<p>[^1]: <a href="https://www.qt.io/blog/taking-qt-for-python-to-android">Taking Qt for Python to Android</a></p>
<p>[^2]: <a href="https://learn.microsoft.com/en-us/windows/wsl/install-manual">Manual installation steps for older versions of WSL</a></p>
<p>[^3]: <a href="https://buildozer.readthedocs.io/en/latest/installation/">Buildozer documentation</a></p>
<p>[^4]: <a href="https://python-for-android.readthedocs.io/en/latest/quickstart.html">P4A documentation</a></p>
<p>[^5]: <a href="https://qt-project.atlassian.net/browse/PYSIDE-2766">PYSIDE6 2766</a></p>
<p>[^6]: <a href="https://doc.qt.io/qtforpython-6/deployment/deployment-pyside6-android-deploy.html">pyside6-android-deploy: the Android deployment tool for Qt for Python</a></p>
]]></content>
            <author>
                <name>晓游</name>
            </author>
            <category term="Development"></category>
            <category term="Development / Python"></category>
            <category term="Python" label="Python"></category>
            <category term="Qt" label="Qt"></category>
            <category term="Android" label="Android"></category>
            </entry>
        </feed>