Android에서 비정상 종료를 탐지하는 방법

지프치프·2023년 5월 27일
0

Android

목록 보기
68/85
post-thumbnail

“Android 로봇은 Google에서 제작하여 공유한 저작물을 복제하거나 수정한 것으로 Creative Commons 3.0 저작자 표시 라이선스의 약관에 따라 사용되었습니다.”


개요

Android 앱을 개발하다보면 exception이 발생하여
앱이 비정상적으로 종료될 때가 있다.
흔히 말하는 앱이 죽는다고 말하는데 앱이 종료되는 시점에 처리되어야하는 코드가 있다면 이 경우엔 onDestroy()나 Service의 onTaskRemoved()가 호출되지 않고 앱이 바로 종료되어버리기 때문에 상당히 난감하다.

방법이 없는 것은 아니니 아래를 살펴보자

UncaughtExceptionHandler

Java SDK에서는 UncaughtExceptionHandler라는 인터페이스를 제공한다.

    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

Thread.java에 정의되어있는 인터페이스로 uncaughtException()메소드가 정의되어있다.
이 메소드의 주석을 살펴보자면 아래와 같다.

처리되지 않은 exception이 발생하여 스레드가 종료될 때 이 메소드가 호출된다.

즉, try-catch로 핸들링 되지않는 exception이 발생하여 스레드가 종료되면 이 메소드가 호출되어 앱이 종료되기 전에 앱의 핸들링이 가능하다.

Thread.setDefaultUncaughtExceptionHandler

Thread 클래스에서 정적 메소드로 처리 코드를 등록할 수 있도록 제공한다.

이렇게 등록된 핸들러는 아래의 ThreadGroup.java에서 재정의된
uncaughtException()을 통해 호출된다.

이제 샘플을 작성해보자
필자는 Application클래스에서 작성했지만
MainActivity의 onCreate에서 작성해도 무방할듯 하다.

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        Log.e(javaClass.simpleName, "onCreate()")

        Thread.setDefaultUncaughtExceptionHandler { t, e ->
            Log.e(javaClass.simpleName, """
                UncaughtExceptionHandler
                Thread info: ${t.toString()}
                Exception info: ${e.toString()}
            """.trimIndent())
        }
    }
}

UncaughtExceptionHandler가 하나의 메소드만 정의되어 있기 때문에 람다식으로 전달이 가능하다.
매개변수는

  • t: exception이 발생한 스레드
  • Throwable: exception의 내용

그리고 Activity에서 버튼을 눌러서 임의로 Exception을 발생시키도록 하였다.

binding.btnCreate.setOnClickListener {
    throw Exception("text exception !!")
}

이제 앱을 실행하고 버튼을 클릭하여 Exception을 던져보면..

위와 같이 핸들링되지 않는 exception이 발생했을 때 log가 출력되는 것을 볼 수 있다.
처리해줘야할 코드가 있다면 여기서 작성하면 동작할 것이다.

주의할 점

다만 주의할 점이 있는데 필자가 느끼기론 2가지 정도가 있다.

시스템에서 StackTrace를 제공하지 않는다.


보통 exception이 발생하면 위와 같이 시스템에서 제공해주는 StackTrace가 발생하지 않는다.
그렇기 때문에 log를 통해 StackTrace를 출력해주는 코드를 추가해주어야 한다.

        Thread.setDefaultUncaughtExceptionHandler { t, e ->
            Log.e(javaClass.simpleName, """
                UncaughtExceptionHandler
                Thread info: ${t.toString()}
                Exception info: ${e.toString()}
                StackTrace:
                ${Log.getStackTraceString(e)}
            """.trimIndent())
        }

ANR이 발생한다.

Exception이 발생 후 UncaughtExceptionHandler가 호출되면 앱이 멈추게 된다. 아마 스레드가 종료 되기 전에 호출되기 때문에 스레드가 blocking 되어 ANR이 발생한 것이 아닌가 싶지만..
아무튼 사용자 입장에선 굉장히 부자연스럽기 때문에 명시적으로 프로세스 종료를 시켜줘야 한다.

        Thread.setDefaultUncaughtExceptionHandler { t, e ->
            Log.e(javaClass.simpleName, """
                UncaughtExceptionHandler
                Thread info: ${t.toString()}
                Exception info: ${e.toString()}
                StackTrace:
                ${Log.getStackTraceString(e)}
            """.trimIndent())

            // 명시적 종료를 위한 killProcess() 호출
            android.os.Process.killProcess(android.os.Process.myPid())
        }

-- Blog footer --
개인적으로 공부했던 것을 바탕으로 작성하다보니
잘못된 정보가 있을수도 있습니다.
인지하게 되면 추후 수정하겠습니다.
피드백은 언제나 환영합니다.
읽어주셔서 감사합니다.

profile
지프처럼 거침없는 개발을 하고싶은 개발자

0개의 댓글