OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 144

NixOS 中的 Waydroid 集成

用实际模块源码区分系统声明、Android 镜像和用户运行状态。

所在专题Waydroid ↗
约 46 分钟阅读开始阅读

适用范围与操作前提#

命令和输出使用通用示例。内核、镜像、包与服务版本会影响具体行为,阅读时应结合所用版本的文档核对。

修改容器、镜像、挂载和防火墙前先备份 Android 数据并停止相关会话;带删除或强制停止的示例可能丢失数据。NixOS 的系统与用户环境配置应通过 Nix 管理,通用 Linux 的 apt/pacman、直接写 /etc 和 pip 安装段落不用于 NixOS 宿主。重建的 build、test、switch 与启动后的功能验证是不同阶段。

本文聚焦 NixOS 上 Waydroid 的声明式配置、模块选项深度解析、命令式与声明式的边界,以及 NixOS 特有的故障排查。通用安装流程和架构原理请先阅读 架构深度解析 和 多发行版安装指南。


NixOS 模块选项详解#

NixOS 通过 virtualisation.waydroid 模块将 Waydroid 的安装、systemd 服务管理、网络后端选择全部封装为声明式选项。理解每个选项的含义及其底层映射,是正确配置的前提。

模块选项全表

以下核对 nixos-26.05 分支模块;unstable 与稳定分支不能混为一个版本。实际配置以项目 flake.lock 锁定的模块为准:

选项 类型 默认值 说明
enable boolean false 启用 Waydroid 容器环境。开启后自动安装 Waydroid 包、启用 systemd 服务、配置 PolicyKit 鉴权规则
package package 按 networking.nftables.enable 选择 启用 nftables 时用 pkgs.waydroid-nftables,否则用 pkgs.waydroid
网桥 模块实现 waydroid0 此版本没有 virtualisation.waydroid.interface 选项
服务启动 systemd 实现 multi-user.target 此版本没有 autoStart 选项,模块为容器服务配置 wantedBy

该模块的源码位于 nixpkgs/nixos/modules/virtualisation/waydroid.nix,你可以直接查阅该文件了解最新的实现细节。阅读模块源码是理解 NixOS 系统行为的最佳方式。

enable 选项的底层行为

当设置 virtualisation.waydroid.enable = true 时,NixOS 模块会执行以下操作:

  1. 安装软件包:将 config.virtualisation.waydroid.package(默认按防火墙配置选择)加入 environment.systemPackages
  2. 启用 systemd 服务:激活 waydroid-container.service,该服务负责管理 Android LXC 容器的生命周期
  3. D-Bus 集成:注册所选包的 D-Bus 文件,服务 BusName 是 id.waydro.Container;实际授权应检查 D-Bus 策略,不虚构 PolicyKit action
  4. 内核条件:26.05 模块检查 ANDROID_BINDER_IPC、ANDROID_BINDERFS、MEMFD_CREATE,并添加 psi=1。模块自身未声明所述 modprobe ExecStartPre;驱动探测由实际包/上游流程处理。
# 最小启用配置
{
  virtualisation.waydroid.enable = true;
}

nixos-rebuild switch 后,你可以用以下命令检查服务的状态:

systemctl status waydroid-container

enable = true 不会自动执行 waydroid init。镜像下载和初始化必须手动完成。详见本文"命令式与声明式的边界"一节。

package 选项:iptables vs nftables 变体

这是 NixOS 上最关键的选项之一。nixpkgs 提供了两个 Waydroid 派生:

派生 网络后端 适用场景
pkgs.waydroid iptables (legacy) 使用 iptables 防火墙规则的旧系统,或显式需要兼容性的场景
pkgs.waydroid-nftables nftables 启用了 networking.nftables.enable 的配置

技术背景:Waydroid 需要创建 NAT 网络让 Android 容器访问外部网络。早期 Waydroid 使用 iptables 规则实现 NAT,但随着 Linux 生态向 nftables 迁移,上游 Waydroid 增加了 nftables 支持。nixpkgs 通过两个独立的派生来支持这两种后端——它们的区别在于编译时的依赖和内置的网络脚本:

  • pkgs.waydroid:依赖 iptables,使用 iptables 命令创建 NAT 和 DNAT 规则
  • pkgs.waydroid-nftables:依赖 nftables,使用 nft 命令创建等价的 nftables 规则
# 显式选择 nftables 变体的配置(应与实际防火墙匹配)
{
  virtualisation.waydroid = {
    enable = true;
    package = pkgs.waydroid-nftables;  # 使用 nftables 后端
  };
}

如何选择

  • 先检查当前 networking.nftables.enable;不能用 NixOS 版本号推断它已经启用。
  • 如果你的 NixOS 配置中显式设置了 networking.nftables.enable = false 且仍在使用 iptables,可回退到 pkgs.waydroid。
  • 二者提供不同网络后端集成,实际和宿主规则的交互仍需验证,不能保证所有行为完全相同。

模块与 systemd 服务的映射关系

Waydroid 在 NixOS 上涉及两层 systemd 服务:

虚拟文件系统层:
  waydroid-container.service (系统级)
    ├── 驱动探测: 由实际 Waydroid/LXC 运行流程执行
    ├── ExecStartPre: mkdir/挂载准备         # 创建运行时目录
    ├── ExecStart: waydroid container start   # 启动 LXC 容器
    └── ExecStop: waydroid container stop     # 停止容器

用户会话层:
  Waydroid 用户会话(不保证有自动生成的 waydroid-session.service)
    ├── Wayland socket 桥接
    ├── PulseAudio 音频桥接
    └── Clipboard/Notification 后台服务

关键理解:waydroid-container.service 是系统级 systemd 服务,由 multi-user.target 拉起(或手动启动);waydroid-session 是用户会话级服务,必须在图形会话(Wayland)中运行。两者通过 D-Bus 通信,PolicyKit 负责权限控制。


声明式配置方案#

本节提供从最小到完整的 NixOS Waydroid 配置示例,可直接用于 configuration.nix 或 flake 模块。

最小配置

适用于快速体验,仅启用 Waydroid 本体和剪贴板支持:

{ config, pkgs, ... }:
{
  virtualisation.waydroid = {
    enable = true;
    package = pkgs.waydroid-nftables;
  };

  # 剪贴板共享(Wayland 环境必需)
  environment.systemPackages = with pkgs; [
    wl-clipboard
  ];
}

应用配置后,手动执行初始化:

sudo nixos-rebuild switch
sudo waydroid init -s GAPPS   # 或 -s VANILLA(无 Google 服务的纯净版)
systemctl start waydroid-container
waydroid session start
waydroid show-full-ui

完整配置:GPU 加速 + 环境变量 + 工具链

以下保留工具链、目录共享和 GPU 配置的结构。社区包/服务名需要按锁定 nixpkgs 验证;它不是完整可直接部署的默认模块。

{ config, pkgs, lib, ... }:
let
  # NUR 仓库(提供 waydroid-script 等第三方工具)
  nur = import <PINNED_NUR_PATH> { # 结构示意:实际使用 flake input 并锁定 revision,不做浮动 fetchTarball
    inherit pkgs;
  };
in
{
  # ===== Waydroid 核心 =====
  virtualisation.waydroid = {
    enable = true;
    package = pkgs.waydroid-nftables;
    # 容器服务由模块配置到 multi-user.target,
    # 但需要先完成 waydroid init 才能正常工作
    # 此模块版本无 autoStart 选项;按需要通过 systemd 覆盖启动策略
  };

  # ===== 用户组 =====
  # 用户组不是本模块的默认授权接口
  # 不假定存在 waydroid 用户组;用户授权按实际包与策略检查

  # ===== 环境变量 =====
  environment.sessionVariables = {
    # GBM (Generic Buffer Management) 开关
    # - 不设置:Waydroid 默认使用 GBM 进行 GPU 缓冲区管理
    # - 设为 "1":禁用 GBM,回退到 SwiftShader 软件渲染
    #   NVIDIA 闭源驱动用户通常需要禁用 GBM
    #   Intel/AMD 开源驱动用户保持默认(不设置)即可
    # 不导出未确认被当前版本读取的 WAYDROID_DISABLE_GBM
  };

  # ===== 系统包 =====
  environment.systemPackages = with pkgs; [
    # 剪贴板共享(必需)
    wl-clipboard

    # Android 调试工具(可选)
    android-tools          # 提供 adb、fastboot

    # 嵌套 Wayland compositor(Hyprland 用户可能需要 cage 创建嵌套会话)
    cage

    # ARM 转译脚本(可选,来自 NUR)
    # nur.repos.ataraxiasjel.waydroid-script
  ];

  # ===== Waydroid Helper(NixOS 25.11+ 推荐) =====
  # waydroid-helper 是 NixOS 社区维护的辅助包,
  # 提供更声明式的目录挂载和容器管理方案
  #
  # 包含以下系统服务:
  #   - waydroid-mount.service:管理 Android 容器与宿主的目录绑定挂载
  #
  # 包含以下用户服务:
  #   - waydroid-monitor.service:监控 Waydroid 容器状态,自动处理热插拔事件

  systemd = {
    # 将 waydroid-helper 的 systemd 单元注册到系统
    packages = [ pkgs.waydroid-helper ];
    # 启用 waydroid-mount 系统服务
    services.waydroid-mount = {
      wantedBy = [ "multi-user.target" ];
      # waydroid-mount 需要在 waydroid-container 之后启动
      after = [ "waydroid-container.service" ];
    };
  };

  # ===== 包安全提示的处理 =====
  # Waydroid 在 25.11+ 上可能触发某些 nixpkgs 的不稳定性警告,
  # 安全告警需要确认具体问题、受影响路径及替代版本,不把 permittedInsecurePackages 作为通用消警方案:
  # nixpkgs.config.permittedInsecurePackages = [
  #   "python3.12-xxx"  # 按实际警告内容填写
  # ];
}

应用上述配置后,执行以下命令完成初始化:

# 1. 重建系统
sudo nixos-rebuild switch

# 2. 重新登录(使 group 变更生效)
# 或使用: # 不假定存在 waydroid 组

# 3. 初始化 Waydroid(-s GAPPS 表示包含 Google 服务)
sudo waydroid init -s GAPPS

# 4. 启动容器服务
sudo systemctl start waydroid-container

# 5. 启动会话
waydroid session start

# 6. 启用 waydroid-monitor(NixOS 25.11+ 带 waydroid-helper 时)
systemctl --user enable --now waydroid-monitor

# 7. 显示 Waydroid 全屏界面
waydroid show-full-ui

GPU 属性配置

Waydroid 的 GPU 相关属性保存在 /var/lib/waydroid/waydroid_base.prop 中。以下配置适用于不同 GPU 场景:

AMD/Intel 开源驱动(推荐):

# 使用宿主 GPU 加速(GBM 模式)
# 修改 /var/lib/waydroid/waydroid_base.prop
# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.gralloc=gbm
# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.egl=mesa

NVIDIA 闭源驱动:

# NVIDIA 闭源驱动下,GBM 支持不完善,回退到 SwiftShader
# 图形覆盖放在 waydroid.cfg 的 [properties];不使用未确认环境变量
# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.gralloc=default
# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.egl=swiftshader

SwiftShader 软件渲染(兜底方案):

# 任何 GPU 都可用,但无硬件加速
# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.gralloc=default
# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.egl=swiftshader

GPU 属性注意事项

  • 修改 waydroid_base.prop 后需要重启容器:sudo systemctl restart waydroid-container
  • GPU 加速的完整讨论见 GPU 与硬件加速
  • AMD/Intel Mesa 路径常是可行起点,但仍应验证实际 renderer 与应用行为

memfd 优化

支持的 Android 镜像可用 memfd 替代旧 ashmem 路径,不能将它视为 Binder 的替代或保证性能提升:

# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 sys.use_memfd=true

内核选择由 boot.kernelPackages 决定,不从 NixOS 发行版本推断固定内核。memfd_create 自 Linux 3.17 提供;镜像与 Waydroid 对它的支持另外核对。启用后,Android 中的匿名共享内存(ashmem)将改用 memfd 实现,减少内核模块依赖。


命令式与声明式的边界#

这是 NixOS 上使用 Waydroid 最核心的认知问题:现成模块管理系统侧配置,Android 镜像与应用数据仍有独立状态。理解这个边界,才能避免陷入"把所有东西写进 Nix"的死胡同。

为什么不能完全声明式

Waydroid 的运行时模型与 NixOS 声明式哲学之间存在根本性矛盾:

graph LR
    subgraph Declarative["NixOS 声明式领域"]
        A[configuration.nix] --> B["virtualisation.waydroid.enable"]
        B --> C[软件包安装]
        B --> D[systemd 服务]
        B --> E[PolicyKit 规则]
        B --> F[环境变量]
    end

    subgraph Imperative["运行状态领域(模块未自动管理)"]
        G["waydroid init<br/>下载 ~500MB 系统镜像"]
        H["waydroid upgrade<br/>按维护窗口检查 OTA"]
        I["waydroid prop set<br/>运行时属性配置"]
        J["waydroid app install<br/>APK 安装"]
    end

    C --> G
    G -.->|"镜像写入<br/>/var/lib/waydroid/"| K["模块未自动管理的镜像/数据状态"]

核心矛盾有三:

  1. 镜像下载是网络副作用:waydroid init 从 sourceforge.net 或 ota.waydro.id 下载 Android 系统镜像(约 300-500MB),这是一个网络 I/O 操作。普通构建限制网络,但 fixed-output fetcher 可以按 hash 获取镜像;是否纳入 Nix 管理是工程设计与更新策略问题。

  2. 镜像更新是持续的外部事件:OTA 随上游发布变化,不保证每周。可记录镜像渠道、版本和 hash,甚至自定义固定来源,但现成 NixOS 模块不会自动做到这一点。

  3. 初始化写入运行时目录:waydroid init 将镜像解压到 /var/lib/waydroid/(系统级)和配置文件写入 ~/.local/share/waydroid/(用户级),这些路径不在 Nix store(/nix/store/)中,不受 NixOS 的原子性保护。

声明式 vs 命令式边界表

操作 声明式 命令式 说明
安装 Waydroid 软件包 :heavy_check_mark: :x: virtualisation.waydroid.enable = true
选择 nftables/iptables 后端 :heavy_check_mark: :x: virtualisation.waydroid.package
设置环境变量 :heavy_check_mark: :x: environment.sessionVariables
安装辅助工具 :heavy_check_mark: :x: environment.systemPackages
systemd 服务管理 :heavy_check_mark: :x: systemd.services / systemd.packages
下载系统镜像 :x: :heavy_check_mark: sudo waydroid init -s GAPPS
升级系统镜像 :x: :heavy_check_mark: sudo waydroid upgrade
设置运行时属性 :x: :heavy_check_mark: sudo waydroid prop set <key> <value>
安装 APK :x: :heavy_check_mark: waydroid app install xxx.apk
用户空间数据管理 :x: :heavy_check_mark: ~/.local/share/waydroid/

社区讨论与设计判断

源笔记没有提供可核实的讨论链接,不能将其中的英文引语和“一致共识”当作真实社区结论。实际设计应区分配置、镜像输入与用户数据:前两者可以按需要做版本化或固定 hash,后者仍需备份和迁移。系统代际回滚不会自动回滚 /var/lib/waydroid 和用户数据。

实践中的折中模式

以下三种组织方式可帮助记录与复用必要操作,不把它们标成未核实的社区统一建议:

模式一:首次安装脚本

将 waydroid init 封装为一个一次性的设置脚本,与 NixOS 配置分离:

# 在 configuration.nix 中声明一个 setup 脚本
environment.systemPackages = with pkgs; [
  (writeShellScriptBin "waydroid-first-setup" ''
    set -euo pipefail
    echo "=== Waydroid 首次安装脚本 ==="

    # 检查是否已初始化
    if [ -f /var/lib/waydroid/waydroid_base.prop ]; then
      echo "Waydroid 已初始化,跳过。如需重新初始化,请先执行:"
      echo "  sudo waydroid init -f -s GAPPS"
      exit 0
    fi

    echo "步骤 1/3: 确保容器服务未运行..."
    sudo systemctl stop waydroid-container || true

    echo "步骤 2/3: 下载并初始化系统镜像..."
    sudo waydroid init -s GAPPS

    echo "步骤 3/3: 应用 NixOS 优化属性..."
    # 启动期属性:在 waydroid.cfg 的 [properties] 中设置 sys.use_memfd=true
    # AMD/Intel GPU:
    # 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.gralloc=gbm
    # 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.egl=mesa
    # 如需 ARM 转译,取消下行的注释:
    # sudo waydroid prop set ro.product.cpu.abilist x86_64,arm64-v8a,armeabi-v7a,armeabi
    # sudo waydroid prop set ro.product.cpu.abilist64 x86_64,arm64-v8a

    echo "=== 初始化完成 ==="
    echo "现在执行:"
    echo "  sudo systemctl start waydroid-container"
    echo "  waydroid session start"
    echo "  waydroid show-full-ui"
  '')
];

首次部署时执行:waydroid-first-setup,后续 nixos-rebuild switch 不再重复初始化。

模式二:周期性升级定时器

可按维护窗口检查镜像更新;只有已确认备份、会话停止和回退策略后才启用自动升级 timer。下方 weekly 只是频率示例:

systemd = {
  services.waydroid-upgrade = {
    description = "Waydroid OTA upgrade";
    after = [ "network-online.target" ];
    wants = [ "network-online.target" ];
    path = with pkgs; [ waydroid-nftables ];
    script = ''
      if [ -f /var/lib/waydroid/waydroid_base.prop ]; then
        # 更新前保存数据并协调运行会话
        waydroid upgrade
      fi
    '';
    serviceConfig = {
      Type = "oneshot";
      # 非关键服务,失败不影响系统
      # 日志可通过 journalctl -u waydroid-upgrade 查看
    };
  };

  timers.waydroid-upgrade = {
    description = "Weekly Waydroid OTA upgrade";
    wantedBy = [ "timers.target" ];
    timerConfig = {
      OnCalendar = "weekly";
      Persistent = true;  # 如果错过了执行窗口,启动后补执行
      RandomizedDelaySec = "1h";  # 随机延迟,避免全网同时请求 OTA 服务器
    };
  };
};

模式三:属性管理增量配置

将 waydroid prop set 的变更记录在一个脚本中,便于换机复现:

environment.systemPackages = with pkgs; [
  (writeShellScriptBin "waydroid-apply-props" ''
    # 增量应用 Waydroid 属性(仅修改与默认值不同的项)
    # 这个脚本可以安全地多次运行

    props() {
      sudo waydroid prop set "$1" "$2"
    }

    # 性能优化
    # 此启动期属性不能由运行时 props 保证生效:sys.use_memfd=true

    # GPU 配置(根据你的硬件选择)
    # 此启动期属性不能由运行时 props 保证生效:ro.hardware.gralloc=gbm
    # 此启动期属性不能由运行时 props 保证生效:ro.hardware.egl=mesa

    echo "属性已应用。重启容器以生效:"
    echo "  sudo systemctl restart waydroid-container"
  '')
];

NixOS 特有故障与解决方案#

NixOS 的不可变基础设施模型和独特的系统组织方式,会引发一些在其他发行版上不会出现的问题。以下是 NixOS 用户最常见的故障场景及解决方案。

问题 1:fstab 警告——"不要先装 Waydroid 再跑 nixos-generate-config"

症状:

$ nixos-generate-config
warning: unknown filesystem type 'waydroid' for mount '/var/lib/waydroid'

或更严重的:

$ sudo nixos-rebuild switch
error: fileSystems option contains an entry for '/var/lib/waydroid' with unknown filesystem type

原因:Waydroid 的 systemd 服务在 /var/lib/waydroid/ 下创建了绑定挂载(bind mount),nixos-generate-config 会扫描 /proc/mounts 并将所有挂载点写入 hardware-configuration.nix。如果此时 Waydroid 容器正在运行,生成的配置会错误地包含 Waydroid 的内部挂载。

解决方案:

# 1. 停止 Waydroid 容器
sudo systemctl stop waydroid-container

# 2. 卸载 Waydroid 的所有绑定挂载
sudo waydroid container stop
# 或手动卸载:
findmnt -R /var/lib/waydroid # 先核对挂载,不盲目递归卸载

# 3. 重新生成硬件配置
sudo nixos-generate-config

# 4. 确认 hardware-configuration.nix 中没有 waydroid 相关条目
grep waydroid /etc/nixos/hardware-configuration.nix
# 应该无输出

# 5. 重新应用配置
sudo nixos-rebuild switch

最佳实践:在安装 Waydroid 之前先完成 NixOS 的初始安装和 nixos-generate-config。如果你已经遇到了这个问题,上面的清理步骤可以解决。

问题 2:LXC 版本不匹配

症状:

failed to start container: lxc version mismatch
# 或
Error: container 'waydroid' is not running

原因:nixpkgs 中的 Waydroid 和 LXC 可能因 channel 更新不同步而产生版本不匹配。Waydroid 深度依赖 LXC 的用户态工具链(lxc-start、lxc-stop 等),版本不兼容会导致容器启动失败。

诊断:

# 检查 Waydroid 链接的 LXC 版本
waydroid --version
file "$(command -v waydroid)" # Waydroid 通常是脚本/包装器,ldd 不能追踪其全部依赖

# 检查系统中 LXC 的版本
lxc-start --version
nix-store --query --requisites "$(readlink -f "$(command -v waydroid)")" | rg lxc # 仅作闭包线索

解决方案:

# 方案 1:锁定 LXC 版本(如果已知兼容版本)
{
  nixpkgs.config.packageOverrides = super: let self = super.pkgs; in {
    waydroid = super.waydroid.override {
      lxc = super.lxc.overrideAttrs (old: {
        # 如果需要特定版本,在此指定
      });
    };
  };
}
# 方案 2:清除 Waydroid 缓存并重新初始化
sudo systemctl stop waydroid-container
sudo waydroid container stop
# 破坏性清理示例:先停止会话/容器、核对路径和备份,再手动决定
# sudo rm -rf -- /var/lib/waydroid/data
# 破坏性清理示例:先停止会话/容器、核对路径和备份,再手动决定
# sudo rm -rf -- /var/lib/waydroid/rootfs
# 仅保留配置文件
sudo waydroid init -f -s GAPPS
sudo systemctl start waydroid-container

在 NixOS unstable channel 上,LXC 和 Waydroid 的版本差异通常会在几天内通过 PR 修复。如果你遇到此问题,可以尝试回退到上一个 channel 版本或等待更新。

问题 3:binder 内核模块不可用

症状:

modprobe: FATAL: Module binder_linux not found in directory /lib/modules/...

原因:NixOS 内核默认将 binder_linux 编译为模块,但某些自定义内核配置可能未包含 ANDROID_BINDER_IPC 选项。

诊断:

# 检查内核是否支持 binder
zgrep ANDROID_BINDER /proc/config.gz
# 期望输出:
# CONFIG_ANDROID_BINDER_IPC=y  (或 =m,表示编译为模块)

# 检查模块是否可用
modinfo binder_linux

# 如果内核不支持,检查是否有 ashmem 替代
zgrep ANDROID /proc/config.gz

解决方案(NixOS):

{
  # 方案 1:启用 binder 内核模块(标准 NixOS 内核已包含)
  boot.kernelModules = [ "binder_linux" ];

  # 方案 2:如果使用自定义内核,确保启用 Android 选项
  boot.kernelPatches = [
    {
      name = "enable-android-binder";
      patch = null;
      extraStructuredConfig = with lib.kernel; {
        ANDROID = yes;
        ANDROID_BINDER_IPC = yes;
      };
    }
  ];

  # 方案 3:使用 支持当前镜像的内核 的 memfd 替代 ashmem
  # (在 waydroid_base.prop 中设置 sys.use_memfd=true)
  # 这不需要内核模块支持
}

NixOS 26.05 (kernel 6.18.x) 默认包含 ANDROID_BINDER_IPC=m,通常不需要额外配置。如果你使用的是非标准内核(如 linux_lqx、linux_zen),需要确认是否包含该选项。

问题 4:合成器兼容性与可选嵌套会话

症状:在 Hyprland 上启动 Waydroid 后,窗口无法正常显示或输入事件混乱。

原因:Waydroid 在 Wayland 下作为独立窗口运行,但某些 Wayland compositor(尤其是基于 wlroots 的 Hyprland)可能在窗口合成、输入焦点管理上与 Waydroid 的 Wayland 后端产生冲突。

解决方案:使用 cage 创建嵌套的 Wayland 会话:

# 1. 安装 cage(已在上述完整配置中包含)
# environment.systemPackages = [ pkgs.cage ];

# 2. 在 cage 中启动 Waydroid
cage -- waydroid show-full-ui

# 或者创建一个专用的启动脚本
cat > ~/bin/waydroid-cage <<'EOF'
#!/usr/bin/env bash
# 在嵌套 Wayland 会话中运行 Waydroid,解决 Hyprland 兼容性问题
cage -s -- waydroid show-full-ui
EOF
chmod +x ~/bin/waydroid-cage

Hyprland 用户可以绑定快捷键:

# ~/.config/hypr/hyprland.conf
bind = $mainMod, W, exec, ~/bin/waydroid-cage

如果你使用 GNOME Wayland 或 KDE Plasma Wayland,通常不需要 cage。是否需要嵌套会话取决于具体版本与故障;Hyprland 当前不是基于 wlroots 的项目,不能泛化归类。

问题 5:混合显卡多输出配置

症状:在混合显卡(Intel + NVIDIA / AMD + NVIDIA)的笔记本电脑上,Waydroid 可能选择了错误的 GPU。

原因:NixOS 上混合显卡通常通过 hardware.nvidia.prime(NVIDIA PRIME)或 services.xserver.videoDrivers 管理。Waydroid 通过 /dev/dri/ 下的渲染节点(renderD128、renderD129 等)检测 GPU,可能选到错误的渲染节点。

诊断:

# 检查可用的渲染节点
ls -la /dev/dri/
# 通常:
# renderD128 - 集成显卡(AMD iGPU / Intel iGPU)
# renderD129 - 独立显卡(NVIDIA / AMD dGPU)

# 检查 Waydroid 当前使用的 GPU
sudo waydroid shell dumpsys SurfaceFlinger | grep -i gpu

解决方案:

{
  # NixOS Hybrid 配置示例(Intel + NVIDIA)
  hardware.nvidia = {
    modesetting.enable = true;
    powerManagement.enable = true;
    prime = {
      # Intel iGPU 作为主渲染设备
      intelBusId = "<VERIFIED_INTEL_BUS_ID>";
      nvidiaBusId = "<VERIFIED_NVIDIA_BUS_ID>";
    };
  };

  # 确保 Waydroid 能访问两个 GPU 的渲染节点
  # /dev/dri/renderD128 (iGPU) 和 /dev/dri/renderD129 (dGPU)
}

在 Waydroid 属性中指定 GPU:

# 使用集显(推荐,功耗更低)
# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.gralloc=gbm
# 启动期属性:在 waydroid.cfg 的 [properties] 中设置 ro.hardware.egl=mesa

# 如果必须使用独显
# sudo waydroid prop set ro.hardware.gralloc gbm
# sudo waydroid prop set ro.hardware.egl mesa

NVIDIA 混合显卡用户 如果你使用 NVIDIA PRIME render offload 模式,Waydroid 在 Wayland 下对 NVIDIA 独显的支持非常有限。推荐:

  1. 优先使用 Intel/AMD 集显运行 Waydroid
  2. 若所用图形路径不受支持,按官方软件渲染 FAQ 在 waydroid.cfg 的 [properties] 设置 gralloc/egl,再应用配置和验证 renderer。

问题 6:存储空间不足与清理

症状:/var/lib/waydroid/ 占用大量空间(典型 5-15GB),/ 分区空间不足。

原因:Waydroid 的 Android 系统镜像和数据分区存储在该目录下。GApps 版本比 VANILLA 版本大约多 1GB。

监控:

# 查看 Waydroid 存储占用
du -sh /var/lib/waydroid/
du -sh /var/lib/waydroid/*/

# 查看 Android 内部存储使用
sudo waydroid shell df -h

清理方案:

# 完全重置 Waydroid(保留系统镜像)
sudo systemctl stop waydroid-container
# 破坏性清理示例:先停止会话/容器、核对路径和备份,再手动决定
# sudo rm -rf -- /var/lib/waydroid/data
# 破坏性清理示例:先停止会话/容器、核对路径和备份,再手动决定
# sudo rm -rf -- /var/lib/waydroid/cache
sudo waydroid init -f -s GAPPS
sudo systemctl start waydroid-container

# 完全删除 Waydroid(包括系统镜像)
sudo systemctl stop waydroid-container
sudo waydroid container stop
# 破坏性清理示例:先停止会话/容器、核对路径和备份,再手动决定
# sudo rm -rf -- /var/lib/waydroid/
# 破坏性清理示例:先停止会话/容器、核对路径和备份,再手动决定
# rm -rf -- ~/.local/share/waydroid/
# 破坏性清理示例:先停止会话/容器、核对路径和备份,再手动决定
# rm -rf -- ~/.config/waydroid/
# 破坏性清理示例:先停止会话/容器、核对路径和备份,再手动决定
# rm -rf -- ~/waydroid/

NixOS 版本兼容性检查

源笔记的发行代号、固定内核和默认后端矩阵没有充分证据,不把它们当作已验证的历史表。改为按实际输入核对:

层次 核对依据 验收
系统模块 flake.lock / 对应分支的 waydroid.nix 支持的选项、内核断言、服务和接口
软件包 锁定 nixpkgs 的 derivation Waydroid/LXC/网络工具闭包
内核 boot.kernelPackages 与运行内核 Binder、BinderFS、MEMFD 和 PSI 实际可用
图形 宿主/镜像的 Mesa、DRM 与合成器 renderer、UI 与应用运行
状态 Android 镜像版本和用户数据 初始化、升级、备份和恢复

总结#

NixOS 上部署 Waydroid 的核心原则:

  1. 声明式管理能做的部分:软件包安装、systemd 服务、环境变量、PolicyKit 规则——全部通过 virtualisation.waydroid 和 systemd 选项声明式配置。
  2. 命令式操作不可避免的部分:waydroid init、waydroid upgrade、waydroid prop set——将其封装为一次性脚本或 systemd timer,保持与 NixOS 配置的清晰边界。
  3. 选择正确的后端:按 networking.nftables.enable 和实际包选择一致的后端。
  4. GPU 优先使用集成显卡:AMD/Intel 开源驱动的 GBM 模式体验最佳,NVIDIA 用户需特殊处理。
  5. 善用社区资源:waydroid-helper(NixOS 25.11+)和 NUR 的 waydroid-script 可以简化常见操作。

Waydroid 的"不完全声明式"并非 NixOS 的缺陷,而是现有模块和运行状态管理方式的边界。接受这个边界,用脚本弥合缝隙,是 NixOS 与 Waydroid 协作的最佳实践。


核对依据#

Waydroid 官方安装说明。

Waydroid 官方命令说明。

Waydroid 上游源码。

NixOS 26.05 Waydroid 模块。

NixOS Wiki:Waydroid。

参考与延伸

  1. Waydroid 官方安装说明 ↗
  2. Waydroid 官方命令说明 ↗
  3. Waydroid 上游源码 ↗
  4. NixOS 26.05 Waydroid 模块 ↗
  5. NixOS Wiki:Waydroid ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

Waydroid

  1. 01Waydroid 架构:LXC、Binder 与会话桥接
  2. 02Waydroid 安装与基础设置
  3. 03NixOS 中的 Waydroid 集成
  4. 04Waydroid 的 GPU 检测与硬件加速
  5. 05Waydroid 网桥、NAT 与安全边界
  6. 06Waydroid 属性、多窗口与 ARM 转译
  7. 07Waydroid 故障排查与 FAQ
  8. 08Waydroid 使用技巧:窗口、转译和共享目录
CONTINUE READING / 下一篇Waydroid 的 GPU 检测与硬件加速

从 DRM 节点、Android 图形属性和合成器交互定位渲染问题。