前言

Waydroid 可以在 Linux 桌面环境中运行接近原生体验的 Android 系统。完成基础部署后,通常可以正常启动 Android、连接网络、登录 Google Play,并安装一部分应用。

但在 x86-64 Linux 主机上,经常会遇到一种典型问题:

  • Google Play 可以正常使用;
  • 一部分应用可以安装;
  • 另一些应用显示“与此设备不兼容”;
  • 修改兼容配置后,应用虽然能够安装,却仍然无法启动。

这类问题的核心通常不在网络、Google 账号或 Play 商店,而在于 CPU 指令集架构不匹配

本文完整记录一次在 Arch Linux、KDE Wayland 和 Waydroid Android 13 环境中,为 Waydroid 安装 Intel Houdini ARM 转译层的过程,包括:

  • 如何确认 ABI 不兼容;
  • 如何安装 ARM/ARM64 转译层;
  • 为什么第一次安装只解决了 Play 商店过滤,却不能真正运行应用;
  • 如何处理第三方安装脚本的兼容性问题;
  • 如何验证 Houdini、Native Bridge 和 binfmt 是否全部生效;
  • 如何在完成后清理备份、缓存和临时文件。

文中的用户名、主机名、个人目录和时间戳均已替换或泛化。


一、问题现象

Waydroid 已经完成以下配置:

  • Android 系统可以正常启动;
  • 容器网络正常;
  • Google Play 可以打开;
  • Google 账号可以登录;
  • 部分应用可以正常安装;
  • Android 版本为 13;
  • Waydroid 版本为 1.6.x。

然而,一些常用阅读类应用在 Google Play 中显示:

此应用与当前设备不兼容。

首先检查 Linux 主机架构:

uname -m

输出:

x86_64

再检查 Waydroid 对外报告的 Android ABI:

sudo waydroid shell -- sh -c '
echo "ABI=$(getprop ro.product.cpu.abi)"
echo "ABILIST=$(getprop ro.product.cpu.abilist)"
echo "ANDROID=$(getprop ro.build.version.release)"
echo "SDK=$(getprop ro.build.version.sdk)"
echo "MODEL=$(getprop ro.product.model)"
'

初始结果类似:

ABI=x86_64
ABILIST=x86_64,x86
ANDROID=13
SDK=33
MODEL=WayDroid x86_64 Device

这已经基本确认问题:

当前 Waydroid 只声明支持 x86 和 x86-64,没有声明支持 ARM 或 ARM64。

很多 Android 应用并未发布 x86 版本,只提供:

arm64-v8a
armeabi-v7a

Google Play 检测到设备 ABI 不匹配后,会直接隐藏下载按钮或显示设备不兼容。


二、确认系统中没有现成的 ARM 转译层

在安装任何组件前,先检查 Native Bridge 和常见转译库:

sudo waydroid shell -- sh -c '
echo "NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)"
echo "ABILIST=$(getprop ro.product.cpu.abilist)"

for f in \
    /system/lib*/libhoudini* \
    /system/lib*/libndk_translation* \
    /vendor/lib*/libhoudini* \
    /vendor/lib*/libndk_translation*
do
    [ -e "$f" ] && echo "FOUND=$f"
done
'

结果:

NATIVE_BRIDGE=0
ABILIST=x86_64,x86

没有任何 FOUND= 输出。

这说明系统中既没有 Intel Houdini,也没有 Google NDK Translation。

再检查 Waydroid 配置:

waydroid --version

sudo grep -E \
'^(arch|system_datetime|vendor_datetime|system_ota|vendor_ota|images_path)[[:space:]]*=' \
/var/lib/waydroid/waydroid.cfg

可确认:

  • 架构为 x86_64
  • 系统镜像为 Android 13 GAPPS;
  • 镜像位于 Waydroid 标准数据目录;
  • 当前使用独立的 system 和 vendor 镜像。

三、安装前创建完整备份

由于 ARM 转译层会修改 Waydroid 的系统镜像或 overlay,安装前应保留完整数据目录。

先停止 Waydroid:

waydroid session stop 2>/dev/null || true
sudo systemctl stop waydroid-container.service

确认服务停止:

systemctl is-active waydroid-container.service

预期结果:

inactive

然后创建完整副本。

以下示例采用“先改名为备份,再复制回正式路径”的方式:

(
    set -euo pipefail

    f=/var/lib/waydroid

    if ! sudo test -d "$f"; then
        echo "错误:目录不存在:$f" >&2
        exit 1
    fi

    stamp=$(sudo date -r "$f" +%Y%m%d-%H%M%S)
    old="${f}.${stamp}"

    if sudo test -e "$old"; then
        echo "错误:备份目标已经存在:$old" >&2
        exit 1
    fi

    sudo mv -- "$f" "$old"
    sudo cp -a --reflink=auto --sparse=always -- "$old" "$f"

    echo "正式目录:$f"
    echo "完整备份:$old"

    sudo ls -ld --time-style=long-iso -- "$old" "$f"
    sudo du -sh -- "$old" "$f"
)

这种方式有两个好处:

  1. 备份目录保留原始数据和属性;
  2. 后续操作修改的是复制出来的正式工作目录。

备份大小约为数 GB,具体取决于已安装应用和 Android 数据量。


四、准备 Houdini 安装脚本

安装所需工具:

sudo pacman -S --needed git python lzip

下载第三方 Waydroid 扩展脚本:

mkdir -p ~/Downloads
cd ~/Downloads

git clone https://github.com/casualsnek/waydroid_script.git
cd waydroid_script

创建独立 Python 虚拟环境:

/usr/bin/python3 -m venv venv

venv/bin/python3 -m pip install --upgrade pip
venv/bin/python3 -m pip install -r requirements.txt

这里明确使用:

/usr/bin/python3

目的是避免 pyenv、Conda 或用户自定义 Python 环境干扰系统工具。

核验环境:

readlink -f venv/bin/python3
venv/bin/python3 --version
git rev-parse HEAD

五、第一次安装:Play 商店允许下载,但应用仍无法运行

停止 Waydroid 后安装 Android 13 Houdini:

waydroid session stop 2>/dev/null || true
sudo systemctl stop waydroid-container.service

cd ~/Downloads/waydroid_script

sudo ./venv/bin/python3 main.py \
    --android-version 13 \
    install libhoudini

安装脚本输出类似:

INFO: Downloading libhoudini.zip
INFO: Extracting libhoudini.zip
INFO: Copying libhoudini library files
INFO: libhoudini installation finished

重新启动:

sudo systemctl start waydroid-container.service
waydroid show-full-ui

Android 能够正常启动。

检查 ABI:

sudo waydroid shell -- sh -c '
echo "NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)"
echo "ABILIST=$(getprop ro.product.cpu.abilist)"
echo "ABILIST32=$(getprop ro.product.cpu.abilist32)"
echo "ABILIST64=$(getprop ro.product.cpu.abilist64)"
'

结果已经发生变化:

NATIVE_BRIDGE=libhoudini.so
ABILIST=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi
ABILIST32=x86,armeabi-v7a,armeabi
ABILIST64=x86_64,arm64-v8a

清除 Google Play 数据:

sudo waydroid shell -- pm clear com.android.vending
waydroid session stop
waydroid show-full-ui

此前显示不兼容的应用,现在可以从 Google Play 正常安装。

但新的问题出现了:

应用能够安装,却无法打开。

这说明 Play 商店已经相信设备支持 ARM,但真正的 ARM 转译执行环境可能并没有生效。


六、发现第一次安装只是“声明兼容”,没有实际挂载 Houdini

检查 Houdini 文件:

sudo waydroid shell -- sh -c '
for f in \
    /system/bin/houdini \
    /system/bin/houdini64 \
    /system/lib/libhoudini.so \
    /system/lib64/libhoudini.so
do
    [ -e "$f" ] && ls -l "$f" || echo "MISSING=$f"
done
'

结果全部缺失:

MISSING=/system/bin/houdini
MISSING=/system/bin/houdini64
MISSING=/system/lib/libhoudini.so
MISSING=/system/lib64/libhoudini.so

进一步检查主机端文件:

sudo find \
    /var/lib/waydroid/overlay \
    /var/lib/waydroid/rootfs \
    /tmp/houdiniunpack \
    \( -type f -o -type l \) \
    \( \
        -name houdini -o \
        -name houdini64 -o \
        -name 'libhoudini.so' -o \
        -name 'houdini.rc' \
    \) \
    -print 2>/dev/null

发现文件确实已经被复制到:

/var/lib/waydroid/overlay/system/bin/houdini
/var/lib/waydroid/overlay/system/bin/houdini64
/var/lib/waydroid/overlay/system/lib/libhoudini.so
/var/lib/waydroid/overlay/system/lib64/libhoudini.so
/var/lib/waydroid/overlay/system/etc/init/houdini.rc

但 Android 运行时中完全看不到。

检查配置:

sudo grep -E \
'^[[:space:]]*(mount_overlays|images_path)[[:space:]]*=' \
/var/lib/waydroid/waydroid.cfg

结果:

mount_overlays = False
images_path = /var/lib/waydroid/images

再查看 rootfs:

findmnt /var/lib/waydroid/rootfs

结果表明 rootfs 直接来自只读 ext4 镜像:

/var/lib/waydroid/rootfs /dev/loopX ext4 ro

与此同时,ARM binfmt 处理器也全部缺失:

arm_exe
arm_dyn
arm64_exe
arm64_dyn

这形成了完整的故障链:

  1. 配置中声明支持 ARM;
  2. Play 商店因此允许下载 ARM 应用;
  3. Houdini 文件位于未使用的 overlay 中;
  4. Android 实际 /system 看不到这些文件;
  5. binfmt 没有注册;
  6. ARM 应用安装后无法启动。

七、重新安装时遇到脚本停止容器错误

确认脚本当前判断:

cd ~/Downloads/waydroid_script

sudo ./venv/bin/python3 -c \
'from tools.container import use_overlayfs; print("SCRIPT_USE_OVERLAYFS =", use_overlayfs())'

结果:

SCRIPT_USE_OVERLAYFS = False

理论上,再次执行安装时应该直接修改 system.img

但重新运行安装命令后,脚本异常退出:

ERROR: Stopping container
subprocess.CalledProcessError:
Command '['waydroid', 'container', 'stop']'
returned non-zero exit status 0.

这段错误本身很矛盾:

non-zero exit status 0

退出状态明明是 0,却被脚本当成异常。

实际原因是:

  • waydroid container stop 成功退出;
  • Waydroid 将正常提示写入 stderr;
  • 脚本辅助函数把 stderr 内容误判为错误;
  • 因此安装流程在真正修改镜像之前就停止。

八、修补第三方脚本的容器停止方式

先备份脚本文件,备份名使用原文件时间戳:

cd ~/Downloads/waydroid_script

f=tools/container.py
old="${f}.$(/usr/bin/date -r "$f" +%Y%m%d-%H%M%S)"

mv -- "$f" "$old"
cp -a -- "$old" "$f"

将:

run(["waydroid", "container", "stop"])

替换为:

run(["systemctl", "stop", "waydroid-container.service"])

可以使用 Python 做精确替换:

/usr/bin/python3 - <<'PY'
from pathlib import Path

path = Path("tools/container.py")
text = path.read_text(encoding="utf-8")

old = '    run(["waydroid", "container", "stop"])\n'
new = '    run(["systemctl", "stop", "waydroid-container.service"])\n'

count = text.count(old)

if count != 1:
    raise SystemExit(
        f"停止修改:预期匹配 1 处,实际匹配 {count} 处"
    )

path.write_text(text.replace(old, new), encoding="utf-8")
print("已修改:", path)
PY

核验差异:

git diff -- tools/container.py

预期:

-        run(["waydroid", "container", "stop"])
+        run(["systemctl", "stop", "waydroid-container.service"])

九、处理 root 属主的 Python 缓存

使用 sudo 运行 Python 脚本后,项目目录内生成了 root 属主的 __pycache__

普通用户执行:

./venv/bin/python3 -m py_compile \
    main.py \
    tools/container.py \
    tools/helper.py

会报:

Permission denied: tools/__pycache__/...

这不是语法错误,而是缓存目录权限问题。

清理缓存:

sudo find . -type d -name '__pycache__' -prune -exec rm -rf -- {} +

然后改用不写入 .pyc 的 AST 语法检查:

./venv/bin/python3 - <<'PY'
import ast
from pathlib import Path

for name in (
    "main.py",
    "tools/container.py",
    "tools/helper.py",
):
    path = Path(name)
    ast.parse(
        path.read_text(encoding="utf-8"),
        filename=str(path),
    )
    print(f"PASS: {path}")
PY

输出:

PASS: main.py
PASS: tools/container.py
PASS: tools/helper.py

十、第二次安装:直接写入 system.img

停止 Waydroid:

waydroid session stop 2>/dev/null || true
sudo systemctl stop waydroid-container.service

重新运行安装,并禁止 Python 写入缓存:

cd ~/Downloads/waydroid_script

sudo env PYTHONDONTWRITEBYTECODE=1 \
    ./venv/bin/python3 main.py \
    --android-version 13 \
    install libhoudini

这一次输出明显不同:

INFO: Resizing .../images/system.img
INFO: Mounting .../images/system.img to /tmp/waydroid
INFO: Mounting .../images/vendor.img to /tmp/waydroid/vendor
WARN: /tmp/waydroid/vendor is not a mount point
INFO: Downloading libhoudini.zip
INFO: Extracting libhoudini.zip
INFO: Copying libhoudini library files
INFO: libhoudini installation finished
INFO: Umounting /tmp/waydroid/vendor
INFO: Umounting /tmp/waydroid

关键变化是:

Resizing system.img
Mounting system.img
Copying libhoudini library files
Umounting system.img

这说明 Houdini 不再只是复制到 overlay,而是被直接写入 Android 的系统镜像。

其中:

WARN: /tmp/waydroid/vendor is not a mount point

没有阻止后续流程。Houdini 的主要文件位于 /system,而 system.img 已经成功挂载、写入并卸载。


十一、最终验证

启动 Waydroid:

sudo systemctl start waydroid-container.service
waydroid show-full-ui

等待 Android 就绪后,执行完整验证:

sudo waydroid shell -- sh -c '
echo "========== PROPERTIES =========="
echo "NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)"
echo "ABILIST=$(getprop ro.product.cpu.abilist)"
echo "ENABLE_EXEC=$(getprop ro.enable.native.bridge.exec)"
echo "ISA_ARM=$(getprop ro.dalvik.vm.isa.arm)"
echo "ISA_ARM64=$(getprop ro.dalvik.vm.isa.arm64)"

echo
echo "========== HOUDINI FILES =========="
for f in \
    /system/bin/houdini \
    /system/bin/houdini64 \
    /system/lib/libhoudini.so \
    /system/lib64/libhoudini.so \
    /system/etc/init/houdini.rc
do
    if [ -e "$f" ]; then
        ls -l "$f"
    else
        echo "MISSING=$f"
    fi
done

echo
echo "========== BINFMT HANDLERS =========="
for n in arm_exe arm_dyn arm64_exe arm64_dyn; do
    echo
    echo "--- $n ---"

    if [ -e "/proc/sys/fs/binfmt_misc/$n" ]; then
        cat "/proc/sys/fs/binfmt_misc/$n"
    else
        echo "MISSING=$n"
    fi
done
'

属性结果:

NATIVE_BRIDGE=libhoudini.so
ABILIST=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi
ENABLE_EXEC=1
ISA_ARM=x86
ISA_ARM64=x86_64

Houdini 文件全部存在:

/system/bin/houdini
/system/bin/houdini64
/system/lib/libhoudini.so
/system/lib64/libhoudini.so
/system/etc/init/houdini.rc

四个 binfmt 处理器全部启用:

arm_exe
arm_dyn
arm64_exe
arm64_dyn

32 位 ARM 使用:

interpreter /system/bin/houdini

ARM64 使用:

interpreter /system/bin/houdini64

这意味着:

  • Play 商店能够识别 ARM ABI;
  • Android Runtime 已加载 Native Bridge;
  • Houdini 可执行文件和共享库真实存在;
  • ARM ELF 和 ARM64 ELF 都会被交给 Houdini;
  • 系统已经具备真正的 ARM/ARM64 转译能力。

重新打开此前无法运行的应用后,应用可以正常启动。


十二、清理安装过程中产生的文件

确认应用可以启动后,可以删除测试期产生的备份和工具,但必须保留当前正式的 Waydroid 数据目录。

需要保留:

/var/lib/waydroid

其中包含:

  • 修改后的 system.img
  • Houdini 本体;
  • Waydroid 配置;
  • Android 用户数据;
  • 已安装应用。

可以删除:

  • 完整 Waydroid 备份;
  • 第三方安装脚本仓库;
  • Python 虚拟环境;
  • Houdini 下载缓存;
  • 临时解压目录;
  • 临时挂载目录;
  • 第一次失败安装留下的无效 overlay 文件;
  • 安装日志;
  • 脚本源码备份。

清理示例:

(
    set -euo pipefail

    current=/var/lib/waydroid
    backup=/var/lib/waydroid.<备份时间戳>
    repo=<用户目录>/Downloads/waydroid_script
    cache=<用户目录>/.cache/waydroid-script
    log=<用户目录>/Downloads/waydroid-libhoudini-install.log

    sudo test -d "$current"

    mount_overlays=$(
        sudo awk -F= '
            /^[[:space:]]*mount_overlays[[:space:]]*=/ {
                gsub(/[[:space:]]/, "", $2)
                print $2
            }
        ' "$current/waydroid.cfg"
    )

    if [ "$mount_overlays" != "False" ]; then
        echo "停止清理:mount_overlays 状态不符合预期" >&2
        exit 1
    fi

    if findmnt -rn -o TARGET |
        grep -qE "^${current}/overlay(/|$)"; then
        echo "停止清理:overlay 当前仍处于挂载状态" >&2
        exit 1
    fi

    sudo rm -f -- \
        "$current/overlay/system/bin/houdini" \
        "$current/overlay/system/bin/houdini64" \
        "$current/overlay/system/lib/libhoudini.so" \
        "$current/overlay/system/lib64/libhoudini.so" \
        "$current/overlay/system/etc/init/houdini.rc"

    sudo rm -rf -- "$backup"
    rm -rf -- "$repo"
    sudo rm -rf -- "$cache"
    sudo rm -rf -- /tmp/houdiniunpack

    if ! findmnt -rn -o TARGET |
        grep -qE '^/tmp/waydroid(/|$)'; then
        sudo rm -rf -- /tmp/waydroid
    fi

    rm -f -- "$log"
)

第一次清理时,缓存中的压缩包是 root 属主,普通用户直接执行:

rm -rf ~/.cache/waydroid-script

会出现:

Permission denied

因此缓存目录需要使用:

sudo rm -rf -- ~/.cache/waydroid-script

最终检查已知路径全部不存在,并确认没有残留完整备份:

sudo find /var/lib -maxdepth 1 -type d \
    -name 'waydroid.????????-??????' \
    -printf '%p\n'

无输出表示备份已经清理。

最后验证 Houdini 本体仍然存在:

sudo waydroid shell -- sh -c '
echo "NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)"
echo "ABILIST=$(getprop ro.product.cpu.abilist)"

for f in \
    /system/bin/houdini \
    /system/bin/houdini64 \
    /system/lib/libhoudini.so \
    /system/lib64/libhoudini.so \
    /system/etc/init/houdini.rc
do
    [ -e "$f" ] && echo "OK=$f" || echo "MISSING=$f"
done
'

只要五个文件全部显示 OK=,删除安装脚本和缓存不会影响 ARM 应用运行。


十三、几个容易误判的关键点

1. Play 商店允许安装,不代表 ARM 转译已经生效

只修改:

ro.product.cpu.abilist

就可能让 Google Play 认为设备支持 ARM。

但如果没有:

/system/bin/houdini
/system/lib64/libhoudini.so

应用仍然无法执行。

因此必须同时检查:

  • ABI 属性;
  • Native Bridge;
  • Houdini 文件;
  • binfmt handler。

2. 安装脚本显示完成,不代表 Android 运行时能看到文件

第三方脚本可能成功下载、解压和复制文件,但目标位置未必被当前 Waydroid 挂载。

应当从 Android 容器内部检查:

sudo waydroid shell -- ls -l /system/bin/houdini

而不能只看主机上的 overlay 目录。


3. mount_overlays=False 时,不应依赖 overlay 文件

如果 Waydroid 配置为:

mount_overlays = False

而 Houdini 只存在于:

/var/lib/waydroid/overlay/system/

那么这些文件可能完全不会进入 Android 运行时。

这种情况下应直接写入 system.img,或先彻底确认 overlay 的实际挂载机制。


4. returned non-zero exit status 0 是脚本错误,不是 Waydroid 真失败

这种异常通常说明脚本对 stderr 或命令输出的判断有问题。

退出状态 0 代表命令本身成功。脚本只是因为正常提示文本而误判。


5. root 运行 Python 会污染项目目录权限

通过:

sudo ./venv/bin/python3 ...

运行脚本时,Python 可能在源码目录生成 root 属主的:

__pycache__
*.pyc

后续普通用户执行语法检查就可能遇到权限错误。

可以通过:

sudo env PYTHONDONTWRITEBYTECODE=1 ...

避免生成缓存。


十四、最终结论

在 x86-64 Arch Linux 主机上,Waydroid 默认通常只支持:

x86_64,x86

因此,大量仅提供 ARM 或 ARM64 原生库的 Android 应用会被 Google Play 判定为不兼容。

安装 Houdini 后,设备 ABI 可以扩展为:

x86_64,x86,arm64-v8a,armeabi-v7a,armeabi

但真正可用必须满足四个条件:

  1. ro.dalvik.vm.native.bridge=libhoudini.so
  2. Houdini 可执行文件和共享库存在于 Android 的真实 /system
  3. ARM 与 ARM64 binfmt handler 已注册
  4. 应用能够实际启动,而不只是允许安装

本次故障的关键并不是“不会安装 Houdini”,而是:

第一次安装把文件放入了未生效的 overlay,导致 Play 商店兼容性过滤已经改变,但 Android 运行时仍然没有 ARM 转译器。

通过修复脚本停止容器的方式,并让脚本直接挂载和修改 system.img,最终完成了完整的 ARM/ARM64 转译支持。

安装成功后,原本显示不兼容的应用不仅可以下载,也可以正常打开。全部临时文件、脚本、缓存和备份也可以在最终验证后安全删除。

Leave a Reply

Your email address will not be published. Required fields are marked *