안드로이드에서 앱 비정상 종료 시 Exception 처리 방법?

2023. 4. 12. 19:07모바일프로그래밍/안드로이드

728x90

안드로이드에서 앱 비정상 종료 시 예외(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 클래스를 사용하는 법을 배웠습니다. 또, 안드로이드내에서 충돌이나 강제종료 후에 애플리케이션을 재 시작하는 법도 배웠습니다.

 

여기서 필요한 부분은 이렇게 충돌이나 강제 종료 되었을 경우에 오류를 리포팅 할 수 있는 방식을 찾아 내는 것일 것이다.

가령, 서버 시스템을 마련해서 클라이언트에서 오류가 발생 되었을 때마다, 오류를 수집하는 경우인데, 이 경우는 서버에서 필요하지 않는 오류를 많이 받을 수 있다.

두번째로는 서버에서 원할 때 오류를 수집하는 방법이다, 이 방법은 서버에서 오류를 수집하려는 당시에 클라이언트가 원하는 동작을 하고 있어야 하고, 서버는 클라이언트로 부터 응답을 받기 전까지는 클라이언트의 상태를 알 수 없을 것이다.

 

이상.


 

 

728x90