返回 Skill 列表
extension
分类: 开发与工程无需 API Key

build-android-app

在代码更改后,在模拟器上构建、安装并启动Android应用程序。自动检测Gradle包装器、构建变体、应用程序ID和模拟器。在进行代码更改以验证编译时使用,当用户说“构建应用程序”、“运行应用程序”、“在模拟器上启动”,或工作区规则要求更改后构建时。

person作者: jakexiaohubgithub

Build Android App on Emulator

After making code changes, build and launch the app on an Android Emulator to verify compilation.

Step 1: Detect Project Settings

Run these commands to discover project configuration. Cache the results for the session.

Find Gradle wrapper

if [ -f "./gradlew" ]; then
  GRADLE_CMD="./gradlew"
elif [ -f "../gradlew" ]; then
  GRADLE_CMD="../gradlew"
else
  echo "ERROR: No gradlew found. Ensure you are in the project root."
  exit 1
fi
echo "GRADLE_CMD: $GRADLE_CMD"

Find build variant

# List available build variants from the app module
$GRADLE_CMD :app:tasks --group=build 2>/dev/null | grep -oP 'assemble\K[A-Z][a-zA-Z]+' | sort -u

Pick the debug variant for local development. Prefer internalDebug if available (common for apps with internal/production flavors), otherwise fall back to debug.

Store the result as BUILD_VARIANT (e.g. internalDebug). Derive the Gradle task as assemble${BUILD_VARIANT} (e.g. assembleInternalDebug).

Find application ID

$GRADLE_CMD :app:properties 2>/dev/null | grep 'applicationId' | head -1 | awk '{print $NF}'

If that doesn't return results, parse app/build.gradle or app/build.gradle.kts:

grep -E 'applicationId\s' app/build.gradle* 2>/dev/null | head -1 | grep -oP '"[^"]+"' | tr -d '"'

For flavored builds, the application ID may have a suffix. Check the flavor block for applicationIdSuffix to get the full ID.

Store the result as APP_ID.

Find emulator (project-branch naming convention)

Emulators are named {project}_{git_branch}, where project is derived from the git remote URL and git_branch is the current git branch. Underscores replace characters that are invalid in AVD names. This ensures each project+branch combination has its own isolated emulator.

PROJECT=$(git remote get-url origin | sed 's/.*\///' | sed 's/\.git$//')
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# AVD names only allow alphanumeric, hyphens, underscores, and dots
AVD_NAME=$(echo "${PROJECT}_${GIT_BRANCH}" | sed 's/[^a-zA-Z0-9._-]/_/g')
echo "AVD_NAME: $AVD_NAME"

Check if the emulator exists. If not, create it using a recent Pixel device and the latest installed system image:

AVD_EXISTS=$($ANDROID_HOME/emulator/emulator -list-avds 2>/dev/null | grep -x "$AVD_NAME")

if [ -z "$AVD_EXISTS" ]; then
  # Find latest installed system image (prefer google_apis x86_64)
  SYSTEM_IMAGE=$(sdkmanager --list_installed 2>/dev/null | grep 'system-images' | grep 'google_apis' | grep -v 'google_apis_playstore' | tail -1 | awk '{print $1}')

  if [ -z "$SYSTEM_IMAGE" ]; then
    # Fall back to any available system image
    SYSTEM_IMAGE=$(sdkmanager --list_installed 2>/dev/null | grep 'system-images' | tail -1 | awk '{print $1}')
  fi

  if [ -z "$SYSTEM_IMAGE" ]; then
    echo "ERROR: No system images installed. Install one via Android Studio SDK Manager or:"
    echo "  sdkmanager 'system-images;android-35;google_apis;x86_64'"
    exit 1
  fi

  echo "Creating AVD '$AVD_NAME' with system image $SYSTEM_IMAGE"
  echo "no" | avdmanager create avd -n "$AVD_NAME" -k "$SYSTEM_IMAGE" -d "pixel_6"
  echo "Created: $AVD_NAME"
else
  echo "AVD '$AVD_NAME' already exists"
fi

Boot emulator

Multiple emulators can run simultaneously (one per worktree/branch). NEVER shut down other running emulators — they belong to other worktrees.

Each running emulator gets a unique serial (emulator-5554, emulator-5556, etc.). To find this AVD's serial, query the AVD name property on each running emulator.

# Snapshot running emulators BEFORE boot so we can identify the new one
SERIALS_BEFORE=$(adb devices | grep 'emulator-' | awk '{print $1}')

# Check if this AVD is already running
DEVICE_SERIAL=""
for SERIAL in $SERIALS_BEFORE; do
  AVD_ON_SERIAL=$(adb -s "$SERIAL" emu avd name 2>/dev/null | head -1 | tr -d '\r')
  if [ "$AVD_ON_SERIAL" = "$AVD_NAME" ]; then
    DEVICE_SERIAL="$SERIAL"
    echo "AVD '$AVD_NAME' already running on $DEVICE_SERIAL"
    break
  fi
done

if [ -z "$DEVICE_SERIAL" ]; then
  # Boot this AVD alongside any others already running
  nohup $ANDROID_HOME/emulator/emulator -avd "$AVD_NAME" -no-snapshot-load &>/dev/null &
  echo "Booting emulator '$AVD_NAME'..."

  # Wait for a NEW emulator serial to appear
  while true; do
    sleep 2
    for SERIAL in $(adb devices | grep 'emulator-' | awk '{print $1}'); do
      if ! echo "$SERIALS_BEFORE" | grep -qx "$SERIAL"; then
        DEVICE_SERIAL="$SERIAL"
        break 2
      fi
    done
  done
  echo "New emulator appeared on $DEVICE_SERIAL"

  # Wait for boot to complete on THIS specific emulator
  while [ "$(adb -s "$DEVICE_SERIAL" shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do
    sleep 2
  done
  echo "Emulator '$AVD_NAME' booted on $DEVICE_SERIAL"
fi

If a physical device is connected and preferred, use its serial instead. List devices with adb devices.

Step 2: Build

# Stop the running app first — hot-swap may conflict with a fresh install
adb -s "$DEVICE_SERIAL" shell am force-stop "$APP_ID" 2>/dev/null

$GRADLE_CMD :app:assemble${BUILD_VARIANT}

If build fails, fix compilation errors before proceeding.

For faster incremental builds, Gradle's build cache is enabled by default. If you suspect a stale cache, add --no-build-cache to the build command.

Step 3: Install and Launch

# Find the APK — path depends on flavor/build type
APK_PATH=$(find app/build/outputs/apk -name "*.apk" -path "*/${BUILD_VARIANT}/*" 2>/dev/null | head -1)

if [ -z "$APK_PATH" ]; then
  # Broader search if variant directory structure differs
  APK_PATH=$(find app/build/outputs/apk -name "*.apk" | head -1)
fi

echo "Installing: $APK_PATH"
adb -s "$DEVICE_SERIAL" install -r "$APK_PATH"

# Find the launcher activity
LAUNCHER_ACTIVITY=$(aapt dump badging "$APK_PATH" 2>/dev/null | grep 'launchable-activity' | grep -oP "name='[^']+'" | head -1 | grep -oP "'[^']+'" | tr -d "'")

if [ -z "$LAUNCHER_ACTIVITY" ]; then
  # Fall back: launch via monkey (opens the default launcher activity)
  adb -s "$DEVICE_SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1
else
  adb -s "$DEVICE_SERIAL" shell am start -n "$APP_ID/$LAUNCHER_ACTIVITY"
fi

Workspace Rule Override

If the project has a .cursor/rules/build-and-run-verification.mdc workspace rule with hardcoded values (build variant, application ID, Gradle task), prefer those values over auto-detection for those specific settings. The workspace rule is authoritative for that project. The emulator naming convention ({project}_{git_branch}, where project comes from the git remote) always applies regardless of workspace rule.

Troubleshooting

  • No emulator found: Run $ANDROID_HOME/emulator/emulator -list-avds and check naming. Ensure ANDROID_HOME or ANDROID_SDK_ROOT is set.
  • No system images: Install via sdkmanager 'system-images;android-35;google_apis;x86_64' or use Android Studio SDK Manager.
  • Build fails with SDK errors: Ensure local.properties has the correct sdk.dir path or ANDROID_HOME is set.
  • APK not found: Check app/build/outputs/apk/ for the correct variant directory structure.
  • App won't launch: Verify APP_ID matches the flavor's application ID. Check adb logcat for crash logs.
  • Gradle daemon issues: Run $GRADLE_CMD --stop to kill stale daemons, then rebuild.
  • Emulator won't boot: Try emulator -avd "$AVD_NAME" -wipe-data to reset the emulator, or delete and recreate the AVD.
  • Multiple emulators: Each worktree runs its own emulator in parallel. NEVER shut down emulators you didn't start — they belong to other worktrees. Always use -s $DEVICE_SERIAL on all adb commands to target the correct emulator. To identify which AVD is on which serial: adb -s <serial> emu avd name.