CircleCIでAndroidエミュレータが起動しなくなった場合の対処

AndroidアプリのCI環境にCircleCIを利用しているのですが、3/12の環境アップデート(CircleCIで用意している実行環境のアップデート)以降エミュレータが起動しなくなる問題が発生しました。

今日はその対処についてメモです。

問題

CircleCIのAndroid向けドキュメントには追加のSDKインストール部分でx86エミュレーターを使う例が書いてあります。

install-dependencies.sh(ドキュメントより該当部分のみ抜粋)

12:  
13:  echo y | android update sdk -u -a -t sys-img-x86-android-18 && ## <- ココ!!  
14:  

その後wait.shスクリプトによってエミュレータのブートステータスが終了になったことを確認して、 ユニットテストを実行する流れになっているのですが、3/12以降 x86エミュレータは以下のエラーを吐いて起動に失敗します。

ubuntu@box26:~$ $ANDROID_HOME/tools/emulator -avd test-target -no-skin -no-audio -no-window -no-boot-anim  
emulator: ERROR: x86 emulation currently requires hardware acceleration!  
Please ensure KVM is properly installed and usable.  
CPU acceleration status: KVM is not installed on this machine (/dev/kvm is missing).  

(ちなみに↑のエラーは、SSHアクセスを有効にしてジョブを実行し直接コマンドを打つと表示されます)

エラーを吐いてジョブごと失敗してくれればいいんですが、emulatorコマンド自体はステータス0で終了しているらしくジョブは続行されます。 wait.sh自体は5秒毎にエミュレータの状況をチェックするスクリプトなので、エミュレータが起動していないと永遠に回り続けるわけです。

で結論なにが問題かというと、エミュレータの起動を待ち続けてジョブが終了しないままになるんですね。 お金払って複数コンテナ実行できるようにしてると、Androidのジョブが食いつぶして他のジョブが実行されないという状態になるわけです。

迷惑極まりない問題です。(悪いのはCircleCIというよりemulatorコマンド)

というわけでグチ吐き終わったので対処方法を書きます。

対策

解決策はズバリ

ARM EABI v7aエミュレータを利用する

これにつきます。上記の様にandroid-18のエミュレータを使いたいなら、

13:  echo y | android update sdk -u -a -t sys-img-armeabi-v7a-android-18  

とします。利用可能なシステムイメージの種類はandroid list sdk --all --extendedで表示されるのでローカルで確認してコピペすればOKです。 ただここだけ書き換えただけだとx86システムイメージのキャッシュが残ってるせいで、エミュレータの作成に失敗するので、エミュレータの作成コマンドでARMを指定するか、 (APIキー発行した上で)CircleCIのREST APIからキャッシュを削除して実行するようにします。

ちなみにシステムイメージでARMを指定する場合はこんな感じ。

15: echo n | android create avd -n testing -f -t android-18 -b armeabi-v7a &&  

新たなる問題

x86エミュレータをARM v7aに置き換えてめでたし...となればいいのですが自分の環境では新たに以下のエラーでジョブが失敗する状態に陥りました。

Warning: The build VMs have a memory limit of 4G.  
Your build hit this limit on one or more containers,  
and your build results are likely invalid.  

メモリ使いすぎってことですね。 ウチの場合エミュレータでテストが終わった後に配布用のAPKを再ビルドしてアップロードしているのですが、その時にメモリが足りなくなってるみたいでした。

"エミュレータのシステムイメージ変えただけでそんなこと起きるのか?"

って気もするんですが、実際なってしまっているので仕方がない。

というわけで、テストの実行後にエミュレータを終了するようなコマンドを入れるようにしました. CircleCIのドキュメントを元にすると以下の様な

test:  
    override:
        - $ANDROID_HOME/tools/emulator -avd testing -no-window -no-audio:
          background: true
          parallel: true
        - ./wait.sh:
          parallel: true
        - ./gradlew connectedAndroidTest
    post:
        - $ANDROID_HOME/platform-tools/adb -s emulator-5554 emu kill

post:ディレクティブはテスト終了後に追加の処理を行う部分です。 ここでadbコマンドをつかいエミュレータを終了させています。(emulator-5554はエミュレータのデフォルトの名前)

これでようやくグリーンが戻ってきました。

もし最近CircleCIの調子が悪いなという人がいたらお試しください。

おまけ

本当はemulatorコマンドが失敗してくれないのが悪いんですが、 文句いっても変わらないので一定時間以内にエミュレータの起動を確認できなかったらジョブ自体失敗させるようにwait.shを書き換えることにしました。

今使っているwait.shはこんなかんじです。

#!/bin/bash

export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH"

while true; do

    # エミュレータを待って10分以上経過していたら異常として終了する
    if [ $SECONDS -ge 600 ]; then
        echo "I can't wait for emulator any more!!"
        exit 1;
    fi

    BOOTUP=$(adb shell getprop init.svc.bootanim | grep -oe  '[a-z]\+')
    if [[ "$BOOTUP" = "stopped" ]]; then
        break
    fi

    echo "Got: '$BOOTUP', waiting for 'stopped'"
    sleep 5
done