← Назад к вопросам

Какие знаешь причины возникновения ANR?

2.0 Middle🔥 241 комментариев
#Многопоточность и асинхронность#Производительность и оптимизация

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Причины возникновения ANR (Application Not Responding)

ANR — это Application Not Responding, диалог, который пользователь видит когда приложение заморозилось. Это критический сценарий, который вызывает среднюю оценку приложения. Вот все причины, по которым может возникнуть ANR.

Что такое ANR?

ANR возникает когда главный поток (Main Thread) блокируется дольше определённого времени:

  • 5 секунд — для обработки Input событий (сенсорные события)
  • 10 секунд — для BroadcastReceiver
  • 20 секунд — для Service
// ❌ Плохо — блокирует главный поток
Button(onClick = {
    val result = heavyComputation() // 6 секунд!
    showResult(result)
})

// ✅ Хорошо — работает в background потоке
Button(onClick = {
    viewModelScope.launch(Dispatchers.Default) {
        val result = heavyComputation()
        withContext(Dispatchers.Main) {
            showResult(result)
        }
    }
})

Основные причины ANR

1. Синхронные сетевые операции на главном потоке

// ❌ ANR! Блокирует 5+ секунд
fun fetchUser(userId: String) {
    val user = apiService.getUser(userId) // синхронный вызов
    showUser(user)
}

// ✅ Правильно
fun fetchUser(userId: String) {
    viewModelScope.launch {
        try {
            val user = withContext(Dispatchers.IO) {
                apiService.getUser(userId)
            }
            showUser(user)
        } catch (e: Exception) {
            showError(e)
        }
    }
}

2. Тяжелые вычисления на главном потоке

// ❌ ANR! Факториал от большого числа
Button(onClick = {
    val result = factorial(10000) // миллиарды операций
    showResult(result)
})

// ✅ Правильно — в Background потоке
Button(onClick = {
    viewModelScope.launch(Dispatchers.Default) {
        val result = factorial(10000)
        withContext(Dispatchers.Main) {
            showResult(result)
        }
    }
})

3. Синхронный доступ к базе данных

// ❌ ANR! Синхронный запрос
Button(onClick = {
    val user = database.userDao().getUserSync(userId) // блокирует!
    showUser(user)
})

// ✅ Правильно — асинхронно
Button(onClick = {
    viewModelScope.launch {
        val user = withContext(Dispatchers.IO) {
            database.userDao().getUser(userId)
        }
        showUser(user)
    }
})

4. Неправильное использование SharedPreferences

// ❌ ANR! SharedPreferences.apply() может заблокировать
SharedPreferences.edit().apply {
    putString("large_json", hugeJsonString)
    putInt("count", 1000000)
}.apply() // может занять 1+ секунду на слабых устройствах

// ✅ Правильно — в background потоке
viewModelScope.launch(Dispatchers.IO) {
    SharedPreferences.edit().apply {
        putString("large_json", hugeJsonString)
    }.apply()
}

5. Цикл без выхода на главном потоке

// ❌ Бесконечный цикл на главном потоке
Button(onClick = {
    while (true) { // блокирует UI!
        doWork()
    }
})

// ✅ Правильно — с условием выхода
Button(onClick = {
    viewModelScope.launch(Dispatchers.Default) {
        repeat(1000) {
            doWork()
        }
    }
})

6. Блокирующий Looper или Handler

// ❌ ANR! Handler блокирует
Handler(Looper.getMainLooper()).post {
    // Долгая операция (10+ секунд)
    processHugeDataset()
}

// ✅ Правильно
viewModelScope.launch(Dispatchers.Default) {
    processHugeDataset()
}

7. Синхронизация на главном потоке (deadlock)

// ❌ ANR! Ожидание lock может привести к deadlock
synchronized(lockObject) {
    val data = fetchDataFromNetwork() // может заблокироваться
}

// ✅ Правильно — работа в IO потоке
viewModelScope.launch(Dispatchers.IO) {
    synchronized(lockObject) {
        val data = fetchDataFromNetwork()
    }
}

8. Слишком много объектов в памяти (Garbage Collection pause)

// ❌ Создаём миллионы объектов, GC паузирует главный поток на 2-5 секунд
for (i in 0 until 1_000_000) {
    val list = mutableListOf<String>()
    list.add("item") // создаём объекты без конца
}

// ✅ Правильно — переиспользуем объекты
val list = mutableListOf<String>()
for (i in 0 until 1_000_000) {
    list.add("item")
}

Признаки ANR

// Android Monitor:
// E/ANRManager: ANR detected: com.example.app
// E/ANRManager: 5000 ms
// Backtrace показывает функцию, которая заблокировала главный поток

Как отладить ANR

1. Использовать StrictMode (Development)

if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(
        StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()
            .penaltyLog()
            .penaltyFlashScreen()
            .build()
    )
    StrictMode.setVmPolicy(
        StrictMode.VmPolicy.Builder()
            .detectLeakedSqlLiteObjects()
            .detectLeakedClosableObjects()
            .penaltyLog()
            .build()
    )
}

2. Использовать Profiler

Android Studio → Profiler → Record
Видишь spike в CPU на главном потоке? Это ANR.

3. Смотреть Logcat

logcat -b crash "ANR in"

Проверка на главном потоке

// Проверить на каком потоке выполняется код
if (Looper.getMainLooper() == Looper.myLooper()) {
    Log.d("Thread", "На главном потоке!")
} else {
    Log.d("Thread", "На background потоке")
}

// Хелпер функция
fun assertMainThread() {
    check(Looper.getMainLooper() == Looper.myLooper()) {
        "This should be called on main thread!"
    }
}

Практический пример: Правильная загрузка данных

@HiltViewModel
class UserViewModel(
    private val userRepository: UserRepository
) : ViewModel() {
    
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    fun loadUser(userId: String) {
        viewModelScope.launch {
            try {
                _uiState.value = UiState.Loading
                
                // Все работает в IO потоке
                val user = withContext(Dispatchers.IO) {
                    userRepository.getUser(userId)
                }
                
                // UI обновляется на Main потоке
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
            }
        }
    }
}

seal class UiState {
    object Loading : UiState()
    data class Success(val user: User) : UiState()
    data class Error(val message: String?) : UiState()
}

Главное правило

Главный поток = для UI только!

  • ✓ Обновление Views
  • ✓ Вызовы Composable
  • ✗ Сетевые запросы
  • ✗ Работа с БД
  • ✗ Тяжелые вычисления
  • ✗ Синхронизация

Это базовое правило гарантирует отсутствие ANR и плавный интерфейс с 60 FPS.