auto-update-daily-20260202

Debug Workflow

Debug, test, and profile Android applications using adb, emulator, logcat, and testing frameworks.

ADB (Android Debug Bridge)

Basic Commands

# List connected devices
adb devices

# Connect to specific device (if multiple)
adb -s <device-id> <command>

# Target emulator specifically
adb -e <command>

# Target physical device specifically
adb -d <command>

# Check device info
adb shell getprop ro.product.model
adb shell getprop ro.build.version.release

App Management

# Install APK
adb install app-debug.apk

# Reinstall (keep data)
adb install -r app-debug.apk

# Uninstall app
adb uninstall com.example.myapp

# Clear app data
adb shell pm clear com.example.myapp

# List installed packages
adb shell pm list packages
adb shell pm list packages | grep myapp

# Get app info
adb shell dumpsys package com.example.myapp

Start/Stop App

# Start app activity
adb shell am start -n com.example.myapp/.MainActivity

# Start with intent data
adb shell am start -n com.example.myapp/.MainActivity -d "https://example.com"

# Stop (force close) app
adb shell am force-stop com.example.myapp

# Kill app process
adb shell am kill com.example.myapp

File Operations

# Push file to device
adb push local-file.txt /sdcard/

# Pull file from device
adb pull /sdcard/file.txt ./

# List files
adb shell ls /sdcard/

# View file content
adb shell cat /sdcard/file.txt

# Remove file
adb shell rm /sdcard/file.txt

Screenshots and Screen Recording

# Take screenshot
adb shell screencap /sdcard/screen.png
adb pull /sdcard/screen.png
adb shell rm /sdcard/screen.png

# Or in one command
adb exec-out screencap -p > screen.png

# Record screen (max 3 minutes)
adb shell screenrecord /sdcard/demo.mp4
# Press Ctrl+C to stop
adb pull /sdcard/demo.mp4
adb shell rm /sdcard/demo.mp4

System Information

# CPU info
adb shell cat /proc/cpuinfo

# Memory info
adb shell cat /proc/meminfo

# Battery status
adb shell dumpsys battery

# Display info
adb shell dumpsys display

# Running processes
adb shell ps

# App processes
adb shell ps | grep com.example.myapp

Network

# Check network connectivity
adb shell ping -c 4 google.com

# View network interfaces
adb shell ip addr

# Port forwarding (device port to local port)
adb forward tcp:8080 tcp:8080

# Reverse port forwarding (local port to device)
adb reverse tcp:9000 tcp:9000

Logcat

View Logs

# All logs (verbose)
adb logcat

# Clear logs first, then follow
adb logcat -c && adb logcat

# Filter by app package
adb logcat | grep "com.example.myapp"

# Filter by tag
adb logcat -s MyTag

# Filter by priority (V=Verbose, D=Debug, I=Info, W=Warn, E=Error, F=Fatal)
adb logcat *:E  # Only errors

# Multiple filters
adb logcat MyTag:D *:E  # MyTag at Debug level, everything else at Error

Log Priorities

V - Verbose (lowest priority)
D - Debug
I - Info
W - Warning
E - Error
F - Fatal
S - Silent (highest priority, nothing printed)

Format Options

# Brief format (default)
adb logcat -v brief

# Time format (with timestamps)
adb logcat -v time

# Threadtime (time + PID + TID)
adb logcat -v threadtime

# Long format (all metadata)
adb logcat -v long

Save Logs to File

# Save all logs
adb logcat > logs.txt

# Save with timestamp in filename
adb logcat > "logs-$(date +%Y%m%d-%H%M%S).txt"

# Save for 10 seconds then stop
timeout 10s adb logcat > logs.txt

Filter Crash Logs

# Show only fatal errors
adb logcat *:F

# Show crashes and errors
adb logcat *:E

# Find specific crash
adb logcat | grep -A 50 "FATAL EXCEPTION"

# Show stack traces
adb logcat | grep -E "(at |Caused by)"

Gomobile / Go-specific Logs

# Go runtime logs
adb logcat | grep GoLog

# Go panic messages
adb logcat | grep "panic:"

# Your Go log.Printf output
adb logcat | grep "GoLog"

Emulator

System Images

Image Types:

Type Google Apps Play Store Use Case
default AOSP, minimal
google_apis Google Maps, etc.
google_apis_playstore Full Play Services

Install System Images:

# List available system images
sdkmanager --list | grep system-images

# Android 14 (API 34) - x86_64 (for Intel/AMD)
sdkmanager "system-images;android-34;google_apis;x86_64"

# Android 14 (API 34) - ARM64 (for Apple Silicon)
sdkmanager "system-images;android-34;google_apis;arm64-v8a"

# Android 13 (API 33)
sdkmanager "system-images;android-33;google_apis;x86_64"

Choose architecture:

  • Intel/AMD processors: x86_64
  • Apple Silicon (M1/M2/M3): arm64-v8a

Create AVD

# Quick start
avdmanager create avd \
    -n Pixel7 \
    -k "system-images;android-34;google_apis;x86_64" \
    -d "pixel_7"

# List available device definitions
avdmanager list device

# Common devices: pixel_7, pixel_7_pro, pixel_6, pixel_5, pixel_tablet

Examples:

# Pixel 6 with Android 13
avdmanager create avd \
    -n Pixel6 \
    -k "system-images;android-33;google_apis;x86_64" \
    -d "pixel_6"

# Tablet (Pixel Tablet)
avdmanager create avd \
    -n PixelTablet \
    -k "system-images;android-34;google_apis;x86_64" \
    -d "pixel_tablet"

# Custom configuration
avdmanager create avd \
    -n CustomPhone \
    -k "system-images;android-34;google_apis;x86_64" \
    -d "pixel_7" \
    -c 512M \  # SD card size
    --force    # Overwrite if exists

Manage AVDs

# List all AVDs
avdmanager list avd

# List with paths
avdmanager list avd -c

# Delete AVD
avdmanager delete avd -n Pixel7

Start Emulator

Basic:

# Start AVD
emulator -avd Pixel7

# Start in background
emulator -avd Pixel7 &

# Start with GPU acceleration
emulator -avd Pixel7 -gpu host

Common Options:

# Wipe data (factory reset)
emulator -avd Pixel7 -wipe-data

# Read-only system
emulator -avd Pixel7 -read-only

# Increase RAM
emulator -avd Pixel7 -memory 4096

# Cold boot (ignore snapshot)
emulator -avd Pixel7 -no-snapshot-load

# No audio
emulator -avd Pixel7 -no-audio

# Maximum performance
emulator -avd Pixel7 \
    -gpu host \
    -memory 4096 \
    -cores 4 \
    -no-boot-anim \
    -no-snapshot

Network Options:

# Use specific DNS
emulator -avd Pixel7 -dns-server 8.8.8.8

# Network latency simulation
emulator -avd Pixel7 -netdelay gprs  # or edge, umts, none

# Network speed simulation
emulator -avd Pixel7 -netspeed full  # or gsm, hscsd, gprs, edge, umts, hsdpa

# Proxy
emulator -avd Pixel7 -http-proxy http://localhost:8888

Emulator Control

Via Telnet:

# Connect to emulator console
telnet localhost 5554  # Port shown when emulator starts

# In telnet session:
# auth <token>  # Token is in ~/.emulator_console_auth_token
# help
# sms send 1234567890 "Test message"
# geo fix -122.084 37.422  # Set GPS location
# quit

Via ADB:

# Rotate screen
adb shell settings put system accelerometer_rotation 0
adb shell settings put system user_rotation 1  # 0=0°, 1=90°, 2=180°, 3=270°

# Simulate low battery
adb shell dumpsys battery set level 10

# Reset battery
adb shell dumpsys battery reset

Multiple Emulators

# Start first emulator
emulator -avd Pixel7 &

# Start second emulator (different AVD)
emulator -avd Pixel6 &

# Install on specific emulator
adb -s emulator-5554 install app-debug.apk
adb -s emulator-5556 install app-debug.apk

Emulator Ports:

  • First emulator: console 5554, adb 5555
  • Second emulator: console 5556, adb 5557

Testing

Test Types

Type Runs On Speed Use For
Unit Tests JVM Fast Business logic, utilities
Instrumentation Tests Android device/emulator Slow Android APIs, database
UI Tests Android device/emulator Slowest User interface, interactions

Unit Tests (Local Tests)

Location: app/src/test/java/com/example/myapp/

Dependencies:

dependencies {
    testImplementation("junit:junit:4.13.2")
    testImplementation("org.mockito:mockito-core:5.7.0")
    testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
}

Example:

package com.example.myapp

import org.junit.Test
import org.junit.Assert.*

class CalculatorTest {

    @Test
    fun addition_isCorrect() {
        val calculator = Calculator()
        val result = calculator.add(2, 3)
        assertEquals(5, result)
    }

    @Test
    fun division_byZero_throwsException() {
        val calculator = Calculator()
        assertThrows(ArithmeticException::class.java) {
            calculator.divide(10, 0)
        }
    }
}

Run Unit Tests:

# All unit tests
./gradlew test

# Specific variant
./gradlew testDebug

# Specific test class
./gradlew test --tests CalculatorTest

# With coverage report
./gradlew testDebugUnitTest jacocoTestReport

View Results:

# HTML report
open app/build/reports/tests/testDebugUnitTest/index.html

# Coverage report
open app/build/reports/jacoco/jacocoTestReport/html/index.html

Instrumentation Tests

Location: app/src/androidTest/java/com/example/myapp/

Dependencies:

android {
    defaultConfig {
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
}

dependencies {
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test:runner:1.5.2")
    androidTestImplementation("androidx.test:rules:1.5.0")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

Example:

package com.example.myapp

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*

@RunWith(AndroidJUnit4::class)
class DatabaseTest {

    @Test
    fun useAppContext() {
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.example.myapp", appContext.packageName)
    }
}

Run Instrumentation Tests:

# Connect device/emulator first
adb devices

# All instrumentation tests
./gradlew connectedAndroidTest

# Specific variant
./gradlew connectedDebugAndroidTest

# Specific test
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.myapp.DatabaseTest

UI Tests (Espresso)

Dependencies:

dependencies {
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1")
    androidTestImplementation("androidx.test.espresso:espresso-contrib:3.5.1")
}

Example:

package com.example.myapp

import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun button_click_showsText() {
        // Type text in EditText
        onView(withId(R.id.editText))
            .perform(typeText("Hello"), closeSoftKeyboard())

        // Click button
        onView(withId(R.id.button))
            .perform(click())

        // Verify text is displayed
        onView(withId(R.id.textView))
            .check(matches(withText("Hello")))
    }
}

Common Espresso Operations:

// Find views
onView(withId(R.id.viewId))
onView(withText("text"))
onView(withContentDescription("description"))

// Actions
perform(click())
perform(typeText("text"))
perform(replaceText("new text"))
perform(clearText())
perform(closeSoftKeyboard())
perform(swipeLeft())
perform(swipeRight())
perform(scrollTo())

// Assertions
check(matches(isDisplayed()))
check(matches(withText("expected")))
check(matches(isEnabled()))
check(matches(isChecked()))
check(doesNotExist())

Testing Gomobile Integration

Unit Test Go Code (Before binding):

cd golib
go test ./...
go test -v ./...
go test -cover ./...

Test AAR Integration:

package com.example.myapp

import androidx.test.ext.junit.runners.AndroidJUnit4
import mylib.Mylib  // Import gomobile generated package
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*

@RunWith(AndroidJUnit4::class)
class GomobileTest {

    @Test
    fun goFunction_returnsExpectedResult() {
        val result = Mylib.fetchURL("https://example.com")
        assertNotNull(result)
        assertTrue(result.isNotEmpty())
    }

    @Test
    fun goFunction_handlesError() {
        try {
            Mylib.fetchURL("invalid-url")
            fail("Should have thrown exception")
        } catch (e: Exception) {
            assertTrue(e.message?.contains("error") == true)
        }
    }
}

Code Coverage (JaCoCo)

Configure:

plugins {
    id("jacoco")
}

android {
    buildTypes {
        debug {
            enableAndroidTestCoverage = true
            enableUnitTestCoverage = true
        }
    }
}

tasks.register<JacocoReport>("jacocoTestReport") {
    dependsOn("testDebugUnitTest", "createDebugCoverageReport")

    reports {
        xml.required.set(true)
        html.required.set(true)
    }

    val fileFilter = listOf(
        "**/R.class",
        "**/R$*.class",
        "**/BuildConfig.*",
        "**/Manifest*.*"
    )

    val debugTree = fileTree("${project.buildDir}/intermediates/javac/debug") {
        exclude(fileFilter)
    }

    val mainSrc = "${project.projectDir}/src/main/java"

    sourceDirectories.setFrom(files(mainSrc))
    classDirectories.setFrom(files(debugTree))
    executionData.setFrom(fileTree(project.buildDir) {
        include("jacoco/testDebugUnitTest.exec", "outputs/code_coverage/debugAndroidTest/connected/**/*.ec")
    })
}

Run Coverage:

./gradlew jacocoTestReport
open app/build/reports/jacoco/jacocoTestReport/html/index.html

Test Best Practices

1. Follow AAA Pattern:

@Test
fun testName() {
    // Arrange - Set up test data
    val calculator = Calculator()

    // Act - Execute the function
    val result = calculator.add(2, 3)

    // Assert - Verify the result
    assertEquals(5, result)
}

2. Use Descriptive Names:

// Good
@Test
fun addition_withPositiveNumbers_returnsSum() { }

@Test
fun division_withZeroDivisor_throwsException() { }

// Bad
@Test
fun test1() { }

3. Test One Thing:

// Good - separate tests
@Test
fun add_returnsCorrectSum() { }

@Test
fun add_handlesNegativeNumbers() { }

4. Mock External Dependencies:

import org.mockito.Mock
import org.mockito.Mockito.*

class UserRepositoryTest {

    @Mock
    lateinit var api: ApiService

    @Test
    fun getUser_callsApi() {
        val userId = "123"
        `when`(api.fetchUser(userId)).thenReturn(User("John"))

        val repository = UserRepository(api)
        val user = repository.getUser(userId)

        verify(api).fetchUser(userId)
        assertEquals("John", user.name)
    }
}

Debugging from Code

Android (Kotlin/Java)

import android.util.Log

class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Log levels
        Log.v(TAG, "Verbose message")
        Log.d(TAG, "Debug message")
        Log.i(TAG, "Info message")
        Log.w(TAG, "Warning message")
        Log.e(TAG, "Error message")

        // With exception
        try {
            // Code that might throw
        } catch (e: Exception) {
            Log.e(TAG, "Error occurred", e)
        }
    }
}

View in logcat:

adb logcat -s MainActivity

Go Code (Gomobile)

package mylib

import "log"

func FetchData(url string) (string, error) {
    log.Printf("Fetching URL: %s", url)  // Appears in logcat with GoLog tag

    result, err := doFetch(url)

    if err != nil {
        log.Printf("Error fetching: %v", err)
        return "", err
    }

    log.Printf("Fetch successful, got %d bytes", len(result))
    return result, nil
}

View in logcat:

adb logcat | grep GoLog

Interactive Debugging

Enable Debug Mode:

// app/build.gradle.kts
android {
    buildTypes {
        debug {
            isDebuggable = true
        }
    }
}

Using Android Studio Debugger:

  1. Build debug APK: ./gradlew assembleDebug
  2. Install on device: adb install app-debug.apk
  3. In Android Studio: Run → Attach Debugger to Android Process
  4. Select your app
  5. Set breakpoints in code
  6. Trigger the code path

Performance Profiling

CPU Profiling

# Start profiling
adb shell am profile start com.example.myapp /sdcard/profile.trace

# Stop profiling
adb shell am profile stop com.example.myapp

# Pull trace file
adb pull /sdcard/profile.trace

# Analyze with Android Studio or Perfetto

Memory Profiling

# Get memory info for app
adb shell dumpsys meminfo com.example.myapp

# Heap dump
adb shell am dumpheap com.example.myapp /sdcard/heap.hprof
adb pull /sdcard/heap.hprof

# Analyze with Android Studio Memory Profiler

Monitor App Performance

# CPU usage
adb shell top | grep com.example.myapp

# Memory usage over time
adb shell dumpsys meminfo com.example.myapp | grep TOTAL

Debugging Crashes

1. View Crash Logs

adb logcat -c  # Clear old logs
# Reproduce crash
adb logcat *:E  # View errors

2. Find Stack Trace

Look for:

FATAL EXCEPTION: main
Process: com.example.myapp, PID: 12345
java.lang.NullPointerException: Attempt to invoke virtual method '...' on a null object reference
    at com.example.myapp.MainActivity.onCreate(MainActivity.kt:25)
    at android.app.Activity.performCreate(Activity.java:...)

3. Analyze Stack Trace

  • Line number: MainActivity.kt:25 - exact location
  • Exception type: NullPointerException - what went wrong
  • Message: Details about the error
  • Call stack: How we got there

4. Common Crash Types

NullPointerException:

// Bad
val user = getUser()  // Returns null
val name = user.name  // Crash!

// Good
val user = getUser()
val name = user?.name ?: "Unknown"

ClassCastException:

// Bad
val text = view as TextView  // Crash if not TextView!

// Good
val text = view as? TextView

ActivityNotFoundException:

// Check if intent can be handled
if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
}

Network Debugging

HTTP Traffic Inspection

# Use adb reverse to connect app to local proxy
adb reverse tcp:8888 tcp:8888

# App should connect to localhost:8888
# Proxy (mitmproxy, Charles, Burp Suite) runs on port 8888

View Network Stats

adb shell dumpsys netstats

Troubleshooting

ADB Issues

“adb: device offline”:

adb kill-server
adb start-server
# Reconnect device

“adb: device unauthorized”:

  • Check device screen for authorization prompt
  • Accept “Always allow from this computer”
  • If no prompt:
    adb kill-server
    rm ~/.android/adbkey*
    adb start-server
    

“Logcat shows nothing”:

# Clear buffer
adb logcat -c

# Check buffer size
adb logcat -g

# Increase buffer size
adb logcat -G 16M

Emulator Issues

Emulator won’t start:

# Check emulator location
which emulator

# Run with verbose logging
emulator -avd Pixel7 -verbose

# Check for errors
emulator -avd Pixel7 -debug init

Slow emulator - Enable hardware acceleration:

Linux (KVM):

# Check KVM support
egrep -c '(vmx|svm)' /proc/cpuinfo  # Should be > 0

# Check KVM device
ls -l /dev/kvm

# Add user to kvm group
sudo usermod -a -G kvm $USER
# Log out and back in

# Verify
emulator -accel-check

macOS: Automatically uses Hypervisor Framework

Windows (HAXM):

sdkmanager "extras;intel;Hardware_Accelerated_Execution_Manager"

Graphics issues:

# Try different GPU modes
emulator -avd Pixel7 -gpu auto
emulator -avd Pixel7 -gpu host
emulator -avd Pixel7 -gpu swiftshader_indirect
emulator -avd Pixel7 -gpu angle_indirect

Test Issues

Tests fail on CI but pass locally:

  • Check Android API level compatibility
  • Verify emulator configuration
  • Check for timing issues (add timeouts)
  • Ensure deterministic test data

Instrumentation tests hang:

# Clear app data between tests
adb shell pm clear com.example.myapp

# Restart adb
adb kill-server
adb start-server

Espresso can’t find view:

// Use idling resources for async operations
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource

Helper Scripts

Monitor App Script

Create monitor-app.sh:

#!/usr/bin/env bash
set -euo pipefail

APP_PACKAGE="com.example.myapp"
LOG_TAG="MyApp"

# Clear logs
adb logcat -c

# Monitor logs with color
adb logcat | grep --color=always -E "$APP_PACKAGE|$LOG_TAG|FATAL|ERROR"

Install and Monitor Script

Create debug-install.sh:

#!/usr/bin/env bash
set -euo pipefail

APK="app/build/outputs/apk/debug/app-debug.apk"
PACKAGE="com.example.myapp"
ACTIVITY=".MainActivity"

echo "Installing $APK..."
adb install -r "$APK"

echo "Starting app..."
adb shell am start -n "$PACKAGE/$ACTIVITY"

echo "Monitoring logs (Ctrl+C to stop)..."
adb logcat -c
adb logcat | grep "$PACKAGE"

Start Emulator and Wait

Create start-emulator.sh:

#!/usr/bin/env bash
set -euo pipefail

AVD_NAME="Pixel7"

echo "Starting emulator: $AVD_NAME"
emulator -avd "$AVD_NAME" -no-snapshot-save &

# Wait for device to boot
echo "Waiting for device to boot..."
adb wait-for-device

# Wait for boot animation to finish
while [ "$(adb shell getprop init.svc.bootanim | tr -d '\r')" = "running" ]; do
    sleep 1
done

echo "Emulator ready!"

# Optional: Install app
if [ -f "app-debug.apk" ]; then
    echo "Installing app..."
    adb install -r app-debug.apk
fi

echo "Done!"

CI/CD Testing

GitHub Actions Example

.github/workflows/test.yml:

name: Android CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Test Go code
        run: |
          cd golib
          go test -v ./...

      - name: Build gomobile AAR
        run: |
          go install golang.org/x/mobile/cmd/gomobile@latest
          gomobile init
          cd golib
          gomobile bind -target=android -o mylib.aar .
          cp mylib.aar ../app/libs/

      - name: Run unit tests
        run: ./gradlew test

      - name: Run instrumented tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 34
          target: google_apis
          arch: x86_64
          script: ./gradlew connectedCheck

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: app/build/reports/tests/

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml

Next Steps

  • Build and publish - Use the Build workflow for release builds
  • Increase coverage - Aim for >80% test coverage
  • Profile performance - Identify and fix bottlenecks
  • Monitor crashes - Set up crash reporting (Firebase Crashlytics)

Resources