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이 발생하면 이 자료의 안내를 사용하여 문제를 진단하고 해결할 수 있습니다.
문제 해결
문제를 식별한 후 이 섹션의 도움말을 사용하여 자주 발생하는 문제를 해결할 수 있습니다.
기본 스레드의 느린 코드
앱의 기본 스레드를 5초 이상 사용 중인 코드의 위치를 확인하세요. 앱에서 의심스러운 사용 사례를 찾아 ANR을 재현해 보세요.
예를 들어 그림 2는 기본 스레드를 5초 이상 사용 중인 Traceview 타임라인을 보여줍니다.

그림 2. 사용량이 많은 기본 스레드를 보여주는 Traceview 타임라인
그림 2는 다음 코드 예에서와 같이 대부분의 문제 코드가
onClick(View) 핸들러에서 발생하는 것을 보여줍니다.
Kotlin
override fun onClick(v: View) {
// This task runs on the main thread.
BubbleSort.sort(data)
}
자바
@Override
public void onClick(View view) {
// This task runs on the main thread.
BubbleSort.sort(data);
}
이 경우 기본 스레드에서 실행되는 작업을 작업자 스레드로 이동해야 합니다. Android 프레임워크에는 작업을 작업자 스레드로 이동하는 데 도움이 되는 클래스가 포함되어 있습니다. 자세한 내용은 작업자 스레드를 참고하세요.
기본 스레드의 I/O
기본 스레드에서 I/O 작업을 실행하는 것은 기본 스레드의 작업이 느려지는 일반적인 원인이며 이로 인해 ANR이 발생할 수 있습니다. Compose에서 개발자는 초기 상태를 파생하려고 할 때 실수로 디스크 읽기 (SharedPreferences 또는 데이터베이스 호출)를 트리거하는 경우가 많습니다.
UI 레이어에서 멀리 떨어진 곳에서 장기 실행 I/O 작업을 실행합니다.
withContext(Dispatchers.IO)에서 ViewModel를 사용하거나 데이터 레이어에서 저장소
를 사용하는 것이 좋습니다. 이전 섹션에 설명한 것처럼 모든 I/O 작업을 작업자 스레드로 이동하는 것이 좋습니다.
I/O 작업의 예로는 네트워크 및 저장소 작업이 있습니다. 자세한 내용은 네트워크 작업 실행 및 데이터 저장을 참고하세요.
잠금 경합
일부 시나리오에서는 ANR을 유발하는 작업이 앱의 기본 스레드에서 직접 실행되지 않습니다. 작업자 스레드가 기본 스레드에서 작업을 완료하는 데 필요한 리소스의 잠금을 유지하는 경우 ANR이 발생할 수 있습니다.
예를 들어 그림 3에서는 대부분의 작업이 작업자 스레드에서 실행되는 Traceview 타임라인을 보여줍니다.

그림 3. 작업자 스레드에서 실행 중인 작업을 보여주는 Traceview 타임라인
하지만 사용자에게 ANR이 계속 발생한다면 Android Device Monitor에서 기본 스레드의 상태를 확인해야 합니다. UI를 업데이트할 준비가 되어 있고 일반적으로
반응하는 기본 스레드는 대체로
RUNNABLE 상태입니다.
그러나 실행을 계속할 수 없는 기본 스레드는 BLOCKED
상태이며 이벤트에 응답할 수 없습니다. 그림 5에서와 같이 Android Device Monitor에 상태가
Monitor 또는 Wait로 표시됩니다.

그림 4. Monitor 상태의 기본 스레드
다음 트레이스는 리소스 대기 중 차단된 기본 스레드를 보여줍니다.
...
AsyncTask #2" prio=5 tid=18 Runnable
| group="main" sCount=0 dsCount=0 obj=0x12c333a0 self=0x94c87100
| sysTid=25287 nice=10 cgrp=default sched=0/0 handle=0x94b80920
| state=R schedstat=( 0 0 0 ) utm=757 stm=0 core=3 HZ=100
| stack=0x94a7e000-0x94a80000 stackSize=1038KB
| held mutexes= "mutator lock"(shared held)
at com.android.developer.anrsample.BubbleSort.sort(BubbleSort.java:8)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:147)
- locked <0x083105ee> (a java.lang.Boolean)
at com.android.developer.anrsample.MainActivity$LockTask.doInBackground(MainActivity.java:135)
at android.os.AsyncTask$2.call(AsyncTask.java:305)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
...
트레이스를 검토하면 기본 스레드를 차단하는 코드를 찾는 데 도움이 될 수 있습니다. 다음 코드는 이전 트레이스에서 기본 스레드를 차단하는 잠금을 유지합니다.
Kotlin
override fun onClick(v: View) {
// The worker thread holds a lock on lockedResource
LockTask().execute(data)
synchronized(lockedResource) {
// The main thread requires lockedResource here
// but it has to wait until LockTask finishes using it.
}
}
class LockTask : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? =
synchronized(lockedResource) {
// This is a long-running operation, which makes
// the lock last for a long time
BubbleSort.sort(params[0])
}
}
자바
@Override
public void onClick(View v) {
// The worker thread holds a lock on lockedResource
new LockTask().execute(data);
synchronized (lockedResource) {
// The main thread requires lockedResource here
// but it has to wait until LockTask finishes using it.
}
}
public class LockTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (lockedResource) {
// This is a long-running operation, which makes
// the lock last for a long time
BubbleSort.sort(params[0]);
}
}
}
또 다른 예는 다음 코드에서와 같이 작업자 스레드의 결과를 대기 중인 기본 스레드입니다. 동시 실행을 처리하기 위한 고유한 메커니즘이 있는 Kotlin에서 wait() 및 notify() 사용은 권장되는 패턴이 아닙니다. Kotlin을 사용할 때는 가능하면 Kotlin 전용 메커니즘을 사용해야 합니다.
Kotlin
fun onClick(v: View) {
val lock = java.lang.Object()
val waitTask = WaitTask(lock)
synchronized(lock) {
try {
waitTask.execute(data)
// Wait for this worker thread's notification
lock.wait()
} catch (e: InterruptedException) {
}
}
}
internal class WaitTask(private val lock: java.lang.Object) : AsyncTask<Array<Int>, Int, Long>() {
override fun doInBackground(vararg params: Array<Int>): Long? {
synchronized(lock) {
BubbleSort.sort(params[0])
// Finished, notify the main thread
lock.notify()
}
}
}
자바
public void onClick(View v) {
WaitTask waitTask = new WaitTask();
synchronized (waitTask) {
try {
waitTask.execute(data);
// Wait for this worker thread’s notification
waitTask.wait();
} catch (InterruptedException e) {}
}
}
class WaitTask extends AsyncTask<Integer[], Integer, Long> {
@Override
protected Long doInBackground(Integer[]... params) {
synchronized (this) {
BubbleSort.sort(params[0]);
// Finished, notify the main thread
notify();
}
}
}
리소스 풀
(예: 데이터베이스 연결 풀) 또는 기타 상호 배제 (뮤텍스)
메커니즘뿐만 아니라 Lock, Semaphore를 사용하는 스레드를 포함하여 기본 스레드를 차단할 수 있는 몇 가지 다른 상황이 있습니다.
앱에서 유지하는 리소스 잠금을 전체적으로 평가해야 하지만 ANR을 피하려면 기본 스레드에서 필요한 리소스에 유지되는 잠금을 확인해야 합니다.
잠금이 최소 시간 동안 유지되었는지 확인하거나, 더 나은 방법으로는 애초에 앱에 잠금 유지가 필요한지 평가합니다. 작업자 스레드 처리를 기반으로 UI를 업데이트하는 시기를 알아내기 위해
잠금을 사용한다면 작업자 스레드와 기본 스레드 간에 통신하는 데 onProgressUpdate() 및 onPostExecute()와 같은 메커니즘을 사용합니다.
느린 broadcast receiver
앱은 broadcast receiver를 사용하여 비행기 모드 사용 설정 또는 중지나 연결 상태 변경과 같은 브로드캐스트 메시지에 응답할 수 있습니다. 앱이 브로드캐스트 메시지를 처리하는 데 너무 오래 걸리면 ANR이 발생합니다.
다음과 같은 경우에 ANR이 발생합니다.
- broadcast receiver가 상당한 시간 내에
onReceive메서드 실행을 완료하지 못했습니다. - broadcast receiver가
goAsync를 호출하고finish를 호출하지 못합니다.PendingResult객체에서
앱은 onReceive 메서드에서
짧은 작업만 실행해야 합니다BroadcastReceiver. 하지만 브로드캐스트 메시지로 인해 앱에 더 복잡한
처리가 필요하다면 작업이 최대 몇 초 정도 걸릴 것으로 예상되는 경우 작업을
ViewModel (Kotlin 코루틴, 범위, 디스패처의 기능 활용)
또는 모든 유형의 상태 홀더로 이전하고
작업이 몇 초 이상 걸릴 것으로 예상되는 경우 WorkManager로 이전해야 합니다.
Traceview와 같은 도구를 사용하면 broadcast receiver가 앱의 기본 스레드에서 장기 실행 작업을 실행하는지 확인할 수 있습니다. 예를 들어 그림 6에서는 약 100초 동안 기본 스레드에서 메시지를 처리하는 broadcast receiver의 타임라인을 보여줍니다.

그림 5. 기본 스레드의 BroadcastReceiver 작업을 보여주는 Traceview 타임라인
다음 예에서와 같이 BroadcastReceiver의
onReceive() 메서드에서 장기 실행 작업을 실행하면 이 동작이 발생할 수 있습니다.
Kotlin
override fun onReceive(context: Context, intent: Intent) {
// This is a long-running operation
BubbleSort.sort(data)
}
자바
@Override
public void onReceive(Context context, Intent intent) {
// This is a long-running operation
BubbleSort.sort(data);
}
이 같은 상황에서는 장기 실행 작업을
IntentService로 이동하는 것이 좋습니다. 이 클래스는 작업자 스레드를 사용하여 작업을 실행하기 때문입니다.
다음 코드는 IntentService를 사용하여
장기 실행 작업을 처리하는 방법을 보여줍니다.
Kotlin
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)
}
}
자바
@Override
public void onReceive(Context context, Intent intent) {
// The task now runs on a worker thread.
Intent intentService = new Intent(context, MyIntentService.class);
context.startService(intentService);
}
public class MyIntentService extends IntentService {
@Override
protected void onHandleIntent(@Nullable Intent intent) {
BubbleSort.sort(data);
}
}
IntentService를 사용하면 장기 실행 작업이
기본 스레드 대신 작업자 스레드에서 실행됩니다. 그림 7은 Traceview 타임라인에서 작업자 스레드로 이전된 작업을 보여줍니다.

그림 6. 작업자 스레드에서 처리되는 방송 메시지를 보여주는 Traceview 타임라인
브로드캐스트 수신기는 goAsync()를 사용하여 메시지를 처리하는 데 시간이 더 필요하다고 시스템에 알릴 수 있습니다. 그러나 PendingResult 객체에서
finish()를 호출해야 합니다. 다음 예는 finish()를 호출하여 시스템에서 broadcast receiver를 재활용하고 ANR을 방지하도록 하는 방법을 보여줍니다.
Kotlin
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)
자바
final PendingResult pendingResult = goAsync();
new AsyncTask<Integer[], Integer, Long>() {
@Override
protected Long doInBackground(Integer[]... params) {
// This is a long-running operation
BubbleSort.sort(params[0]);
pendingResult.finish();
}
}.execute(data);
그러나 브로드캐스트가 백그라운드에 있다면 느린 broadcast receiver에서 다른 스레드로 코드를 이동하고
using goAsync()을 사용해도 ANR이 해결되지 않습니다.
ANR 시간 제한이 계속 적용됩니다.