用Docker搭建Android CI环境

说实话,在没有容器化之前,我们的CI机器上总是装着各种互相冲突的SDK版本。有的项目要API 28,有的要API 30,升级一次模拟器就得重跑所有流水线。后来我用了Docker,才彻底摆脱了这个噩梦。

这一章,我就带你从头搭一个可用于TalkBack测试的Docker容器镜像。

为什么选择Docker?

  • 环境一致性:本地、CI、生产环境使用同一镜像,杜绝「我这能跑」问题。
  • 快速重建:模拟器崩溃?删容器重起,30秒恢复。
  • 并行隔离:多个PR可以用不同容器跑测试,互不干扰。

你想想看,如果每个开发人员都要手动装一遍Android SDK、配置avd、调显卡驱动……那光环境搭建就能耗掉半天。用Docker,一条命令全搞定。

整体流程一览

先看一张架构图,理解我们接下来要做的事情:

Docker Android CI 环境构建流程 1. Ubuntu 基础镜像 2. 安装依赖包 3. 安装 Android SDK + cmdline-tools platform-tools, build-tools, emulator 4. 创建 AVD (API 30) 5. 启动模拟器 (无头 + 无窗口) 6. 运行 TalkBack 测试用例 关键点 • SDK不需要GUI组件 • AVD使用x86_64架构 • 无头模式节省资源 避坑提示 • KVM权限必须有 • 磁盘空间预留 4GB+ • 确保网络可访问dl.google.com

第一步:编写Dockerfile

我个人习惯用Ubuntu 22.04做基础镜像,稳定性最好。

FROM ubuntu:22.04

# 避免交互式安装时卡住
ENV DEBIAN_FRONTEND=noninteractive

# 安装必要工具
RUN apt-get update && apt-get install -y --no-install-recommends \
    wget unzip git openjdk-11-jdk \
    libgtk-3-0 libxtst6 libxrender1 \
    libdbus-glib-1-2 libx11-xcb1 \
    qemu-kvm libvirt-daemon-system \
    && rm -rf /var/lib/apt/lists/*

# 设置ANDROID_HOME
ENV ANDROID_HOME=/opt/android-sdk
ENV ANDROID_SDK_ROOT=$ANDROID_HOME
ENV PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator
注意 KVM 权限:模拟器必须在内核支持虚拟化的环境下运行。CI节点如果是裸机或嵌套虚拟化,需要挂载 /dev/kvm 设备。我曾经在这个坑里卡了两天,后来发现是CI容器没有加 --device /dev/kvm 参数。

第二步:安装Android SDK与模拟器

这里用cmdline-tools来静默安装,干净利落。

# 下载并安装cmdline-tools
RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip \
    && unzip -q commandlinetools-linux-*.zip -d $ANDROID_HOME/cmdline-tools \
    && mv $ANDROID_HOME/cmdline-tools/cmdline-tools $ANDROID_HOME/cmdline-tools/latest \
    && rm commandlinetools-linux-*.zip

# 同意许可并安装SDK组件
RUN yes | sdkmanager --sdk_root=$ANDROID_HOME \
    "platforms;android-30" \
    "build-tools;30.0.3" \
    "platform-tools" \
    "emulator" \
    "system-images;android-30;google_apis;x86_64"

嗯,这里要注意一点:国内下载系统镜像可能很慢。我建议在Dockerfile里加上代理变量,或者在CI配置里预缓存好镜像文件。

第三步:创建AVD

AVD配置文件写成脚本,方便后续重建。

# avd_create.sh
echo "no" | avdmanager create avd -n testAVD \
    -k "system-images;android-30;google_apis;x86_64" \
    -d pixel_4 --force

# 配置模拟器参数
echo "hw.gpu.enabled=no" >> ~/.android/avd/testAVD.avd/config.ini
echo "hw.ramSize=2048" >> ~/.android/avd/testAVD.avd/config.ini
echo "vm.heapSize=512" >> ~/.android/avd/testAVD.avd/config.ini
为什么关掉GPU? 因为无头环境下没有物理GPU,强行启用会导致模拟器闪退。我实测过,纯CPU渲染虽然慢一些,但稳定性非常好。

第四步:无头启动模拟器

核心命令就这一条:

emulator -avd testAVD -no-window -no-audio -no-boot-anim \
    -gpu off -memory 2048 -port 5556
模拟器启动参数说明
参数说明必要性
-no-window无窗口模式,适合CI必选
-no-audio禁用音频,减少资源建议
-no-boot-anim跳过开机动画,加速启动建议
-gpu off关闭GPU加速必选
-port 5556ADB连接端口可选

启动后需要等待系统完全就绪:

adb wait-for-device
# 等待系统启动完成(可能需要60秒)
while [ "$(adb shell getprop sys.boot_completed)" != "1" ]; do
  sleep 5
done
核心技巧:在CI流水线中,建议把模拟器启动放到后台(&),然后配合 timeout 命令,防止测试卡死超过规定时间。

第五步:集成到CI流水线

拿GitLab CI举例,.gitlab-ci.yml的关键部分:

talkback_test:
  image: my-registry/android-talkback:latest
  services:
    - docker:dind
  variables:
    DOCKER_HOST: tcp://docker:2375
  before_script:
    - echo "启动模拟器(后台)..."
    - emulator -avd testAVD -no-window -no-audio -no-boot-anim -gpu off &
    - adb wait-for-device
    - while [ "$(adb shell getprop sys.boot_completed)" != "1" ]; do sleep 5; done
    - echo "模拟器就绪"
  script:
    - ./gradlew :app:connectedCheck  # 运行TalkBack测试
  after_script:
    - adb emu kill  # 关闭模拟器
  artifacts:
    paths:
      - app/build/reports/

哦对了,docker:dind 是为了支持Docker in Docker,如果你用Kubernetes Runner可能需要额外配置挂载 /dev/kvm。

常见坑与调优

  • 模拟器启动超时:我遇到过CI节点资源不足导致启动时间超过2分钟。解决方案:增大CI超时时间,同时调低模拟器内存(如设为1024M)。
  • AVD被锁定:如果前一次构建异常退出,下次启动会报错。可以在 before_script 里先执行 adb emu kill 2>/dev/null || true 清理残留。
  • TalkBack测试本身需要无障碍服务开启:在测试用例的Setup里用 UiAutomation 开启即可,不用在模拟器里手动设置。

最终镜像大小与优化

完整镜像大概 4.5GB,其中系统镜像占了 1.8GB。如果你在意存储,可以去掉无用的语言包和字体。我自己常用的优化方法:

RUN rm -rf /opt/android-sdk/system-images/android-30/google_apis/x86_64/system.img.checksum \
    && rm -rf /opt/android-sdk/emulator/resources \
    && apt-get clean

这样能省出 300MB 空间。