Android ANR(App Not Responding) 분석
2019. 12. 9. 20:15
ANR(Application Not Responding)
- 어플리케이션이 응답하지 않는 경우 안드로이드 시스템에서 보여주는 에러. 애플리케이션 응답 없음.
- 일정 시간 충분한 속도로 응답하지 않는 경우 발생. Main Thread(일명, UI Thread)가 일정시간 동안 Hold되면 발생함.
- 언제 발생하는가?
- 입력 이벤트(Key, Touch)를 통한 사용자 입력이 5초 내에 처리되지 않았을 때
- BroadcastReceiver가 10초 내에 처리하지 못했을 때
- Service가 20초 내로 처리되지 않았을 때
- Activity Manager 및 Window Manager 시스템 서비스에서 애플리케이션 응답성을 모니터링 함.
ANR을 유발하는 원인
- Main Thread에서 I/O 작업
- Main Thread에서 복잡한 동작 구현(오랜 시간을 소비하는)
ANR을 피하는 방법
- 오랜 시간이 걸리는 작업을 수행하는 경우 Main Thread를 대신하는 Worker Thread를 활용
- 사용자에게 프로그래스바 등을 이용해 작업의 진행 과정을 안내
ANR 분석
- Easy to get and hard to fix
- Strict mode with debugging: 개발자 모드에서 Strict Mode enabled하면 애플리케이션에서 긴 작업을 Main Thread에서 진행할 경우 Flash Screen 현상이 발생
- Google Play: 구글 플레이에 Real-time ANRs를 통해 ANR을 확인
- Systrace
- Traceview: Android Studio 3.2의 경우 CPU Profiler
- dmtracedump with Graphviz
- Dumpstate:
- "ANR in" Filter
- SIG:9 시스템에서 ANR을 발생시키는 프로세스를 종료하기 위해 SIGNAL_KILL을 호출하면서 남기는 로그. 해당 로그의 전후를 확인
- "am_anr" Filter
- Detecting ANRs by Runnable
public class AnrSupervisor {
private ExecutorService anrExecutor = ExecutorService.newSingleThreadExecutor();
private final AnrSupervisorRunnable anrSupervisor = new AnrSupervisorRunnable();
public synchronized void start() {
synchronized (this.anrSupervisor) {
if (this.anrSupervisor.isStopped()) {
this.anrExecutor.execute(this.anrSupervisor);
} else {
this.anrSupervisor.unstop();
}
}
}
public synchronized void stop() {
this.anrSupervisor.stop();
}
}
private class AnrSupervisorRunnable implements Runnable {
private Handler handler = new Handler(Looper.getMainLooper());
private boolean isStopped;
private boolean isSupervisionStopped = true;
@Override
public void run() {
this.isRunnableStopped = false;
while (!Thread.interrupted()) {
try {
Log.d(AnrSupervisor.class.getSimpleName(), "Check for ANR...");
AnrSupervisorCallback callback = new AnrSupervisorCallback();
synchronized (callback) {
this.mHandler.post(callback);
callback.wait(1000);
if (!callback.isCalled()) {
AnrException e = new AnrException(this.mHandler.getLooper().getThread());
FirebaseCrash.report(e);
e.logProcessMap();
callback.wait();
} else {
Log.d(AnrSupervisor.class.getSimpleName(), "UI Thread responded within 1s");
}
}
this.checkStopped();
Thread.sleep(5000);
} catch (InterruptedException e) {
break;
}
}
Log.d(AnrSupervisor.class.getSimpleName(), "ANR supervision stopped");
this.isSupervisionStopped = true;
}
private synchronized void checkStopped() throws InterruptedException {
if (this.mStopped) {
Thread.sleep(1000);
if (this.mStopped) {
throw new InterruptedException();
}
}
}
synchronized void stop() {
Log.d(AnrSupervisor.class.getSimpleName(), "Stopping...");
this.isStopped = true;
}
synchronized void unstop() {
Log.d(AnrSupervisor.class.getSimpleName(), "Revert stopping...");
this.isStopped = false;
}
synchronized boolean isStopped() {
return this.isSupervisionStopped;
}
}
public class AnrException extends Exception {
public AnrException(Thread thread) {
super("ANR detected");
this.setStackTrace(thread.getStackTrace());
}
public void logProcessMap() {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(bos);
this.printProcessMap(ps);
Log.i(this.getClass().getSimpleName(), new String(bos.toByteArray()));
}
public void printProcessMap(PrintStream ps) {
Map<Thread, StackTraceElement[]> stackTraces = Thread.getAllStackTraces();
ps.println("Process map:");
for (Thread thread : stackTraces.keySet()) {
if (stackTraces.get(thread).length > 0) {
this.printThread(ps, Locale.getDefault(), thread, stackTraces.get(thread));
ps.println();
}
}
}
private void printThread(PrintStream ps, Locale l, Thread thread, StackTraceElement[] stack) {
ps.println(String.format(l, "\t%s (%s)", thread.getName(), thread.getState()));
for (StackTraceElement element : stack) {
ps.println(String.format(l, "\t\t%s.%s(%s:%d)",
element.getClassName(),
element.getMethodName(),
element.getFileName(),
element.getLineNumber()));
}
}
}
private class AnrSupervisorCallback implements Runnable {
private boolean isCalled;
public AnrSupervisorCallback() {
super();
}
@Override
public synchronized void run() {
this.isCalled = true;
this.notifyAll();
}
synchronized boolean isCalled() {
return this.isCalled;
}
}
public class AnrSupervisedActivity extends AppCompatActivity {
static final AnrSupervisor anrSupervisor = new AnrSupervisor();
public void onStart() {
super.onStart();
anrSupervisor.start();
}
public void onStop() {
super.onStop();
anrSupervisor.stop();
}
}
참고 문헌
- https://developer.android.com/topic/performance/vitals/anr
- https://developer.android.com/training/articles/perf-anr.html
- https://developer.android.com/reference/android/os/StrictMode.html
- https://developer.android.com/studio/profile/systrace?hl=ko
- https://developer.android.com/studio/profile/traceview?hl=ko
- https://medium.com/@cwurthner/detecting-anrs-e6139f475acb