Detecting, Diagnosing, and Fixing ANRs in Android Applications
Denis Koome
Mobile Developer | Writer | Web 3 | AI & Tech Enthusiast | Kotlin | Flutter | React
With Application Not Responding (ANR) one of the most frustrating experiences for users and developers, detecting, diagnosing, and fixing them is critical to maintain a smooth app performance. ANRs severely degrade user experience and app reputation, necessitating a careful design, robust error handling, and regular monitoring to help mitigate occurrences. Developers need to adopt asynchronous programming patterns and leverage tools like Kotlin coroutines and Android Studio Profiler to build responsive and stable applications. This article suggests some of the best approaches to detect, diagnose, and fix ANRs to help build applications with an improved performance and user satisfaction.
Detecting ANRs
Google Play Console
If an application is already published, Google play console offers Android Vitals to provide insights into ANR occurrences. Android Vitals offers several metrics including ANR rate, multiple ANR rate, and user-perceived ANR rate to help developers understand the scale of ANR. While ANR Rate shows the percentage of daily active users experiencing any ANR, User-Perceived ANR Rate ensure one can understand the percentage experiencing input dispatching timed out ANRs. On the other hand Multiple ANR Rate shows the percentage experiencing at least two ANRs, enabling developers to compare these metrics with the bad behavior thresholds set by Google Play and determine where their applications fall.
Custom ANR Detection
Developers can also implement custom ANR detection within their application to help detect occurrences. Libraries like ANR-Watch-Dog can monitor the main thread for responsiveness. Here's a simple example using Kotlin;
import com.github.anrwatchdog.ANRWatchDog
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize ANRWatchDog with a 5-second timeout
ANRWatchDog().setANRListener { anrError ->
// Handle ANR here, e.g., log or show a custom alert
Log.e("ANR", "Detected ANR: ${anrError.message}")
}.start()
}
}
Diagnosing ANRs
Diagnosing ANRs involves understanding why the main thread was blocked. Mobile developers need to look for common patterns such as slow I/O operations or long calculations on the main thread, synchronous binder calls taking too long, and main thread blocked by synchronized blocks or deadlocks to diagnose ANRs.
ANR Traces
Android generates ANR trace files in /data/anr when an ANR occurs. Accessing these files (typically via adb) provides stack traces for all threads at the moment of ANR, pinpointing where the blockage occurred.
Testing Under Stress
Simulating heavy loads, poor network conditions, or limited resources to uncover potential issues can also help diagnose ANRs.
Profiling Tools
Profiling tools also offer great support in supporting developers diagnose ANRs. Some of these tools include;
StrictMode
This developer tool helps detect accidental disk or network access on the main thread during development. Its implementation is as follows;
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build())
}
}
领英推荐
Fixing ANRs
Once diagnosed, fixing ANRs usually involves moving long-running operations off the main thread using classes like AsyncTask or Executor and shifting I/O operations to worker threads. Besides, one must avoid holding locks for long periods on resources needed by the main thread. Use mechanisms like onProgressUpdate() and onPostExecute() for thread communication.
Kotlin Coroutines
A modern approach for managing background tasks.
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
private val scope = CoroutineScope(Dispatchers.Main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
scope.launch {
doLongOperation()
}
}
}
private suspend fun doLongOperation() = withContext(Dispatchers.Default) {
// Perform long-running operation here
delay(10000) // Simulate long operation
}
}
Deadlocks
Deadlocks occur when threads are waiting for each other to release resources, causing the main thread to become unresponsive. Prevent deadlocks by using deadlock prevention algorithms and ensuring efficient lock management.
Slow Broadcast Receivers
Broadcast receivers can cause ANRs if they take too long to process messages. Move long-running operations from onReceieve() to an IntentService to execute work on a worker thread.
override fun onReceive(context: Context, intent: Intent) {
Intent(context, MyIntentService::class.java).also { intentService ->
// The task now runs on a worker thread.
context.startService(intentService)
}
}
class MyIntentService : IntentService("MyIntentService") {
override fun onHandleIntent(intent: Intent?) {
BubbleSort.sort(data)
}
}
Using goAsync() in Broadcast Receivers
For long-running operations, use goAsync() and call finish() on the PendingResult object to avoid ANRs.
val pendingResult = goAsync()
object : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? {
// This is a long-running operation
BubbleSort.sort(params[0])
pendingResult.finish()
return 0L
}
}.execute(data)
Avoiding Blockages
(Example- Room Database Access)
@Dao
interface UserDao {
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
}
fun loadUsers() {
CoroutineScope(Dispatchers.IO).launch {
val users = userDao.getAllUsers()
withContext(Dispatchers.Main) {
displayUsers(users)
}
}
}
Conclusion
Dealing with ANRs is crucial for providing a seamless user experience in Android applications. By integrating robust detection tools, leveraging Kotlin's coroutines for non-blocking operations, and utilizing profiling tools for in-depth diagnosis, developers can significantly reduce ANR occurrences. Remember, the key to effective ANR management is to keep the main thread light and responsive, offloading any potentially time-consuming tasks to background threads. Proactively addressing potential issues during development will ensure a smoother user experience and better app performance.
Building Dafifi | Low-code powerful tools that saves time, so you can focus on what counts.
2 个月Tech bros! This is worth a read.