안드로이드에서 앱 비정상 종료 시 예외(Exception)을 처리 할 수 있는 방법
일단, 내 생각에는 현재 에러를 낸 앱에서 비정상 종료가 일어 났을 경우 로그 파일을 저장하는 방법1이 필요하다고 생각한다.
그리고 라이브러리를 사용하거나 단순히 logcat를 호출해서 사용하기 위해서는 Exception이 일어 났을 경우에 어디에서 이 오류가 생성되었는 지, 그래서 내가 이것을 잡아(catch) 낼 수 있는 지에 대한 생각이 우선 시도 되어야 할 것이다. 우선 앱이 만들어 지면 이를 쌓아 놓고 분석 할 수 있는 공간 도 필요 해 질 텐데, 이를 위한 서버 시스템도 만들어야만 할 것이다. 물론 데이터베이스도 선정을 해야 할 것이고... 지금은 오직 안드로이드에 대해서만 생각 해본다면 다음과 같이 스택 오버 플로우에서는 내용2을 확인 할 수 있다.
위 아이디어의 github 3사이트는 아래와 같다.
그 외의 일반적으로 예외를 잡아 내는 방법으로는
Thread.setDefaultUncaughtExceptionHandler4 를 사용하는 방법이다.
그리고 위의 메서드를 사용해서 오류가 발생 했을 경우에 메일로 보내는 방법5도 있다.
그래서, 다음 두 가지 문서를 분석 해 보았다.
안드로이드에서 강제 종료 피하기6
모든 안드로이드 개발자는 응용 프로그램을 개발할 동안 강제 종료 문제에 직면 하고 있습니다. 이 문서는 그 오류를 잡아내고 또 이 오류를 우아하게 다루는 방법을 다룹니다.
이를 통해서 우리는 안드로이드 애플리케이션 내에서(Application) 메커니즘에 의한 오류 페이지도 생성 할 것입니다. 그래서 응용 프로그램이 오류를 일으킬 때마다 사용자가 귀찮은 팝업 대화 창를 볼 수 없게 만들 것입니다. 대신, 해당 오류 앱에 미리 정의 된 뷰가 사용자에게 표시 될 것입니다. 이러한 종류의 메커니즘을 만들기 위해 우리는 하나의 에러 핸들러와 앱이 강제 종료 될 때마다 뷰를 얻을 수있는 Activity 클래스를 만들어야 합니다.
package com.example.forceclose;
import java.io.PrintWriter;
import java.io.StringWriter;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
public class ExceptionHandler implements java.lang.Thread.UncaughtExceptionHandler
{
private final Activity myContext;
private final String LINE_SEPARATOR = "\n";
public ExceptionHandler(Activity context) {
myContext = context;
}
public void uncaughtException(Thread thread, Throwableexception) {
StringWriter stackTrace = newStringWriter();
exception.printStackTrace(newPrintWriter(stackTrace));
StringBuilder errorReport = newStringBuilder();
errorReport
.append("************ CAUSE OF ERROR ************\n\n")
.append(stackTrace.toString())
.append("\n************ DEVICEINFORMATION ***********\n")
.append("Brand: ").append(Build.BRAND)
.append(LINE_SEPARATOR)
.append("Device: ").append(Build.DEVICE)
.append(LINE_SEPARATOR)
.append("Model: ").append(Build.MODEL);
.append(LINE_SEPARATOR)
.append("Id: ").append(Build.ID)
.append(LINE_SEPARATOR)
.append("Product: ").append(Build.PRODUCT)
.append(LINE_SEPARATOR)
.append("\n************FIRMWARE ************\n")
.append("SDK: ").append(Build.VERSION.SDK)
.append(LINE_SEPARATOR)
.append("Release:").append(Build.VERSION.RELEASE)
.append(LINE_SEPARATOR)
.append("Incremental: ").append(Build.VERSION.INCREMENTAL)
.append(LINE_SEPARATOR);
Intent intent = newIntent(myContext, AnotherActivity.class);
intent.putExtra("error",errorReport.toString());
myContext.startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
}
위의 클래스는 강제 종료 오류에 대한 리스너로 동작 할 것입니다. 전체 오류 로그와 함께 장치 정보도 함께 표시 될 수 있도록 합니다. 때로는 특정 제조업체의 장치에서 특정 오류가 발생하는 경우도 있습니다. Intent 및 startActivit메서드는 앱이 다운 될 때마다 새로운 액티비티를 시작하는 데 사용되는 것을 볼 수 있을 것입니다. 따라서 앱이 비정상 종료 될 때마다 CrashActivity라는 액티비티가 시작될 것입니다. 지금부터 스택 추적 내용을 인덴트의 extra로 넘겼습니다.
이제 CrashActivity는 당신이 원하는 방식으로 처리 할 수 있는 일반적인 안드로이드 액티비티 입니다.
자 그럼 중요한 부분이 있습니다, 즉 그럼 예외를 어떻게 잡을 것이냐는 것이죠. 예제는 매우 간단합니다. 오버라이드 된 onCreate 메소드에서 super 메소드를 호출 한 직후 각 액티비티에서 다음 코드를 복사해서 붙입니다.
Thread.setDefaultUncaughtExceptionHandler(newExceptionHandler(this));
우리가 만드는 액티비티는 다음과 비슷하게 구성될 것입니다.
public class ForceClose extends Activity {
/**Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.setDefaultUncaughtExceptionHandler(newExceptionHandler(this));
setContentView(R.layout.main);
// Your mechanism is ready now.. In this activity from anywhere
// if you get force close error it will be redirected to the CrashActivity.
}
}
위 코드의 결과물은 아래와 같을 것입니다.
ForceClose7 소스 코드 다운.
안드로이드에서 충돌 강제 종료 후에 앱을 자동 재 시작하기8
안드로이드 애플리케이션에서 우리는 제대로 예외를 발생시키지 않는다면 일반적으로 "강제 종료"오류가 발생합니다. 이에 대해서 누구나 다음과 같은 물음을 가지게 될 것입니다. "강제 종료 된 경우 자동으로 응용 프로그램을 다시 시작할 수 있을까?"
이 튜토리얼에서는 수동으로 예외를 처리하는 하는 법과 충돌 / 강제 종료 후 응용 프로그램을 다시 시작 / 자동 실행하는 방법을 배우게 될 것입니다. 이 질문에 대한 답은 매우 간단합니다. 이 경우 Thread.setDefaultUncaughtExceptionHandler () 메서드를 사용할 필요가 있습니다. 응용 프로그램이 충돌이 일어난 경우에는 항상 uncaughtException () 메서드로 들어오게 됩니다.
응용 프로그램이 충돌했을 때 응용 프로그램을 다시 시작하려면 다음 방법을 사용해야 합니다.
단계 1
다시 시작하려고 하는 액티비트의 Intent 를 생성합니다.
Intent intent = new Intent(activity, RelaunchActivity.class);
intent.addFlags(
Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
이 단계에서, 우리는 몇 몇 플래그와 함께 시작할 액티비티를 저장해야 합니다.
여기 보면,
Intent.FLAG_ACTIVITY_CLEAR_TOP 가 사용 되었는데, 이 플래그가 설정되면, 시작할 액티비트는 이미 현재 테스크에 구동 중이므로, 해당 액티비티의 새로운 인스턴스를 시작하는 대신에 이 액티이티 상위에 있었던 모든 다른 액티비티를 닫게하고 새로운 인덴트로써 이전 액티비티(old activity)에 – 지금은 최상위에 있는- 데이터를 전달하게 됩니다.
Intent.FLAG_ACTIVITY_CLEAR_TASK 플래그는 액티비티가 시작되기 전에 액티비티와 연관되었던 기존 태스크를 지우 기 위해서 사용되었습니다.
이 플래그는 히스토리 스택 상에서 새로운 task를 시작하도록 할 것입니다. task (다음 task 액티비티에서 시작한 액티비티에서)는 사용자가 이동할 수 있는 활동의 원자적 그룹을 정의합니다.
단계 2:
uncaughtException() 메서드 내에 다음 코드를 작성하도록 합니다.
PendingIntent pendingIntent
= PendingIntent.getActivity(
YourApplication.getInstance().getBaseContext(), 0,
intent, intent.getFlags());
AlarmManager mgr
= (AlarmManager)YourApplication.getInstance().getBaseContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC,
System.currentTimeMillis()+ 2000, pendingIntent);
activity.finish();
System.exit(2);
모르겠다구요?
자 여기, PendingIntent 가 사용되었습니다. 이 클래스는 단순한 Intent 와는 다른면이 있습니다. 이 클래스는 인덴트 플래그들과 함께 request code 를 저장합니다.
Read more about Intent flags.
Intent flags9 에 대해서 좀 더 알아 보시기 바랍니다.
AlarmManager 는 2초 후에 task를 실행하는 알람을 설정하기 위해서 사용되었습니다.
여기서 우리가 할 것은 매 2초 후에 우리의 애플리케이션을 시작 할 것입니다. 그래서 우리는 타이머를 매 2초후 마다 실행하도록 설정하고, 이 타이머에 PendingIntent 던지는 것입니다.
그러고 나면, 우리는 현재 에러를 발생한 곳으로부터 현재 액티비티를 종료 해야만 합니다. 이는 우리의 애플리케이션을 exit 시키는 데 필요한 단계입니다.
Fullsource code 전체 소스 코드
YourApplication.java
public class YourApplication extends Application {
private static Context mContext;
public static YourApplication instace;
@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
instace = this;
}
@Override
public Context getApplicationContext() {
return super.getApplicationContext();
}
public static YourApplication getIntance() {
return instace;
}
}
DefaultExceptionHandler.java
import android.app.Application;
/**
* This custom class is used to Application level things.
*
* @author Chintan Rathod (http://www.chintanrathod.com)
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Environment;
import android.util.Log;
/**
* This custom class is used to handle exception.
*
* @author Chintan Rathod (http://www.chintanrathod.com)
*/
public class DefaultExceptionHandler implements UncaughtExceptionHandler {
private UncaughtExceptionHandler defaultUEH;
Activity activity;
public DefaultExceptionHandler(Activity activity) {
this.activity = activity;
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
try {
Intent intent = new Intent(activity, RelaunchActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent
= PendingIntent.getActivity(
YourApplication.getInstance().getBaseContext(), 0,
intent, intent.getFlags());
//Following code will restart your application after 2 seconds
AlarmManager mgr
= (AlarmManager) YourApplication.getInstance().getBaseContext()
.getSystemService(Context.ALARM_SERVICE);
mgr.set(
AlarmManager.RTC, System.currentTimeMillis() + 1000,
pendingIntent);
//This will finish your activity manually
activity.finish();
//This will stop your application and take out from it.
System.exit(2);
} catch (IOException e) {
e.printStackTrace();
}
}
}
YourApplication 클래스 내의 코드는 애플리케이션 클래스 입니다. 이 애플리케이션 클래스에 대해서 좀 더 알고 싶으시면, Usage of Application class in Android를 읽어 보시기 바랍니다.( https://capdroid.wordpress.com/2011/07/28/usage-of-application-class-in-android/)
애플리케이션에서 이 클래스를 사용하기 위해서는, 액티비티의 onCreate() 메서드 내에 다음 코드를 작성하면 됩니다.
Thread.setDefaultUncaughtExceptionHandler(new DefaultExceptionHandler(this));
요약
이 튜토리얼에서, 우리는 안드로이드 애플리케이션에서 예외를 수작업으로 처리 하기 위해서 UncaughtExceptionHandler 클래스를 사용하는 법을 배웠습니다. 또, 안드로이드내에서 충돌이나 강제종료 후에 애플리케이션을 재 시작하는 법도 배웠습니다.
여기서 필요한 부분은 이렇게 충돌이나 강제 종료 되었을 경우에 오류를 리포팅 할 수 있는 방식을 찾아 내는 것일 것이다.
가령, 서버 시스템을 마련해서 클라이언트에서 오류가 발생 되었을 때마다, 오류를 수집하는 경우인데, 이 경우는 서버에서 필요하지 않는 오류를 많이 받을 수 있다.
두번째로는 서버에서 원할 때 오류를 수집하는 방법이다, 이 방법은 서버에서 오류를 수집하려는 당시에 클라이언트가 원하는 동작을 하고 있어야 하고, 서버는 클라이언트로 부터 응답을 받기 전까지는 클라이언트의 상태를 알 수 없을 것이다.
이상.
1. https://github.com/Smeat/alogcat/tree/master/src/org/jtb/alogcat
3. https://github.com/idolon-github/android-crash-catcher
5. http://stackoverflow.com/questions/19897628/need-to-handle-uncaught-exception-and-send-log-file
6. https://trivedihardik.wordpress.com/2011/08/20/how-to-avoid-force-close-error-in-android/
7. https://github.com/hardik-trivedi/ForceClose
8. http://chintanrathod.com/auto-restart-application-after-crash-forceclose-in-android/
9. https://developer.android.com/reference/android/content/Intent.html#setFlags(int)
'모바일프로그래밍 > 안드로이드' 카테고리의 다른 글
[Android]OpenCV 샘플 돌려보기 - 1 (0) | 2024.01.18 |
---|---|
[Android]OpenCV와 안드로이드 스튜디오 (0) | 2024.01.17 |
ProGuard를 통한 안드로이드 애플리케이션의 난독화 (0) | 2023.04.11 |
이벤트 기반 아키텍처와 메시지 기반 아키텍처 (0) | 2023.04.02 |
SQLCiper를 통한 안드로이드 Sqlite 데이터베이스 암호화 하기 (0) | 2023.04.01 |