본문 바로가기
안드로이드

[안드로이드] uncaught exception 사용하기 (UncaughtExceptionHandler)

by 호군 2012. 10. 2.
반응형





question : how to catch Error globally on android?


배경

안드로이드 앱을 개발하고, 앱을 배포를 합니다. 하지만 고려하지 않던 Exception이 발생하면 앱은 죽게됩니다. 왜 죽었는지 오류를 수집하지 않는다면, 앱의 인지도는 떨어지기 마련입니다. 그렇다면 고려하지 않던 Exception이 발생하면 어떻게 해야할까요?




고려하지 않던 예외를 잡아주는 UncaughtExceptionHandler 클래스

이 기능은 자바에서 제공해주는 기능입니다. 사용되는 클래스는 Thread 클래스와 UncaughtExceptionHandler 클래스 입니다. 

아래는 예제로 작성한 CustomUncaughtExceptionHandler 클래스 입니다.


[CustomUncaughtExceptionHandler]


public class CustomUncaughtExceptionHandler implements UncaughtExceptionHandler {

    private Context mContext;

    private UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;


    public CustomUncaughtExceptionHandler(Context context) {

        mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();

        mContext = context;

    }


    //@Override

    public void uncaughtException(Thread thread, Throwable ex) {

        final String logMsgParams = makeStackTrace(thread, ex);


        //TODO: to process exception for reporting.

        

        callDefaultUncaughtExceptionHandler(thread, ex);

    }


    private static String makeStackTrace(Thread thread, Throwable ex) {

        StringBuilder errLog = new StringBuilder();

        errLog.append("FATAL EXCEPTION: " + thread.getName());

        errLog.append("\n");


        errLog.append(ex.toString());

        errLog.append("\n");


        StackTraceElement[] stack = ex.getStackTrace();

        for (StackTraceElement element : stack) {

            errLog.append("    at " + element);

            errLog.append("\n");

        }


        StackTraceElement[] parentStack = stack;

        Throwable throwable = ex.getCause();

        while (throwable != null) {

            errLog.append("Caused by: ");

            errLog.append(throwable.toString());

            errLog.append("\n");


            StackTraceElement[] currentStack = throwable.getStackTrace();

            int duplicates = countDuplicates(currentStack, parentStack);

            for (int i = 0; i < currentStack.length - duplicates; i++) {

                errLog.append("    at " + currentStack[i].toString());

                errLog.append("\n");

            }

            if (duplicates > 0) {

                errLog.append("    ... " + duplicates + " more");

            }

            parentStack = currentStack;

            throwable = throwable.getCause();

        }


        return errLog.toString();

    }


    private static int countDuplicates(StackTraceElement[] currentStack, StackTraceElement[] parentStack) {

        int duplicates = 0;

        int parentIndex = parentStack.length;

        for (int i = currentStack.length; --i >= 0 && --parentIndex >= 0;) {

            StackTraceElement parentFrame = parentStack[parentIndex];

            if (parentFrame.equals(currentStack[i])) {

                duplicates++;

            } else {

                break;

            }

        }

        return duplicates;

    }


    private void callDefaultUncaughtExceptionHandler(Thread thread, Throwable ex) {

        if (mDefaultUncaughtExceptionHandler != null) {

            mDefaultUncaughtExceptionHandler.uncaughtException(thread, ex);

        }

    }

}


[CustomActivity]


public class CustomActivity {

    ...

    public void onCreate(Bundle savedInstanceState) {

        UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();

        if ( !(handler instanceof CustomUncaughtExceptionHandler) ) {

            Thread.setDefaultUncaughtExceptionHandler(new CustomUncaughtExceptionHandler(context));

            Log.i("TEST", "registered CustomUncaughtExceptionHandler");

        }


        super.onCreate(savedInstanceState);

        ...

    }

    ...

}




잡을 수 없는 java.lang.Error 클래스들

Thread.setDefaultUncaughtExceptionHandler()를 사용한다고해도 java.lang.Error 클래스의 서브 클래스들은 잡아낼 수가 없습니다. UncaughtExceptionHandler의 uncaughtException(Thread t, Throwable ex)를 보면 Throwable 타입으로 넘겨줘서 Error 클래스도 받을 것 같아 보였지만, 아무리해도 Error는 잡을 수 없더군요. 

그래서 Runtime.addShutdownHook(Thread hook)을 사용해서 혹시나 잡을 수 있지 않을까 했지만, 이녀석은 종료가 됐는데도 호출이 되지 않더군요. 안드로이드 개발문서를 보면, 안드로이드 라이프사이클은 VM종료를 포함하지 않는다고 하면서 호출을 보장하지 않는다고 합니다. 


Log를 통해서 확인해 본 결과, uncaughtException에 OutOfMemoryError가 들어오기는 하는군요. 즉, catch를 하기는 합니다. 하지만 내부에 구현된 여러 기능이 작동을 안하는군요. 좀 더 테스트 해본 후 다시 올리겠습니다.


해결방법을 알려줘!!!


정상적으로 동작합니다.



기본 UncaughtExceptionHandler

개발자가 Thread.setDefaultUncaughtExceptionHandler()를 사용하여 변경하기 전의 기본 Handler는 RuntimeInit$UncaughtHandler 입니다.

이 Handler 역시 Thread.UncaughtExceptionHandler를 상속 받고 있습니다.



printStackTrace 만드는 형식을 보고 싶다면...

Throwable$printStackTrace() 를 확인해보세요.

Throwable.java 파일은 dalvik/libcore/luni-kernel/src/main/java/java/lang/ 위치에 있습니다.





반응형