Android 앱의 UI 스레드가 너무 오랫동안 차단되면 'ANR(애플리케이션 응답 없음)' 오류가 트리거됩니다. 앱이 포그라운드에 있으면 그림 1에서와 같이 시스템에서 사용자에게 대화상자를 표시합니다. 사용자가 ANR 대화상자에서 앱을 강제 종료할 수 있습니다.
ANR은 UI 업데이트를 담당하는 앱의 기본 스레드가 사용자 입력 이벤트 또는 그리기를 처리하지 못하여 사용자 불만을 초래하므로 문제가 됩니다. 앱의 기본 스레드에 관한 자세한 내용은 프로세스 및 스레드 개요를 참고하세요.
다음 조건 중 하나가 발생하면 앱과 관련한 ANR이 트리거됩니다.
- 입력 전달 타임아웃: 앱이 입력 이벤트 (예: 키 누름 또는 화면 터치)에 5초 이내에 응답하지 않은 경우
- 서비스 실행: 앱에서 선언한 서비스가 몇 초 이내에
Service.onCreate및Service.onStartCommand/Service.onBind실행을 완료할 수 없는 경우 Service.startForeground호출되지 않음: 앱이Context.startForegroundService를 사용하여 포그라운드에서 새 서비스를 시작했지만 서비스가 5초 내에startForeground를 호출하지 않은 경우- 인텐트 브로드캐스트:
BroadcastReceiver가 설정된 시간 내에 실행을 완료하지 못한 경우. 앱에 포그라운드 활동이 있는 경우 이 제한 시간은 5초입니다. JobScheduler상호작용:JobService가 몇 초 이내에JobService.onStartJob또는JobService.onStopJob에서 반환되지 않거나 사용자 시작 작업이 시작되고JobService.onStartJob이 호출된 후 몇 초 이내에 앱이JobService.setNotification을 호출하지 않는 경우. Android 13 이하를 타겟팅하는 앱의 경우 ANR은 자동이며 앱에 보고되지 않습니다. Android 14 이상을 타겟팅하는 앱의 경우 ANR은 명시적이며 앱에 보고됩니다.
앱에서 ANR이 발생하는 경우 이 문서의 안내를 사용하여 문제를 진단하고 해결할 수 있습니다.
문제 감지
이미 앱을 게시했다면 Android vitals를 사용하여 앱의 ANR에 관한 정보를 확인할 수 있습니다. 다른 도구를 사용하여 필드에서 ANR을 감지할 수 있지만 서드 파티 도구는 Android vitals와 달리 Android 10 이하에서 ANR을 보고할 수 없습니다.
Android vitals
Android vitals를 사용하면 앱의 ANR 발생률을 모니터링하고 개선할 수 있습니다. Android vitals는 다음과 같은 ANR 발생률을 측정합니다.
- ANR 발생률: 모든 유형의 ANR을 경험한 일일 활성 사용자의 비율입니다.
- 사용자 인식 ANR 발생률: 한 번 이상 사용자 인식 ANR 을 경험한 일일 활성 사용자의 비율입니다. 현재
Input dispatching timed out유형의 ANR만 사용자 인식으로 간주됩니다. - 다중 ANR 발생률: ANR을 두 번 이상 경험한 일일 활성 사용자의 비율입니다.
일일 활성 사용자 는 하루 동안 단일 기기에서 앱을 사용하는 고유한 사용자이며 여러 세션에 걸쳐 사용할 수 있습니다. 사용자가 하루에 두 개 이상의 기기에서 앱을 사용하는 경우 각 기기가 해당 날짜의 활성 사용자 수에 기여합니다.
사용자 인식 ANR 발생률은 핵심 vitals에 속하는 요소로, Google Play에서 앱의 검색 가능 여부에 영향을 미칩니다. 사용자 인식 ANR 발생률에 집계되는 ANR은 항상 사용자가 앱을 사용할 때 발생하여 가장 큰 지장을 유발하므로 중요합니다.
Play에서는 이 측정항목에 대해 비정상적인 동작 기준 두 가지를 정의했습니다.
- 전반적 비정상적인 동작 기준: 일일 활성 사용자의 0.47% 이상이 모든 기기 모델에서 사용자 인식 ANR을 경험합니다.
- 기기별 비정상적인 동작 기준: 일일 사용자의 8% 이상이 단일 기기 모델 에서 사용자 인식 ANR을 경험합니다.
앱이 전반적 비정상적인 동작 기준을 초과하면 모든 기기에서 검색 가능성이 낮아질 수 있습니다. 앱이 일부 기기에서 기기별 비정상적인 동작 기준을 초과하면 해당 기기에서 검색 가능성이 낮아질 수 있으며 스토어 등록정보에 경고가 표시될 수도 있습니다.
앱에서 ANR이 과도하게 발생하면 Android vitals에서 Play Console을 통해 알림을 보낼 수 있습니다.
Google Play에서 Android vitals 데이터를 수집하는 방법에 관한 자세한 내용은 Play Console 문서를 참고하세요.
ANR 진단
ANR을 진단할 때 다음과 같은 일반 패턴을 찾아야 합니다.
- 앱이 기본 스레드에서 I/O와 관련된 느린 작업을 실행 중입니다.
- 앱이 기본 스레드에서 긴 계산을 실행 중입니다.
- 기본 스레드에서 다른 프로세스에 관한 동기 바인더 호출을 실행 중이고 다른 프로세스가 반환하는 데 오랜 시간이 걸립니다.
- 다른 스레드에서 발생하는 긴 작업을 위해 동기화된 블록을 대기하는 동안 기본 스레드가 차단되었습니다.
- 기본 스레드가 프로세스에서 또는 바인더 호출을 통해 다른 스레드와 교착 상태에 있습니다. 기본 스레드가 긴 작업이 완료될 때까지 대기하는 것만이 아니라 교착 상태에 있습니다.
다음 기법을 통해 ANR의 원인을 확인할 수 있습니다.
HealthStats
HealthStats 는 총 사용자 및 시스템 시간, CPU 시간, 네트워크, 무선 통계, 화면 켜짐/꺼짐 시간, 깨우기 알람을 캡처하여 애플리케이션의 상태에 관한 측정항목을 제공합니다. 이렇게 하면 전체 CPU 사용량과 배터리 소모를 측정하는 데 도움이 됩니다.
디버그
Debug 는 앱의 버벅거림 및 지연을 식별하는 추적 및 할당 수를 포함하여 개발 중에 Android 애플리케이션을 검사하는 데 도움이 됩니다.
Debug를 사용하여 런타임 및 네이티브 메모리 카운터, 특정 프로세스의 메모리 사용량을 식별하는 데 도움이 될 수 있는 메모리 측정항목을 얻을 수도 있습니다.
ApplicationExitInfo
ApplicationExitInfo 는 Android 11 (API 수준 30) 이상에서 사용할 수 있으며 애플리케이션 종료 이유에 관한 정보를 제공합니다. 여기에는 ANR, 메모리 부족, 앱 비정상 종료, 과도한 CPU 사용, 사용자 중단, 시스템 중단, 런타임 권한 변경이 포함됩니다.
엄격 모드
StrictMode를 사용하면 앱을 개발하는 동안 기본
스레드에서 실수로 인한 I/O 작업을 찾을 수 있습니다. StrictMode을(를) 애플리케이션 또는 활동 수준에서 사용할 수 있습니다.
백그라운드 ANR 대화상자 사용 설정
Android는 기기의 개발자 옵션 에서 모든 ANR 표시 가 사용 설정된 경우에만 브로드캐스트 메시지를 처리하는 데 너무 오래 걸리는 앱에 관한 ANR 대화상자를 표시합니다. 따라서 앱에 성능 문제가 발생하고 있더라도 백그라운드 ANR 대화상자가 항상 사용자에게 표시되지는 않습니다.
리컴포지션 병목 현상
Android 스튜디오 프로파일러와 레이아웃 검사기를 사용하여 재구성 병목 현상을 추적하세요. 자세한 내용은 Jetpack Compose 성능을 참고하세요.
트레이스 파일 가져오기
Android에서는 ANR이 발생할 때 트레이스 정보를 저장합니다. 이전 OS 버전에는 기기에 하나의 /data/anr/traces.txt 파일이 있습니다. 최신 OS 버전에는 여러 /data/anr/anr_* 파일이 있습니다. 루트로 Android 디버그 브리지 (adb)를 사용하여 기기 또는 에뮬레이터에서 ANR
트레이스에 액세스할 수 있습니다.
adb root
adb shell ls /data/anr
adb pull /data/anr/<filename>
기기에서 버그 신고 개발자 옵션을 사용하거나 개발 머신에서 adb bugreport 명령어를 사용하여 실제 기기에서 버그 신고를 캡처할 수 있습니다. 자세한 내용은
버그 신고 캡처 및 읽기를 참고하세요.
문제 해결
문제를 식별한 후 이 섹션의 도움말을 사용하여 자주 발생하는 문제를 해결할 수 있습니다.
기본 스레드의 느린 코드
앱의 기본 스레드를 5초 이상 사용 중인 코드의 위치를 확인하세요. 앱에서 의심스러운 사용 사례를 찾아 ANR을 재현해 보세요.
일반적인 문제는 구성 가능한 항목에서 직접 실행되는 장기 실행 작업입니다.
@Composable
fun BadList(rawStrings: List<String>) {
// Math or sorting inside the composable runs on EVERY recomposition pass!
val heavilyProcessedList = rawStrings
.filter { it.isNotBlank() }
.map { it.uppercase().reversed() }
.map { it.computationallyHeavyFunction() }
.sortedBy { it.length }
LazyColumn { items(sortedList) { Text(it) } }
}
// Modern Compose-first fix
@Composable
fun GoodList(viewModel: MyViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// UI simply renders state; no heavy processing allowed here
LazyColumn { items(uiState.sortedData) { Text(it) } }
}
기본 스레드의 I/O
기본 스레드에서 I/O 작업을 실행하는 것은 기본 스레드의 작업이 느려지는 일반적인 원인이며 이로 인해 ANR이 발생할 수 있습니다. Compose에서 개발자는 초기 상태를 파생하려고 할 때 실수로 디스크 읽기 (SharedPreferences 또는 데이터베이스 호출)를 트리거하는 경우가 많습니다.
UI 레이어에서 멀리 떨어진 곳에서 장기 실행 I/O 작업을 실행하세요. ViewModel에서
withContext(Dispatchers.IO)를 사용하거나 데이터 레이어에서
Repository를 사용하는 것이 좋습니다.
교착 상태
한 스레드에서 필요한 리소스를 다른 스레드가 보유하고 있고, 다른 스레드 역시 첫 번째 스레드가 보유 중인 리소스를 대기하고 있어 스레드가 대기 상태가 되는 경우 교착 상태가 발생합니다. 앱의 기본 스레드가 이 상황이면 ANR이 발생할 가능성이 큽니다.
교착 상태는 컴퓨터 공학에서 철저히 연구된 현상으로, 교착 상태 방지 알고리즘을 사용하여 교착 상태를 방지할 수 있습니다.
자세한 내용은 위키백과의 교착 상태 및 교착 상태 방지 알고리즘을 참고하세요.
Kotlin 및 Compose를 사용하는 경우 기본 잠금을
비차단 코루틴 뮤텍스 (Mutex.withLock)로 대체하여 UI
스레드를 고정하는 대신 실행 컨텍스트를 정지하여 스레드
차단을 방지할 수 있습니다. 예를 들면 다음과 같습니다.
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
// Modern non-blocking concurrency state architecture
class SecureDataRepository {
private val mutex = Mutex()
suspend fun safeUIAccess() {
// If locked, the main thread suspends seamlessly, preventing an ANR
mutex.withLock {
performSafeOperation()
}
}
}
느린 broadcast receiver
앱은 broadcast receiver를 사용하여 비행기 모드 사용 설정 또는 중지나 연결 상태 변경과 같은 브로드캐스트 메시지에 응답할 수 있습니다. 앱이 브로드캐스트 메시지를 처리하는 데 너무 오래 걸리면 ANR이 발생합니다.
다음과 같은 경우에 ANR이 발생합니다.
- broadcast receiver가 상당한 시간 내에
onReceive메서드 실행을 완료하지 못했습니다. - broadcast receiver가
goAsync를 호출하고finish를 호출하지 못합니다.PendingResult객체에서
앱은 onReceive 메서드
에서 짧은 작업만 실행해야 합니다BroadcastReceiver. 하지만 방송 메시지로 인해 앱에 더 복잡한 처리가 필요하다면 작업이 최대 몇 초 정도 걸릴 것으로 예상되는 경우 작업을 ViewModel (Kotlin 코루틴, 범위, 디스패처의 기능 활용) 또는 모든 유형의 상태 홀더로 이전해야 하며, 작업이 몇 초 이상 걸릴 것으로 예상되는 경우 WorkManager로 이전해야 합니다.
GameActivity
GameActivity 라이브러리는 C 또는 C++로 작성된 게임과 앱의 우수사례를 보면 ANR이 줄었습니다. 기존 네이티브
액티비티를 GameActivity로 교체한다면 UI 스레드 차단을 줄이고 ANR이 발생하는 것을 어느 정도 막을 수 있습니다.
ANR에 관한 자세한 내용은 앱 응답성 유지를 참고하세요. 스레드에 관한 자세한 내용은 스레딩을 통한 성능 개선을 참고하세요.
추가 리소스
콘텐츠 보기
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- 불필요한 wakeup