최근에 안드로이드에서 글로벌한 방법으로 에러를 처리하는 방법에는 어떤 것이 있는지 찾아보다가 우연히 java.lang.Thread.UncaughtExceptionHandler 인터페이스에 대해서 알게됐습니다. 


Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); 


public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {

        @Override

        public void uncaughtException(Thread thread, final Throwable ex) {

           // 여기서 에러를 처리

        }

}


위와 같은 코드를 통해서 캐치되지 않은 런타임 exception을 일관된 방식으로 한곳에서 처리할 수 있도록 JDK 5.0부터 추가된 것이더군요.


안드로이드에서도 위 방법을 사용하는 것이 가능한데요, UncaughtExceptionHandler 구현시 도움이 될만한 팁을 공유합니다.


1. uncaughtException() 메소드의 맨 마지막에는 반드시 System.exit()를 호출해야 한다. 

그렇지 않으면 앱은 죽은 것도 아니고 작동하는 것도 아닌 불확실한 상태가 됩니다(백그라운드 쓰레드에서 던져진 exception인 경우에는 예외???).


http://stackoverflow.com/questions/13416879/show-a-dialog-in-thread-setdefaultuncaughtexceptionhandler

http://stackoverflow.com/questions/11281182/setdefaultuncaughtexceptionhandler-makes-app-crash-silently


참고로, ACRA의 경우에는 아래와 같은 코드를 사용하고 있습니다.


android.os.Process.killProcess(android.os.Process.myPid());

System.exit(10);



2. Toast를 띄우려면 명시적으로 UI쓰레드에서 띄워야 한다. 

그리고 사용자가 토스트 메시지를 읽을 수 있게 잠깐 쓰레드를 쉬는 것이 좋습니다.


http://stackoverflow.com/questions/11609640/toast-not-showing-up-in-uncaughtexceptionhandler


new Thread() {

@Override

public void run() {

                // UI쓰레드에서 토스트 뿌림

Looper.prepare();

Toast.makeText(getApplicationContext(), "에러메시지", Toast.LENGTH_SHORT)

.show();

Looper.loop();

}

}.start();


// 쓰레드 잠깐 쉼

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

}


 android.os.Process.killProcess(android.os.Process.myPid());

 System.exit(10);



3. Dialog를 띄우려면 Dialog 테마를 가진 새로운 액티비티를 시작하면 된다.

(잘 안되는 경우 ACRA 소스를 참조. Intent.FLAG_ACTIVITY_NEW_TASK 플래그 추가하여 액티비티 시작). 


http://stackoverflow.com/questions/13416879/show-a-dialog-in-thread-setdefaultuncaughtexceptionhandler

http://lky1001.tistory.com/45


Intent crashedIntent = new Intent(getApplicationContext(), MyErrorMessageDialogActivity.class);

crashedIntent.putExtra("error.message", "에러메시지");

crashedIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

startActivity(crashedIntent);


<activity

android:name=".MyErrorMessageDialogActivity"

android:excludeFromRecents="true"

android:finishOnTaskLaunch="true"

android:launchMode="singleInstance"

android:theme="@android:style/Theme.Dialog" >

</activity>


그런데 Toast 띄우는 것과는 다르게 Dialog 띄우는 것은 exception이 UI 쓰레드에서 던져진 것인지 백그라운드 쓰레드에서 던져진 것인지에 따라 다르게 처리해주어야 할 것으로 보입니다. 

실제로 ACRA 라이브러리의 경우 ReportingInteractionMode.DIALOG 모드로 설정했더라도 백그라운드 쓰레드에서 발생한 에러에 대해서는 dialog가 띄워지지 않았습니다(System.exit() 때문?)


예를들어, UI쓰레드에서 던져진 exception인 경우에는 현재 액티비티를 바로 종료하고, 백그라운드 쓰레드에서 던져진 경우에는 현재 액티비티 화면을 그대로 살려두고 싶다면 아래와 같이 System.exit()를 선택적으로 호출할 수 있습니다.

if (Thread.currentThread() == Looper.getMainLooper().getThread() ) {
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}


4. 앱 종료된 후 앱을 자동으로 재시작하고 싶으면 AlarmManager를 이용한다.

http://www.kmshack.kr/277#.UZCNP4Ez3E0

http://stackoverflow.com/questions/8943288/how-to-implement-uncaughtexception-android




신고
Posted by 에코지오

댓글을 달아 주세요

  1. 임성묵 2014.03.25 15:07 신고  댓글주소  수정/삭제  댓글쓰기

    정보 공유 감사합니다 ^^


The Joel Test: 나은 코딩을 위한 12단계








TODO : 추가

신고
Posted by 에코지오

댓글을 달아 주세요

최근에 Sonar를 이용해서 회사에서 만든 안드로이드 앱 소스의 품질을 분석한 적이 있습니다.




나름 잘 만들었다고 생각하고 있었는데 규칙 위반사항이 꽤 많이 검출되어 놀랐었죠. 다행히 중복코드는 없었습니다. Major한 위반사항부터 고쳐나가다가 패키지간 의존성을 없애기 위해 어떻게 하면 좋을까 고민을 많이 했습니다. 단순히 다른 Activity를 명시적으로 클래스명으로 부르지 않고 인텐트필터를 이용해서 부른다고 패키지간 의존성이 명쾌히 해소되지는 않더군요. 

그러다가 EventBus라는 라이브러리를 발견했습니다. 


EventBus 홈페이지

https://github.com/greenrobot/EventBus 


EventBus 사용 예제 설명

http://awalkingcity.com/blog/2013/02/26/productive-android-eventbus/


EventBus 슬라이드 자료

http://www.slideshare.net/greenrobot/eventbus-for-android-15314813


Square에서 만든 Otto라는 원조(?) 라이브러리도 있지만 greenrobot의 EventBus가 왠지 더 땡기더군요. 사용법도 심플하고, 홈페이지에 나오는 Otto와의 비교자료가 마음을 굳히게 만들었죠. (바빠서 직접 비교 테스트는 패스~)


특히나 저희 앱에서는 로그인, 로그아웃 관련해서 단순히 세션을 조작하는데 그치지 않고 앱 내부적으로 다양한 일을 처리합니다. 그래서 로그인 모듈에서 로그인 완료후 다른 모듈들을 직접(!) 호출하는 방식으로 처리하고 있었는데, LoggedInEvent, LoggedOutEvent라는 걸 만들어서 이벤트버스 방식으로 변경했더니 모듈간 의존성도 없어지고 소스도 깔끔해지고 좋네요. 


물론 별별 상황에서 제대로 안정적으로 오류없이 작동하는지는 좀더 지켜봐야겠지만 현재까지는 만족하고 있습니다. 구글링해보면 사실 Otto가 더 많은 레퍼런스와 개발자의 지지를 받는 것으로 보입니다. Square 개발자의 기술력을 신뢰하기 때문으로 보이는데요, 여유가 생기면 Otto로도 재구현해서 테스트해볼 생각입니다. (EventBus의 컨벤션 방식과 비교해서 Otto의 어노테이션 방식도 나름 장점이 있기에...)


아직까지 EventBus 사용하면서 큰 이슈는 없었는데요 이런 점은 고려하셔야 합니다. 


(1) 이벤트를 수신하는 메소드가 컨벤션에 따라서 onEventMainThread(), onEventBackgroundThread() 와 같은 식이어서 만약 상속관계에 있는 클래스에서 동일한 메소드 시그너쳐를 쓴다면 부모 클래스의 메소드를 호출하는 것을 빼먹으면 안됩니다. 이런 경우는 Otto의 어노테이션 방식이 좀더 유연해보입니다.


(2) 이벤트를 어디서 던지고 어디에서 처리할 것인지 아키텍처적으로 정하는 일이 필요해보입니다. 그냥 아무데서나 이벤트 발생시키고 아무데서나 처리하는 것보다는 Presentation 레이어 던지고 처리한다거나, Biz 레이어에서 던지고 처리한다 처럼 어떤 규칙을 정해놓고 개발하는 것이 나을듯합니다.




신고
Posted by 에코지오

댓글을 달아 주세요



티스토리 툴바