개발자에게 있어서, 앱의 버그만큼 신경쓰이는 부분이 있습니다.
바로 앱 구동 중에, 비정상적으로 종료되버리는 현상입니다.
앱이 비정상 종료되는 원인은 다양합니다.
현재까지 대표적으로 겪은 사례로는 IndexOutOfException, Thread Exception 두 가지가 있었습니다.
* IndexOutOfException : ArrayList를 사용하던 중에, 빈 데이터에서 직접적으로 특정 인덱스를 접근하는 경우, 에러가 발생하는 경우.
ArrayList<Strng> articleList = new ArrayList<String>();
// 5번째 항목을 접근
String fifthValue = articleList.get(4); // IndexOutOfException 발생
* Thread Exception : 백그라운드 스레드에서는 UI 요소를 수정하거나 갱신하는 것은 불가능합니다. 그러나 직접적으로 접근하려고 시도하는 경우는 에러를 뿜으면서 앱이 종료되는 현상을 확인 가능.
Application 클래스에서 오류처리 Handler 설정
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
setCrashHandler()
}
private fun setCrashHandler() {
val defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { _, _ ->
// Crashlytics에서 기본 handler를 호출하기 때문에 이중으로 호출되는것을 막기위해 빈 handler로 설정
}
Fabric.with(this, Crashlytics())
val fabricExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(
AppServiceExceptionHandler(
this,
defaultExceptionHandler,
fabricExceptionHandler
)
)
}
}
- defaultExceptionHandler: 안드로이드의 기본 오류 처리 Handler
- fabricExceptionHandler: Crashlytics에서 사용되는 오류처리 Handler
- HeyDealerExceptionHandler: 위의 2개를 가지고 있으며 오류처리 화면을 표시하기 위한 Handler
오류 메세지 발생하는 핸들러 정의
class AppServiceExceptionHandler(
application: Application,
private val defaultExceptionHandler: Thread.UncaughtExceptionHandler,
private val fabricExceptionHandler: Thread.UncaughtExceptionHandler
) : Thread.UncaughtExceptionHandler {
private var lastActivity : Activity? = null
private var activityCount = 0
init {
application.registerActivityLifecycleCallbacks(
object : SimpleActivityLifecycleCallbacks() {
override fun onActivityStarted(activity: Activity) {
if (isSkipActivity(activity)) return
activityCount++
lastActivity = activity
}
override fun onActivityStopped(activity: Activity) {
if (isSkipActivity(activity)) return
activityCount--
if (activityCount < 0) lastActivity = null
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (isSkipActivity(activity)) return
lastActivity = activity
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
}
}
)
}
private fun isSkipActivity(activity: Activity) = activity is ErrorActivity
override fun uncaughtException(thread: Thread, throwable: Throwable) {
fabricExceptionHandler.uncaughtException(thread, throwable)
lastActivity?.run {
val stringWriter = StringWriter()
throwable.printStackTrace(PrintWriter(stringWriter))
startErrorActivity(this, stringWriter.toString())
overridePendingTransition(R.anim.anim_slide_in_left, R.anim.anim_slide_out_left)
} ?: defaultExceptionHandler.uncaughtException(thread, throwable)
Process.killProcess(Process.myPid())
System.exit(-1)
}
private fun startErrorActivity(activity: Activity, errorText: String) = activity.run {
val errorActivityIntent = Intent(this, ErrorActivity::class.java)
.apply {
putExtra(ErrorActivity.EXTRA_INTENT, intent)
putExtra(ErrorActivity.EXTRA_ERROR_TEXT, errorText)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
startActivity(errorActivityIntent)
finish()
}
}
- SimpleActivityLiSimpleActivityLifecycleCallbacks (추상 클래스) 내용
더보기
abstract class SimpleActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
override fun onActivityPaused(activity: Activity) {
// no-op
}
override fun onActivityResumed(activity: Activity) {
// no-op
}
override fun onActivityStarted(activity: Activity) {
// no-op
}
override fun onActivityDestroyed(activity: Activity) {
// no-op
}
override fun onActivityStopped(activity: Activity) {
// no-op
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
// no-op
}
}
오류 상세 메세지 안내 액티비티
class ErrorActivity : AppCompatActivity() {
private lateinit var binding : ActivityErrorBinding
private val lastActivityIntent by lazy { intent.getParcelableExtra<Intent>(EXTRA_INTENT)}
private val errorText by lazy { intent.getStringExtra(EXTRA_ERROR_TEXT) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityErrorBinding.inflate(layoutInflater)
setContentView(binding.root)
// 에러메세지
String message = errorText
Log.d("TAG", "에러메세지 : $message")
startActivity(lastActivityIntent)
finish()
}
companion object {
const val EXTRA_INTENT = "EXTRA_INTENT"
const val EXTRA_ERROR_TEXT = "EXTRA_ERROR_TEXT"
}
}
본 포스팅은 '박상권님의 블로그' 내용을 참조한 글입니다.
'안드로이드' 카테고리의 다른 글
액티비티 상태 저장 onRestoreInstanceState(), onSaveInstanceState() (0) | 2021.03.29 |
---|---|
[JetPack] ViewModel 의 역할 (0) | 2021.03.15 |
[Android] 회전모드 최적화 하는 방법 (상태유지) (0) | 2021.01.22 |
[Kotlin] 싱글턴 패턴 (0) | 2021.01.21 |
[Kotlin] 코틀린 익스텐션 사용하기 (kotlin-android-extensions) (0) | 2021.01.20 |
댓글