ANR Prevention on Android
ANR Prevention on Android
ANR (Application Not Responding) is the Android system's response to an application that blocks its main thread for too long. When the main thread cannot process input events or BroadcastReceiver.onReceive() within the system's timeout window, Android displays a dialog asking the user to wait or force close the app. ANRs directly affect app store ratings and user retention. By the end of this lesson, you will be able to:
- Explain what triggers an ANR and the specific timeout thresholds the system enforces.
- Identify common main thread blocking patterns that lead to ANRs.
- Apply coroutines and dispatchers to move work off the main thread.
- Use
StrictModeand the Android Profiler to detect potential ANR sources. - Design architectures that keep the main thread exclusively for UI rendering and event dispatch.
ANR Triggers and Timeout Thresholds
The Android system monitors the main thread through two primary mechanisms. For input dispatching, if the main thread does not respond to an input event (touch, key press) within 5 seconds, the system triggers an ANR. For broadcast receivers, if onReceive() does not return within 10 seconds (foreground) or 60 seconds (background), the system triggers an ANR.
The main thread is a single thread that processes all UI rendering, input events, lifecycle callbacks, and broadcast receiver calls sequentially through a Looper and MessageQueue. Any operation that blocks this thread for longer than the timeout prevents subsequent messages from being processed.
// This will trigger an ANR if the network call takes over 5 seconds
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val data = api.fetchDataSynchronously() // blocks main thread
displayData(data)
}
Common blocking patterns include synchronous network requests, large database queries on the main thread, file I/O operations, heavy JSON parsing, complex bitmap decoding, and Thread.sleep() calls. Any of these can exceed the 5 second threshold under poor network conditions or on lower end devices.
Moving Work Off the Main Thread
The primary defense against ANRs is ensuring the main thread never performs blocking work. Kotlin Coroutines provide the most straightforward approach by suspending work on an appropriate dispatcher while keeping the calling code sequential.
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _users = MutableStateFlow<List<User>>(emptyList())
val users: StateFlow<List<User>> = _users.asStateFlow()
fun loadUsers() {
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
repository.fetchUsers() // runs on IO thread pool
}
_users.value = result // runs on main thread
}
}
}
viewModelScope.launch starts on Dispatchers.Main by default. The withContext(Dispatchers.IO) block suspends the coroutine on the main thread and resumes execution on a thread from the IO pool. When the blocking work completes, the coroutine resumes on the main thread to update the UI state. The main thread is never blocked during the network call.
This interview continues for subscribers
Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.
Become a Sponsor