镜像:yeshen/docker-android:emulator_16.0

docker push yeshen/docker-android:emulator_16.0

基础原理

  1. 系统镜像来源
    docker-android 项目 = 用 Docker 容器运行 Google 官方 Android 模拟器。
    容器里跑的是与 Android Studio 完全同源的组件:

    • emulator 二进制(QEMU/KVM 虚拟机)
    • system-images 系统镜像(Android 操作系统镜像)
      两者都来自 Google 官方仓库 dl.google.com/android/repository,
      通过 sdkmanager/avdmanager 下载和管理——和 Android Studio 的
      SDK Manager / Device Manager 是同一个仓库、同一套镜像、同一个 emulator。
  2. 容器内启动链路(CLI 驱动,Python)
    容器启动 -> supervisord -> docker-android start device
    -> avdmanager create avd(创建虚拟设备,首次)
    -> emulator @avd -gpu swiftshader …(启动 QEMU,需宿主机 /dev/kvm)
    -> adb 轮询 sys.boot_completed / launcher 聚焦(就绪检查)
    -> device_status 写 READY -> keep_alive 保活(并检测崩溃自动重启)

  3. 对外连接方式

    • adb:容器暴露 5555 端口,宿主机 adb connect localhost:5555
    • noVNC / webVNC:容器暴露 6080 端口,浏览器访问 http://localhost:6080
      看画面、点按(x11vnc + noVNC 转发)
    • VNC:5900 端口
    • Appium:4723(可选,镜像已裁剪掉 Node/Appium,需要可自行加)
    • 日志共享:9000 端口
  4. 关键版本信息

    • 当前支持:Android 16(API 36),系统镜像 system-images;android-36;google_apis;x86_64
    • emulator:37.1.11(sdkmanager 安装的 channel-0 稳定版)
    • 架构:仅 x86_64;需要宿主虚拟化 /dev/kvm

修改点涉及

  1. 多阶段构建瘦身(docker/emulator)
    构建阶段(build stage):

    • FROM appium/appium:v3.6.0-p0(提供 JDK + cmdline-tools/sdkmanager/avdmanager)
    • 安装 system-images;android-36;google_apis;x86_64 + emulator + platform-tools
    • 不装 platforms / build-tools(运行期用不到)
      运行阶段(runtime stage):
    • FROM ubuntu:24.04
    • 保留:emulator + 系统镜像、adb、JRE(avdmanager 需要)、Python CLI、
      supervisord、Xvfb/openbox/x11vnc/noVNC、socat
    • 砍掉:Node/Appium(约 1GB)、JDK->JRE、build-tools、platforms
    • 结果:7.92GB -> 6.64GB
  2. CLI 版本支持(Android 16 适配,真正改动只有几行)

    • cli/src/device/emulator.py API_LEVEL 字典加 “16.0”: “36”
    • app.sh supported_android_version 加 “16.0”
      api_levels 加 [“16.0”]=36
      IMAGE_NAME 改为 yeshen/docker-android(发布前缀)
    • .github/workflows/release.yml android 矩阵加 “16.0”
    • README.md 镜像表格加 16.0 行
  3. CLI 健壮性 / 兼容性修复(版本无关,但必要)

    • -screen multi-touch:保证 guest 创建触摸设备(X/noVNC 点击可用)
    • -feature -Vulkan:禁用 guest Vulkan,规避 SwiftShader Vulkan SIGSEGV
    • 自愈 keep_alive:检测 emulator 进程退出自动重启(原实现只是死循环)
    • DISABLE_SYSTEM_UI:opt-in 开关(Android 17 需要,16 不需要)
  4. 运行时修复

    • noVNC 响应加 Cache-Control: no-cache(防止浏览器混用新旧 JS 模块)
    • run.sh 启动时清理残留 X lock(docker restart 后 Xvfb 能正常启动)
    • data partition 2G(原 550m 太小导致安装应用空间不足)
    • CMD 改为 exec-form(保证 SIGTERM 优雅退出)
    • cli/setup.py 修复(py_modules 错误导致 CLI 无法 pip 安装)
    • .fehbg 修复(openbox 壁纸 autostart)

修改后编译镜像

  1. 构建命令
   DOCKER_BUILDKIT=1 docker build \
     -t yeshen/docker-android:emulator_16.0 \
     --build-arg DOCKER_ANDROID_VERSION=<ver> \
     --build-arg EMULATOR_ANDROID_VERSION=16.0 \
     --build-arg EMULATOR_API_LEVEL=36 \
     -f docker/emulator .
  1. 产物
    yeshen/docker-android:emulator_16.0 (版本 tag,滚动更新)
    yeshen/docker-android:emulator_16.0_v0.1 (具体发布 tag,与上面同镜像)
    体积:6.64GB(原基于 appium 的镜像 7.92GB)

  2. 启动方式

   docker run -d -p 6080:6080 -p 5555:5555 \
     -e EMULATOR_DEVICE="Pixel 8" -e WEB_VNC=true \
     --device /dev/kvm --name android-16 \
     yeshen/docker-android:emulator_16.0
  • 访问 http://localhost:6080 看画面
  • adb connect localhost:5555 用 adb
  • 无需 DISABLE_SYSTEM_UI,SystemUI 完整可用
  1. 端到端验证结果(Android 16)
    • boot 到 READY 约 50 秒
    • SystemUI 完整运行,无崩溃
    • 网络:wifi 连 AndroidWifi,ping 8.8.8.8 0% 丢失
    • 触摸输入:X 点击 -> guest 触摸事件正常
    • Chrome 压力:多页面导航 + 快速返回 x12,0 崩溃、qemu 存活
    • scrcpy:MediaCodec 编码器可用,可正常投屏
    • 单元测试 34 passed

遇到的问题

【阶段一:先试 Android 17(API 37),因上游 bug 放弃】

  1. SurfaceFlinger 崩溃循环(“模拟器一直重启”)

    • 现象:framework 每 20~90 秒整体重启,进不去稳定 launcher
    • 根因:API 37 系统镜像的 mapper.ranchu gralloc HAL 断言崩溃
      (Assertion failed: !rcEnc->featureInfo()->hasReadColorBufferDma)
      emulator 的 gfxstream host 广播 ReadColorBufferDMA 特性,但 API 37
      的 mapper 预期关闭 -> 版本错配,属上游 bug(API 33~36 均正常)
    • 处理:DISABLE_SYSTEM_UI=true(禁用 SystemUI 后亮度采样触发源消失)
    • 结论:能压住但无法根治;同时连带触摸/投屏/任务快照多个问题
  2. 触摸输入无效

    • 根因:emulator 在 hw.screen=touch 模式下不创建触摸设备(API 37)
    • 处理:-screen multi-touch
  3. 任务快照间歇崩溃

    • 现象:打开/切换 App(尤其 Chrome)时 system_server 崩溃
    • 根因:TaskSnapshotPersister 截取窗口快照走同一 mapper.ranchu 读回路径
    • 处理:无运行时开关(编译期配置),靠自愈兜底
  4. scrcpy / screencap 不可用

    • 根因:编码器与截屏都走 gralloc 读回 -> 同一断言
    • 处理:无法规避,等上游修复
  5. “CPU 494%” 假象

    • ps -o %cpu 是生命周期平均值,被崩溃循环拉高;瞬时采样实际 ~40%

结论:Android 17 无法稳定使用,改为 Android 16(API 33~36 不受影响)。

【阶段二:Android 16 遇到的问题】

  1. SwiftShader Vulkan SIGSEGV(打开 Chrome 偶发整个模拟器段错误)

    • 现象:emulator 进程 exit code -11(SIGSEGV),崩溃前有
      VkInstance application:‘Chromium’ 日志
    • 根因:emulator 37.1.11 的 SwiftShader Vulkan 实现 bug(宿主机侧)
    • 处理:-feature -Vulkan 禁用 guest Vulkan,Chrome 退回 GLES 路径
    • 验证:修复后 Chrome 压力测试 0 次 SIGSEGV
  2. 网络"没网"多为崩溃连锁 + wifi 关联慢

    • 容器本身网络正常;guest wifi 关联需几分钟,且 framework 崩溃会掉线
    • 稳定后 ping 0% 丢失
  3. 容器重建清空 /data

    • 未挂持久卷时 docker rm+run 会清空 /data,应用私有目录需重新初始化,
      曾出现应用写失败(目录缺失),补齐目录即可

【阶段三:通用工程问题】

  1. app.sh last_key bug

    • keys[-2] 用未排序关联数组序导致 :latest tag 指错版本,改 sorted_keys[-2]
  2. noVNC 浏览器报错 supportsWebCodecsH264Decode

    • 服务端文件一致,是浏览器启发式缓存混用新旧 JS 模块
    • 处理:websockify 响应加 Cache-Control: no-cache
  3. 多阶段构建运行时库风险

    • emulator 二进制依赖众多系统库,精简镜像需按 ldd 实测补齐
    • Java 不能省(avdmanager 创建 AVD 需要 JRE)

更多推荐