Common Anr (Application Not Responding) in Database Client Apps: Causes and Fixes

ANR occurs when the Android main thread is blocked for more than 5 seconds. In database client apps, the root causes cluster around a few predictable patterns:

February 07, 2026 · 5 min read · Common Issues

What Causes ANR in Database Client Apps

ANR occurs when the Android main thread is blocked for more than 5 seconds. In database client apps, the root causes cluster around a few predictable patterns:

Main-thread database operations. Running SQLite or Room queries on the UI thread is the single most common trigger. A SELECT against a table with 50,000 rows, an unindexed WHERE clause, or a JOIN across multiple large tables can easily exceed the 5-second threshold.

Synchronous network calls to remote databases. Connecting to PostgreSQL, MySQL, or a REST API wrapper around a database on the main thread blocks until the network round-trip completes. On a slow mobile connection, a single query can take 10+ seconds.

Cursor and result-set mishandling. Loading an entire result set into memory via Cursor without pagination, or iterating over a large Cursor on the main thread, causes both ANR and memory pressure.

Lock contention. When a background thread holds a database write lock and the main thread attempts a read (or vice versa), the main thread blocks until the lock is released. Long-running transactions make this worse.

UI rendering tied to query results. Triggering notifyDataSetChanged() on a RecyclerView adapter with thousands of rows after a database read forces layout passes on the main thread.

Real-World Impact

ANR is not a soft failure. Android's ANR dialog gives users one option: "Close app." The consequences are measurable:

How ANR Manifests in Database Client Apps

1. Full-table scan on the main thread. A user taps "Search orders" and the app runs SELECT * FROM orders WHERE customer_id = ? without an index on customer_id. The UI freezes for 8 seconds. ANR dialog appears.

2. Bulk insert without background threading. An import feature reads a CSV file and inserts 10,000 rows into SQLite using a single transaction on the main thread. The app becomes unresponsive until the transaction commits.

3. Cursor window overflow. A query returns 20,000 rows. The CursorWindow has a 2 MB limit. The main thread stalls as the system copies data across process boundaries in multiple passes.

4. Database migration on first launch. A Room migration with ALTER TABLE on a table with millions of rows runs synchronously during onCreate(). The splash screen hangs. ANR fires before the UI renders.

5. Network query to remote database on main thread. A MySQL client app executes SELECT * FROM transactions WHERE date > '2024-01-01' over a REST API call using HttpURLConnection on the main thread. On a 3G connection, the call takes 12 seconds.

6. Lock contention during concurrent read/write. A background service performs a bulk update while the user tries to view a record. The main thread's SELECT blocks waiting for the write transaction to release its lock.

7. RecyclerView adapter refresh after large query. A dashboard app queries 5,000 rows and calls notifyDataSetChanged() on the main thread. The RecyclerView measures and layouts all visible items synchronously, blocking the thread for 6+ seconds.

How to Detect ANR

Android Vitals (Play Console). Check the ANR rate under Quality > Android Vitals. The bad behavior threshold is 0.47%. Drill into stack traces to identify the blocking call.

StrictMode. Enable StrictMode in debug builds to catch accidental disk I/O and network calls on the main thread:


StrictMode.setThreadPolicy(
    StrictMode.ThreadPolicy.Builder()
        .detectDiskReads()
        .detectDiskWrites()
        .detectNetwork()
        .penaltyLog()
        .penaltyFlashScreen()
        .build()
)

Firebase Crashlytics. ANR stack traces are captured automatically. Filter by exception type Application Not Responding and look for android.os.Looper or SQLiteDatabase in the trace.

Systrace / Perfetto. Record a trace during a suspected ANR. Look for slices longer than 5 seconds on the main thread. The atrace categories am (Activity Manager) and Choreographer show ANR-related signals.

Custom watchdog. Run a periodic task that pings the main thread Handler. If no response arrives within 3 seconds, log the main thread stack trace:


class AnrWatchdog(private val thresholdMs: Long = 3000) {
    private val handler = Handler(Looper.getMainLooper())
    private var tick = 0

    fun start() {
        handler.postDelayed(object : Runnable {
            override fun run() {
                val start = SystemClock.uptimeMillis()
                handler.post {
                    val latency = SystemClock.uptimeMillis() - start
                    if (latency > thresholdMs) {
                        Log.e("ANR_WATCHDOG", "Main thread blocked for ${latency}ms")
                        // Dump stack trace
                    }
                }
                handler.postDelayed(this, thresholdMs)
            }
        }, thresholdMs)
    }
}

SUSATest autonomous testing. Upload your APK to SUSATest and let it explore your database client app across 10 user personas. SUSA autonomously triggers ANR scenarios — large queries, bulk operations, slow network conditions — and reports exact reproduction steps with stack traces. No test scripts needed.

How to Fix Each Example

1. Full-table scan. Add an index: CREATE INDEX idx_orders_customer ON orders(customer_id). Move the query to a coroutine or AsyncTaskLoader. Use EXPLAIN QUERY PLAN to verify index usage.

2. Bulk insert. Use CoroutineScope(Dispatchers.IO) with Room, or wrap inserts in a single transaction on a background thread:


withContext(Dispatchers.IO) {
    database.runInTransaction {
        csvRows.forEach { row ->
            dao.insert(row.toEntity())
        }
    }
}

3. Cursor window overflow. Implement pagination. Use LIMIT and OFFSET in SQL queries, or use Room's PagingSource with the Paging 3 library. Never load more than 100-200 rows at once.

4. Database migration. Use Migration classes with fallbackToDestructiveMigration() only as a last resort. For large schema changes, run migrations asynchronously and show a progress indicator. Pre-package the database if possible.

5. Network query on main thread. Use Retrofit with coroutines or RxJava. Never use HttpURLConnection directly on the main thread. Set connection timeouts:


connection.connectTimeout = 5000
connection.readTimeout = 10000

6. Lock contention. Use WAL (Write-Ahead Logging) mode in SQLite: database.enableWriteAheadLogging(). This allows concurrent reads during writes. Keep transactions short.

7. RecyclerView refresh. Use DiffUtil instead of notifyDataSetChanged(). Run the diff calculation on a background thread and dispatch results to the main thread:


val diffResult = DiffUtil.calculateDiff(MyDiffCallback(oldList, newList))
withContext(Dispatchers.Main) {
    adapter.submitList(newList)
    diffResult.dispatchUpdatesTo(adapter)
}

Prevention: Catch ANR Before Release

CI/CD integration. Add ANR detection to your build pipeline. SUSATest provides a CLI tool (pip install susatest-agent) that integrates with GitHub Actions. Run autonomous ANR detection on every pull request:


- name: Run SUSATest ANR Detection
  run: |
    susatest-agent run --app app/build/outputs/apk/debug/app-debug.apk \
      --focus anr \
      --output junit-results.xml

Automated testing. Write instrumentation tests that execute your heaviest queries and assert main thread responsiveness. Use Espresso idling resources to detect blocking.

Code review checklist. Before merging, verify: no SQLiteDatabase calls on the main thread, no synchronous network calls, all migrations are async, all list updates use DiffUtil.

Cross-session learning. SUSATest gets smarter about your app with each run. It learns which screens and flows trigger ANR and prioritizes those paths in subsequent test sessions, catching regressions that manual testing misses.

ANR in database client apps is a solvable problem. The fixes are well-known. The gap is detection — most teams discover ANR through user complaints. Autonomous testing closes that gap by finding ANR before your users do.

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free