아름답게 안드로이드 앱 오류 처리하기

Ted Park
PRND
Published in
9 min readMay 29, 2019

앱을 개발하고 운영하다보면 예기치 않은 오류를 맞이하게 됩니다.

모든 앱개발자가 앱에서 오류가 발생하지 않기를 원하지만 한번도 오류를 발생시켜보지 않은 개발자는 없을것입니다.(그런 개발자분이 계신다면 지금 바로 지원해주세요)

구글이 만든앱도 오류가 발생합니다. 너무 스스로를 자책하지 마세요

이런 오류를 감지하고 수정하기 위해서 우리는 다양한 오류 리포팅 서비스를 이용하고 있습니다.

요즘에는 안드로이드든 아이폰이든 Firebase Crashlytics를 필수로 사용하고 있는 추세입니다.(Firebase Crashlytics는 Fabric Crashlytics와 같은 서비스입니다.)

이런 오류 리포팅 서비스들을 활용하면 오류가 발생하는것을 알게되고 이를 즉각적으로 수정해서 문제를 조기에 해결할 수 있습니다.

사용자 입장에서의 오류

하지만 그럼에도 불구하고 여전히 사용자의 오류경험은 개선되지 않았습니다.

사용자는 이유도 모른채 앱이 종료되어 버릴것이며 ‘뭐하자는거지?’ 라고 생각하고 앱을 떠나버릴 수도 있습니다. 이는 고객 이탈률의 치명적인 영향을 끼칩니다.

오류가 발생하면 어떻게 처리되면 좋을까?

오류가 발생했을때 개발자와 사용자 모두에게 좋은 경험의 흐름으로 흘러가기위해서는 아래와 같은 시나리오로 흘러가기를 바랬습니다.

  1. 오류 발생시 즉각적인 오류 리포팅으로 개발자가 알 수 있도록 함
  2. 사용자에게 문제가 생겼음을 알림
  3. 이전에 열려있었던 화면을 다시 실행할 수 있도록 제공

그래서 헤이딜러 앱에서는 오류가 발생했을때 아래와 같은 화면을 보여주고 사용자가 다시 이전 화면을 실행할 수 있도록 제공하도록 하고 있습니다.

이것이 바로 헤이딜러에서 오류가 발생했을때 사용자에게 나타나는 화면입니다.

앱을 사용하다가 오류가 발생하면 아래와 같은 시나리오로 사용자경험이 이루어집니다.

어때요? 오류가 발생하더라도 사용자는 앱을 이탈하지 않고 이전화면으로 다시 빠르게 돌아올 수 있게 되어 이탈을 막을 수 있습니다.

이로써 기존에 불친절하고 사용자에게 좋은 경험이 아니었던 화면보다 좀더 유연하고 아름다운 앱의 오류처리가 가능해졌습니다.

QA팀: “QA중에 오류가 발생했어요"

개발팀: “제 폰에서는 되는데요"

또한 개발/QA 모드일경우에는 오류 화면에서 어떤 오류인지까지 표시해주도록 합니다. QA팀과 개발팀간의 사이가 좋아집니다.

개발자가 아닌 대표님이나 기획자분이 이 글을 보셨다면 개발자에게 공유해주시고 각자의 서비스에 맞는 오류대응이 가능하도록 하시면 좋을것 같습니다.

구현

오류 화면에 대한 구현 원리는 아래와 같습니다.

  1. Global하게 오류를 처리하는 Handler생성
  2. 오류가 발생할 경우 이 Handler에서 catch
  3. Crashlytics에 해당 오류내용을 보고
  4. 직전에 실행했었던 화면 정보를 가져와서 실행

실행먼저 해보고 싶으신 분들은 아래 GitHub을 통해서 확인해보시면 됩니다.

Application class에서 오류처리 Handler 설정

  • crashlyticsExceptionHandler: Crashlytics에서 사용되는 오류처리 Handler
  • HeyDealerExceptionHandler: crashlyticsExceptionHandler를 가지고 있으며 오류처리 화면을 표시하기 위한 Handler

Crashlytics에서 기본 defaultExceptionHandler를 내부적으로 Crashlytics의 것으로 설정해둡니다.

최근 화면 정보 저장하기

오류발생시 최근에 실행된 화면을 재실행하기 위해서 새로운 화면이 보여질때마다 최신 화면정보를 갱신해줍니다.

init {
application.registerActivityLifecycleCallbacks(
object : SimpleActivityLifecycleCallbacks() {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (isSkipActivity(activity)) {
return
}
lastActivity = activity
}
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
}
}
})
}
private fun isSkipActivity(activity: Activity) = activity is ErrorActivity

오류발생 처리

  • 오류가 발생하면 오류리포팅을 위해 Crashlytics에 알립니다.
  • 오류발생을 알리는 Error Activity를 실행합니다.
  • 마지막으로 실행된 화면정보도 함께 포함합니다.
  • 개발/QA모드인경우 오류내용을 표시하기 위해 오류내용도 함께 넘겨줍니다.
override fun uncaughtException(thread: Thread, throwable: Throwable) {
lastActivity?.run {
val stringWriter = StringWriter()
throwable.printStackTrace(PrintWriter(stringWriter))
startErrorActivity(this, stringWriter.toString())
}
crashlyticsExceptionHandler.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()
}

전체 HeyDealerExceptionHandler 코드는 아래와 같습니다.

오류 화면

오류 화면은 간단합니다.

  • 재실행버튼 클릭시 마지막 화면정보를 이용하여 화면 실행
  • 개발/QA모드인경우 오류내용을 화면에 표시

전체 소스코드는 아래 GitHub에서 확인가능합니다.

마치며

앱을 개발하면서 오류는 항상 발생하지만 이 오류에 얼마나 잘 대응 하느냐는 개발자의 몫입니다.

이 포스팅을 참고하셔서 각자 운영중인 서비스의 특성에 맞게 아름답게 오류에 대응하셨으면 좋겠습니다.

더 좋은 오류처리 방식이나 피드백이 있으시다면 댓글로 남겨주시면 감사하겠습니다.

저희와 함께 헤이딜러 서비스를 발전 시켜나가실 분들을 기다리고 있습니다.

http://bit.ly/prnd-hiring

위의 채용링크로 많은 지원부탁드립니다.

감사합니다.

--

--