mcpField
A
android-emulator

android-emulator

github.com/chunkytofustudiosテスト・品質591 回インストール26 週間前に更新
リポジトリ

android-emulator について

Android エミュレーターで Flutter アプリを起動、確認、テストするための Bash ヘルパーです。開発者やエージェント向けに、`adb` と qemu コンソールを組み合わせ、アクセシビリティツリーの確認やマルチタッチ操作を行えます。

主な機能

  1. 01AVD の起動と終了
  2. 02Flutter アプリのビルドと実行
  3. 03アクセシビリティノードの確認
  4. 04ラベルまたは座標による操作
  5. 05スクリーンショットとログの取得

活用シーン

  1. 01エミュレーターでアプリの問題を再現
  2. 02Flutter アプリのスモークテスト
  3. 03画面ラベルを使ったナビゲーション
  4. 04画面状態や実行ログの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/chunkytofustudios/flutter-skills --skill android-emulator

インストール後、Claude Code で/android-emulatorを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

scripts/
emu.sh
tests/
dispatch.bats
help.bats
helpers.bash
parsers.bats
ui.bats
tests/fixtures/
ui-dump.xml
tests/stubs/
adb
convert
emulator
flutter
fvm
nc
pkill
sips
sleep
tail

詳細

Android エミュレーター(Flutter)

adb と qemu エミュレーターコンソールをラップする bash ヘルパーです。Android エミュレーター上で実行中の Flutter アプリを AI エージェントが確認(スクリーンショット、アクセシビリティツリー)し、操作(タップ、長押し、スワイプ、ピンチ)できるようにします。qemu には macOS アプリバンドルがないため、コンピューター操作 API ではエミュレーターウィンドウにアクセスできません。また、adb shell input はシングルタッチのみです。このスクリプトは、その両方の問題を解決します。

こんなときに使います

ユーザーが Android 上で Flutter アプリを起動、デバッグ、スモークテスト、QA、またはスクリーンショット撮影したい場合に使います。座標を推測したり、DevTools から手動でスクリーンショットを撮ったり、ユーザーに flutter run を自分で実行してもらったりする前に、このスキルを使ってください。

セットアップ

このスクリプトは、動作する Android SDK のインストール(adb と emulator が $PATH 上、または ~/Library/Android/sdk/ / ~/Android/Sdk/ にあること)、少なくとも 1 つの AVD の作成、および Flutter(または fvm)のインストールを前提とします。macOS 専用ユーティリティとして、sips(スクリーンショットのリサイズに使用。macOS に付属)を使います。Linux に移植する場合は、ImageMagick の convert に置き換えてください。

スクリプトは、このスキルを基準にした相対パス scripts/emu.sh にあります。スキルをプロジェクトに組み込んだら(README.md を参照)、エージェントはこの相対パスでスクリプトを呼び出します。

クイックスタート(コールドブートから操作可能な状態まで)

scripts/emu.sh boot        # start an AVD (cold boot), idempotent
scripts/emu.sh run         # flutter build & install in background (~30–60s first time)
scripts/emu.sh wait-run    # blocks until attached or errors (180s timeout)
scripts/emu.sh health      # sanity check
scripts/emu.sh ui-list     # see what's on screen — start here, not screenshot

run はプロジェクトのルートを自動検出し(pubspec.yaml を探して親ディレクトリをたどります)、プロジェクトで指定されていれば fvm flutter を使います。run には必ず wait-run を組み合わせ、sleep は使わないでください。デーモンの stdin はなくなっているため、ビルド完了を判断する手掛かりはタイミングだけです。

コマンド

エミュレーターのライフサイクル
コマンド動作
bootAVD をコールドブート(-no-snapshot-load)で起動し、sys.boot_completed まで待機します。冪等です。デバイスがすでにオンラインなら、直ちに戻ります。AVD は emulator -list-avds が最初に表示するものがデフォルトです。ANDROID_EMU_AVD で固定できます。
exitqemu コンソール(kill)経由で、実行中のエミュレーターを終了します。
health接続状態、AVD 名、解像度、フォアグラウンドアプリ、検出されたプロジェクトルート、検出されたパッケージ、Flutter ログのサイズを一度に表示します。状況を把握するため、まずこれを実行してください。デバイスが接続されていない場合は、0 以外の終了コードで終了します。
devices / size / foregroundadb devices / wm size / フォーカス中のアクティビティをそのまま表示します。
app-running対象パッケージがフォーカス中のアプリなら 0、そうでなければ 1 で終了します。スクリプト内で使用します。
アプリの制御
コマンド動作
launchインストール済みの APK を monkey で起動します(再ビルドはしません)。
stopam force-stop でアプリを停止します。
runバックグラウンドで flutter run -d <device> を起動します。ログは /tmp/android-emu-flutter-<id>.log(ANDROID_EMU_TMP_ID をキーにした呼び出しごとのログ)に記録されます。デーモンの pid は <log>.pid に書き込みます。.fvm/ がある場合は fvm flutter を使います。
wait-runログに Flutter run key commands. が表示されるか、ビルドが失敗するまで待機します。タイムアウトは 180 秒(ANDROID_EMU_WAIT_SECS)です。run には必ず wait-run を組み合わせてください。sleep は使わないでください。
kill-runFlutter デーモンを終了し(<log>.pid を使用。見つからない場合は pkill -f flutter_tools.snapshot にフォールバック)、アプリを強制停止します。
log [-f] [N]呼び出しごとのログの末尾 N 行(デフォルトは 50 行)を表示します。-f を付けるとライブ出力を追跡します。grep でフィルタリングできます。たとえば、警告と重大なログを表示するには log 500 | grep -E 'I/flutter.*\[(W|S)\]' を使います(logcat はすべての print() を I/flutter ( PID): で囲むため、レベルタグは行頭にはありません)。
入力 — 座標ベース(すべての座標はスクリーンショットのピクセル単位、幅360の領域)
コマンド説明
screenshot画面をキャプチャして /tmp/android-emu-shot-<id>.jpg に保存します(幅360px、JPEG q85)。正確なパスは標準出力に表示されます。現在の状態を確認するには、そのパスを Read してください。呼び出しごとに異なるパスを使用するため、同時に実行しても互いのファイルを上書きしません。
tap X Y1回タップします。
hold X Y [MS]長押しします(既定値は800ms)。
swipe X1 Y1 X2 Y2 [MS]スワイプします(既定値は300ms)。msを短くするとフリックになります。
pinch out|in [CX CY SG EG]2本指でピンチします。out は拡大します。既定値:中心 (180,367)、開始間隔67、終了間隔333。ピンチ操作はqemuコンソール経由で実行します。adb input ではマルチタッチを実行できません。
入力 — ラベルベース(推奨 — 座標を推測する必要はありません)

これらのコマンドは、uiautomator dump で Android のアクセシビリティツリーを読み取り、指定した LABEL と text、content-desc、resource-id、または hint が一致するノードに対して操作します。完全一致が部分一致より優先されます。hint は空の TextField に対応します(Flutter の InputDecoration.labelText はここに表示されます)。

コマンド説明
ui-list画面上にあるラベル付きノードの、人が読める形式の一覧を表示します。スクリーンショット上の中心座標、タップ/長押し/スクロールの可否、ラベルが含まれます。まずこれを実行して、操作可能な要素を確認してください。
ui-find LABEL最初に一致した要素のデバイスpx境界と、スクリーンショットpxでの中心座標を表示します。デバッグに役立ちます。
ui-dump生の uiautomator XML を表示します。ui-list に目的のノードが表示されない場合(ラベルのない親ノードなど)に便利です。
tap-label LABEL最初に一致したノードの中心をタップします。
hold-label LABEL [MS]最初に一致したノードの中心を長押しします。

座標はスクリーンショット上の座標です。 ui-list の cx cy 列は、スクリーンショットJPEGおよび tap X Y と同じ幅360の座標系を使用しているため、拡大縮小せずに両者を照合できます。

ui-list の出力例:

 cx   cy  flags          label
 27   70  tap            'Settings'
137  141  tap            'Get started'
 47  254  tap            'New project'
 45  760  tap            'Home\nTab 1 of 4'
135  760  tap            'Search\nTab 2 of 4'
225  760  tap            'Library\nTab 3 of 4'
315  760  tap            'Profile\nTab 4 of 4'

次に tap-label "Search" を実行します。部分一致で十分なので、\nTab 2 of 4 の接尾辞を含める必要はありません。

重要度によるログのフィルタリング

アプリが [F]/[I]/[W]/[S] のプレフィックスを使う package:logging を使用している場合は、log の出力を grep にパイプします。Logcat はすべての print() を I/flutter ( PID): で囲むため、重要度タグは行頭にありません。ラッパーの後にあるタグに一致させてください。

scripts/emu.sh log 500 | grep -E 'I/flutter.*\[(W|S)\]'    # warnings + severe
scripts/emu.sh log -f  | grep --line-buffered -E 'I/flutter.*\[S\]'   # follow severe

正規表現は、アプリが出力する方式に合わせて変更してください(例:\[(WARNING|SEVERE)\])。

スクリーンショットと ui-list の使い分け

スクリーンショットは1枚あたり約30~60 KBで、会話のコンテキスト内ですぐに容量が増える一方、ui-list の出力は約2 KBです。通常は ui-list を使ってください。 画面上のラベルが変わっていれば、新しい画面に移動しています。これは多くの画面遷移で確認が必要なことです。

次の場合にのみ screenshot を使ってください。

  1. 内容が本質的に視覚的な場合 — CustomPainter、画像やカメラのプレビュー、カラースウォッチ、サムネイルなど、ウィジェットではなくピクセルとして描画されるもの。
  2. ラベルでは状態を区別できない場合 — 同じ画面で、アクセシビリティツリーに反映されない状態変化がある場合(例:スライダーをドラッグして値を変更した場合や、同じテキストを再利用するチップを切り替えた場合)。
  3. tap-label の失敗をデバッグする場合 — ラベルが一致しない、または誤った要素をタップする場合は、画面の内容を把握するために ui-dump を読むよりスクリーンショットを撮るほうが速いです。

目安:タップした後は、まず ui-list を確認してください。質問への答えが得られない場合にのみ、スクリーンショットを撮ってください。

ウィジェットを操作可能にする(Flutter Semantics)

uiautomator dump は Android のアクセシビリティツリーを読み取ります。Flutter アプリでは、アプリのセマンティクスツリーが公開されていない限り、そのツリーは空です。方法は 2 つあります。

  1. デバッグビルドでセマンティクスを有効にする — main() の先頭付近に追加します。

    import 'package:flutter/foundation.dart';
    import 'package:flutter/semantics.dart';
    
    void main() {
      if (kDebugMode) {
        SemanticsBinding.instance.ensureSemantics();
      }
      runApp(const MyApp());
    }
    

Material ウィジェット(BottomNavigationBar、tooltip: を指定した IconButton、TextButton、TextField など)はセマンティクスを自動的に出力します。ナビゲーションバー、アプリバーのボタン、フォームフィールドなどを、ウィジェットごとにラップする必要はありません。

  1. AVD でアクセシビリティサービスを有効にする — TalkBack などのサービスによって、プラットフォームにツリーを実体化させます。やや大がかりな方法です。(1)のほうがすっきりしています。

ui-list が (no labelled nodes found — is Flutter semantics enabled?) と出力する場合は、どちらも有効になっていません。

操作可能にする必要がある、アイコンのみのカスタムウィジェットの場合:

Semantics(
  identifier: 'some_stable_id',   // optional, locale-invariant
  label: 'Human readable name',
  child: ...,
)

identifier はロケールによって変わらないため、テストではこちらが推奨されます。label は支援技術(および tap-label)が読み上げる値です。

注意点

  • adb shell input swipe X Y X Y MS は長押しではありません。 DOWN/UP を非常に短い間隔で送るため、Flutter の LongPressGestureRecognizer はキャンセルされたタップとして扱い、onLongPress を呼び出しません。hold(または hold-label)を使ってください。これらは motionevent DOWN を送信してから待機し、その後 motionevent UP を送信します。
  • ピンチ操作は adb 経由では実行できません。 adb shell input はシングルタッチのみです。sendevent /dev/input/event1 によるマルチタッチには、root 権限(本番用 AVD では adb root が拒否されます)か、shell ユーザーにはない SELinux 権限が必要です。pinch コマンドは qemu のホスト側コンソール(localhost:5554。認証トークンは ~/.emulator_console_auth_token にあります)にフォールバックし、イベントを仮想入力デバイスに直接書き込みます。
  • セマンティクスがない場合、アプリは不透明な矩形 1 つとして扱われます。 リリースビルド(または ensureSemantics() を呼び出していないデバッグビルド)で ui-list を実行しても、有用な情報は返されません。座標を推測するのではなく、アプリ側で修正してください。上のセクションを参照してください。
  • ui-list に表示されるのはラベル付きノードのみです。 ラベルのない親ノードやラッパーは表示されません。想定したノードが見つからない場合は、ui-dump で生の XML を確認してください。
  • ホットリスタートとフルビルドの違い。 run は毎回、新しい flutter run を起動します。コード変更後にホットリロードしたい場合は、デーモンの stdin に r\n を送信します。ただし、このスクリプトはデーモンをバックグラウンドで実行するため(stdin は失われます)、代わりに kill-run と run を再度実行する想定です。より短いサイクルで作業する場合は、自分のターミナルで flutter run を実行し、ここでは入力/スクリーンショット用のコマンドだけを使ってください。
  • 初回起動時のダイアログ。 多くのアプリでは初回起動時に、スプラッシュ画面、オンボーディング画面、アップセル画面が表示されます。ui-list で何が操作を妨げているか確認してから、tap-label で閉じてください(例:tap-label "Close"、tap-label "Skip")。固定時間のスリープには頼らず、ui-list に想定した画面が表示されるまで待ってください。

典型的なワークフロー

何もない状態からコールドスタートする
scripts/emu.sh boot
scripts/emu.sh run
scripts/emu.sh wait-run
scripts/emu.sh health
scripts/emu.sh ui-list
ユーザーが見ている問題をデバッグする
  1. scripts/emu.sh ui-list — 現在画面に表示されているものを確認します。
  2. ユーザーの操作手順を再現します。各ステップで tap-label "…" を実行します。
  3. 問題が疑われる操作の後、もう一度 ui-list を実行します。期待どおりに見える場合は、視覚的な不具合があるときだけスクリーンショットを撮ります。
  4. scripts/emu.sh log 100 — flutter ログの末尾を確認し、例外やアサーションを見つけます。
デバッグセッションを適切に停止する
scripts/emu.sh kill-run

エージェント内から TaskStop を実行しないでください。バックグラウンドタスクはすでに終了している可能性があり、いずれの場合も、強制停止するまではデバイス上でアプリが実行され続けます。

完全に終了する
scripts/emu.sh kill-run    # stop flutter + app
scripts/emu.sh exit        # shut down the emulator

スクリーンショットが幅360pxのJPEGである理由

PixelクラスのAVDでフル解像度のPNG(1080×2424)は600 KB~1.5 MBになり、会話のコンテキストを大きく圧迫します。PNGのリサイズだけではほとんど効果がありません。エンコーダーの圧縮が強くないためです。幅360pxで品質85のJPEGなら、UIラベルや小さな文字が読める状態を保ちながら、約25~60 KBになります。幅360pxの画像は、tap/hold/swipe/pinchの基準入力空間です。スクリプトがデバイス側のスケーリングを処理するため、呼び出し側から見た解像度は変わりません。ラベルが読みにくい場合(92を試してください)、またはファイルを小さくしたい場合(70を試してください)は、ANDROID_EMU_SHOT_QUALITYでJPEG品質を変更できます。

同時利用

呼び出しごとの一時パス(ANDROID_EMU_TMP_IDを付加。デフォルトは$$)に、スクリーンショット、UIダンプ、Flutterデーモンのログとpidfileを保存します。そのため、異なるエミュレーターデバイスを操作する2つのエージェントが互いのファイルを上書きすることはありません。run → wait-run → log → kill-runの一連の処理では、ANDROID_EMU_DEVICEとANDROID_EMU_TMP_IDを固定してください。screenshotは出力パスを表示するため、そのパスを読み取り、ハードコードしないでください。

1台のエミュレーターで実行できるFlutterデーモンは1つだけです。そのため、同じデバイスを使う2つのエージェントは調整する必要があります(通常、一方がrun/kill-runを担当し、両方が自由にlog/screenshot/ui-listを実行します)。共有状態には、エミュレーターの入力ストリーム(タップを同時に行うと交錯します)とデバイスサイズのキャッシュがあります。

自動検出

対象方法
プロジェクトルート$PWDから上の階層をたどり、pubspec.yamlを探します。ANDROID_EMU_PROJECT_ROOTで変更できます。
applicationIdandroid/app/build.gradleまたはandroid/app/build.gradle.ktsから解析します(最初のapplicationId "…"行)。ANDROID_EMU_PKGで変更できます。
AVDANDROID_EMU_AVDが設定されていない場合、emulator -list-avdsの最初の項目を使います。複数のAVDがある場合は、明示的に1つを指定してください。
flutter CLIプロジェクトに.fvm/ディレクトリがあり、fvmがPATHにある場合はfvm flutterを使い、それ以外の場合はflutterを使います。ANDROID_EMU_FLUTTER_CMDで変更できます。

scripts/emu.sh healthを実行すると、解決された値が表示されます。問題があるように見える場合は、まずこれを実行してください。

環境変数による上書き

変数デフォルト用途
ANDROID_EMU_DEVICEemulator-5554adbデバイスのシリアル番号
ANDROID_EMU_AVD一覧の最初の項目起動するAVD
ANDROID_EMU_PKGgradleから解析applicationId
ANDROID_EMU_PROJECT_ROOT$PWDから上の階層をたどって検出Flutterプロジェクトのルート
ANDROID_EMU_FLUTTER_CMDflutterまたはfvm flutterflutterコマンドの上書き
ANDROID_EMU_CONSOLE_PORT5554qemuコンソールのポート
ANDROID_EMU_SHOT_QUALITY85JPEG品質(1~100)
ANDROID_EMU_WAIT_SECS180wait-runのタイムアウト
ANDROID_EMU_BOOT_SECS180bootのタイムアウト
ANDROID_EMU_TMP_ID$$(スクリプトのPID)一時ファイルの接尾辞。同じエミュレーターを複数のエージェントで共有する場合は、呼び出し間で固定してください

コメント