Android에서 ANR의 주요 원인과 해결 방법 (완전한 개발자 가이드)

발행: (2026년 4월 7일 AM 03:33 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

Introduction

사용자가 Android 앱을 열고 버튼을 탭했지만 아무 일도 일어나지 않는다고 상상해 보세요. 화면이 멈춥니다. 몇 초 후 Android가 Application Not Responding (ANR) 을 표시합니다. 대부분의 사용자는 기다리지 않고 앱을 닫고 삭제합니다. 그래서 ANR은 Android 앱에서 가장 위험한 성능 문제 중 하나입니다.

Android Developers 문서에 따르면, ANR은 메인 스레드가 차단되어 특정 시간 내에 사용자 입력에 응답하지 못할 때 발생합니다. ANR 비율이 높아지면 Google Play가 앱의 노출을 감소시킬 수 있습니다. 따라서 ANR을 이해하는 것이 중요합니다.

ANR이란? (간단 설명)

공식 정의

ANR은 UI 스레드가 차단되어 사용자 입력을 처리하거나 프레임을 그릴 수 없을 때 발생합니다. Android는 다음 상황에서 ANR을 표시합니다:

  • 입력 이벤트가 5 초 이내에 처리되지 않을 때
  • BroadcastReceiver 가 너무 오래 실행될 때
  • 서비스가 시작되는 데 너무 오래 걸릴 때
  • 작업 또는 포그라운드 서비스가 응답을 지연시킬 때

실제 예시

앱을 레스토랑에 비유해 보세요.

역할비유
UI Thread웨이터
Background Thread주방
User손님

정상 흐름
손님이 주문 → 웨이터가 주방에 전달 → 주방이 준비 → 웨이터가 전달 (원활).

문제 흐름 (ANR)
손님이 주문 → 웨이터가 직접 주방에 가서 요리 시작 → 새로운 주문을 받지 못하고, 음식도 제공되지 않음 → 레스토랑이 멈춤.
이는 UI 스레드에서 무거운 작업을 수행하는 상황과 동일합니다.

ANR의 주요 원인

1. 메인 스레드에서 무거운 작업

네트워크 호출, 데이터베이스 쿼리, 파일 읽기, JSON 파싱, 이미지 처리 등을 UI 스레드에서 실행하면 해당 스레드가 차단됩니다. Android 문서에서는 메인 스레드를 차단하지 말고 무거운 작업은 워커 스레드로 옮기라고 명시하고 있습니다.

2. 긴 데이터베이스 또는 네트워크 작업

val data = api.getUsers() // 메인 스레드에서 실행

이 코드는 UI를 차단하고 ANR을 유발할 수 있습니다.

3. BroadcastReceiver에서 무거운 작업 수행

BroadcastReceiver는 짧은 작업만 수행해야 합니다. onReceive 내부에서 장시간 실행되는 작업을 하면 ANR이 발생합니다. Android에서는 이러한 작업을 백그라운드 스레드로 옮길 것을 권장합니다.

4. 서비스가 제때 시작되지 않음

포그라운드 서비스는 5 초 이내에 startForeground()를 호출해야 합니다. 이를 지키지 못하면 ANR이 발생합니다.

5. 교착 상태 및 스레드 차단

한 스레드가 다른 스레드(예: 스레드 A가 스레드 B를 기다림)를 기다릴 때 앱이 멈춥니다. 이러한 교착 상태는 ANR의 주요 원인 중 하나입니다.

일반 개발자 실수

액티비티에서 API 호출 실행

override fun onCreate() {
    super.onCreate()
    val users = api.getUsers() // Bad: runs on UI thread
}

UI 스레드에서 대용량 JSON 파싱

val json = File("data.json").readText() // Bad: blocks UI

ANR을 방지하기 위한 모범 사례

  • 코루틴 사용

    viewModelScope.launch {
        val users = repository.getUsers()
    }
  • I/O에는 Dispatchers.IO 사용

    withContext(Dispatchers.IO) {
        api.getUsers()
    }
  • 연속 데이터에는 Flow 사용

    repository.getUsersFlow()
        .flowOn(Dispatchers.IO)
  • 백그라운드 작업에는 WorkManager 사용 (예: 동기화, 업로드, 다운로드, 스케줄링)

  • BroadcastReceiver를 가볍게 유지

    override fun onReceive(context: Context, intent: Intent) {
        CoroutineScope(Dispatchers.IO).launch {
            repository.sync()
        }
    }

최신 Kotlin 코드 예시

class UserRepository(
    private val api: UserApi
) {
    suspend fun getUsers(): List = withContext(Dispatchers.IO) {
        api.getUsers()
    }
}
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {

    private val _users = MutableStateFlow<List<User>>(emptyList())
    val users = _users.asStateFlow()

    init { loadUsers() }

    private fun loadUsers() {
        viewModelScope.launch {
            _users.value = repository.getUsers()
        }
    }
}
@Composable
fun UserScreen(viewModel: UserViewModel) {
    val users by viewModel.users.collectAsState()
    LazyColumn {
        items(users) { user ->
            Text(user.name)
        }
    }
}

실제 사용 사례

시나리오: 앱이 기기 비디오를 스캔합니다.

  • 잘못된 경우: Activity에서 파일을 직접 스캔 → UI가 멈춤 → ANR.
  • 올바른 경우: 스플래시 화면을 표시하고, 레포지토리가 Dispatchers.IO를 사용해 스캔하도록 하며, Flow를 통해 결과를 방출하고 데이터가 도착할 때 UI를 업데이트합니다.

결과: ANR이 없고, 부드러운 사용자 경험.

핵심 요점

  • 핵심 규칙: 메인 스레드를 절대 차단하지 말 것.
  • UI 스레드는 사용자 입력 및 렌더링을 위해 항상 자유롭게 유지되어야 함.

결론

ANR은 단순히 성능 문제에 그치지 않으며; 사용자 경험, 앱 평점, Play 스토어 순위, 그리고 수익에 직접적인 영향을 미칩니다. 가장 안전한 전략은 간단합니다: UI 스레드를 깨끗하게 유지하고 모든 무거운 작업을 백그라운드 스레드로 옮기는 것입니다. Android 공식 가이드와 최신 Kotlin 관행을 따름으로써 ANR을 거의 완전히 없앨 수 있습니다.

0 조회
Back to Blog

관련 글

더 보기 »