(Android) 놓치기 쉬운 Lifecycle

Activity Lifecycle 관리

Jaesung Lee
jaesung dev
12 min readJan 20, 2023

--

Photo by Nathan Dumlao on Unsplash

안드로이드 개발에서 Lifecycle 관리는 필수라고 해도 과언이 아닙니다. 그만큼 중요한 내용이면서도 어려운 내용이기도 합니다. 안드로이드 개발을 한다면 Activity나 Fragment의 전반적인 Lifecycle을 처음 접하지는 않을 것입니다.

이 글에서는 개발하면서 마주칠 수 있는 여러가지 상황 속에서 놓치기 쉬운 Lifecycle 사용 사례와 기본적인 상태 유지에 대해 다룹니다.

Lifecycle-Aware Components

androidx.lifecycle 라이브러리를 통해 Activity와 Fragment에서 Lifecycle 전환에 따른 상태와 이벤트를 쉽게 observe할 수 있었습니다.

https://developer.android.com/topic/libraries/architecture/lifecycle

전반적인 Activity와 Fragment의 Lifecycle 흐름은 위와 같은 형태로 동작하게 됩니다.

크게 5가지의 상태와 6가지의 이벤트로 구성됩니다. (ON_ANY 제외)

Activity Lifecycle

안드로이드 개발자 문서에서는 Activity의 Lifecycle을 아래 그림처럼 표현하고 있습니다. Activity의 각 Lifecycle 콜백에 대해 짧게 알아보도록 하겠습니다. 이미 알고 계신 분들은 가볍게 넘어가시면 됩니다.

https://developer.android.com/images/activity_lifecycle.png

onCreate()

Activity의 생성과 함께 가장먼저 호출되는 콜백입니다. 이 시점에는 잘 아시는 것 처럼 setContentView를 통해 View를 inflate하게 됩니다. 이 콜백은 Activity가 메모리상에 올라갈 때 실행되기 때문에 Activity가 만들어지면서 최초에 단 한 번 실행됩니다. 그렇기 때문에 View와 관련된 작업이나 리소스 초기화같은 작업을 해야 합니다.

onStart()

Activity가 사용자에게 보이기 시작할 때 호출되는 콜백입니다. 즉, Activity가 포그라운드로 올라올 때 호출된다고 이해할 수 있습니다. 또 다른 경우로는, 사용자가 다른 Activity에 있다가 다시 돌아올 때 onRestart 다음에 호출됩니다. onStart 에서는 실질적으로 사용자가 스크린을 통해 상호작용하기 전에 필요한 작업들이 수행되어야 합니다. 로그인 관련 세션 상태 확인, 애니메이션 처리가 대표적입니다.

onResume() / onPause()

onResume이 호출되면 사용자가 Activity와 상호작용할 수 있습니다. Activity가 사용자의 포커스를 잃을 때 까지 유지됩니다. 포커스는 사용자의 관심이라고도 표현할 수 있을 것 같습니다.

Android N (API 24)부터 적용할 수 있었던 멀티 윈도우에서는 창간의 전환이 발생되면 onResume과 onPause의 전환이 반복적으로 발생됩니다. 즉, 사용자가 멀티 윈도우 상에서 잠시 다른 앱을 사용하게 되면 onPause가 호출되는 것입니다.

앱이 사용자의 관심 밖으로 이동하게 되면 포커스를 잃고 (onPause) 관심 안으로 들어오면 포커스를 얻는다(onResume)라고 이해할 수 있습니다.

onStop()

포그라운드에 있던 Activity가 백그라운드로 이동할 때 호출되는 콜백입니다. 위 그림에도 표시된 것 처럼 onStop에서 다음 콜백으로 이동할 수 있는 몇가지 케이스가 존재합니다. 시스템에서 메모리 부족시 백그라운드 상에 존재하는 앱들을 강제로 종료할 수 있습니다. 그렇기 때문에 메모리에 할당된 리소스들 중 중요한 리소스들의 해제는 이 시점에 하는 것이 좋겠습니다.

onDestroy()

Activity가 종료되면서 호출되는 콜백입니다. 이 콜백이 호출되면 해당 Activity는 스택에서 pop되게 됩니다.

onRestart()

사용자가 다른 Activity에 있다가 다시 돌아오는 경우에 onStop, onStart와 함께 호출되는 콜백입니다.

Activity 상태 유지

Activity는 비정상적으로 종료되기도 합니다. 흔히 말하는 Configuration Changed로 인해 원하지 않는 경우에도 onDestroy가 호출되기도 합니다. 이 경우 Activity의 Lifecycle은 onResume -> .. -> onDestroy -> onCreate -> .. -> onResume이 되기 때문에 기존에 Activity가 가지고 있던 UI 상태들을 잃게 됩니다.

비정상적인 종료에도 상태를 저장하기 위해 SaveInstanceState를 사용할 수 있습니다.

해당 시점의 Lifecycle의 상태를 CREATED로 설정하고 super를 호출합니다. 또한, 내부적으로 Android P (API 28) 이후 부터는 onStop 이후에 onSaveInstanceState를 발생시킵니다. 이전 플랫폼의 경우에는 onStop 이전에 발생하며 onPause 이전에 발생하는지 이후에 발생하는지에 대한 보장은 할 수 없습니다.

사용하는 Activity에서는 아래처럼 활용할 수 있습니다. 다양한 저장 메서드를 지원하기 때문에 상황에 맞게 사용할 수 있습니다.

저장한 데이터를 복원할 때는 두가지 콜백을 사용할 수 있습니다.

onCreate에는 Nullable한 Bundle 객체를 통해 이전 상태를 복원할 수 있습니다. onCreate는 Activity가 처음 생성되어 초기화될 때 호출되는 콜백이지만 Configuration Change시 onCreate가 다시 호출될 수 있습니다. onCreate에서는 아래처럼 대응할 수 있습니다.

추가로, onRestoreInstanceState를 사용할 수 있습니다.

onRestoreInstanceState는 저장된 상태가 있고, Activity가 다시 초기화되었을 때 onStart 이후에 호출되는 콜백입니다. 마찬가지로 Bundle을 통해 상태를 복원할 수 있고, onCreate와는 다르게 Non-null한 Bundle을 사용합니다. 이 콜백은 항상 호출되는 Lifecycle 콜백이 아닌 반드시 비정상적인 종료에 의해 호출되는 콜백이기 때문에 어떤 데이터든 무조건 포함되어있을 것이기 때문입니다. 마찬가지로 다양한 복원 메서드를 제공합니다.

사실 onCreate에서도 저장된 상태를 복원할 수 있기 때문에 굳이 필요한 콜백인가 하는 의문도 듭니다. 실제로 onSaveInstanceState를 통해 저장한 데이터는 onCreate와 onRestoreInstanceState에서 동일한 Bundle 객체로 복원됩니다. onRestoreInstanceState가 상태 복원에서 갖는 이점은 바로 하위클래스의 유연한 확장성입니다. 상태를 복원하고 일부 초기화가 필요한 경우 onCreate보다 onRestoreInstanceState를 override하면 더 많은 유연성을 제공할 것입니다.

다양한 사용 사례

1. 기본적인 Activity 전환

최초에 Activity를 실행시키면 onResume까지 호출되면서 RESUMED 상태에 도달할 것입니다. 이 후, 새로운 Activity로 전환하면 기존 Activity는 포커스를 잃고 백그라운드로 전환될 것이고, 새로운 Activity는 Activity 스택 최상단에 위치하면서 화면에 표시될 것입니다. 다시 기존 Activity로 전환하면 새로운 Activity는 Activity 스택에서 pop되기 때문에 DESTROYED 상태에 도달하면서 메모리에서 해제됩니다.

MainActivity             D  onCreate
MainActivity D onStart
MainActivity D onResume

<-- 새로운 Activity 전환 -->
MainActivity D onPause
NewActivity D onCreate
NewActivity D onStart
NewActivity D onResume
MainActivity D onStop
MainActivity D onSaveInstanceState

<-- 기존 Activity 전환 -->
NewActivity D onPause
MainActivity D onRestart
MainActivity D onStart
MainActivity D onResume
NewActivity D onStop
NewActivity D onDestroy

2. Configuration Change (화면 회전)

화면 회전을 하게되면 Activity의 인스턴스가 파괴되고 다시 생성됩니다. 따라서, View의 상태를 유지할 수 없기 때문에 화면 회전시에도 동일한 View를 보이려 한다면 위에서 살펴본 상태 유지 방법을 사용하면 됩니다.

MainActivity             D  onCreate
MainActivity D onStart
MainActivity D onResume

<-- 화면 전환 -->
MainActivity D onPause
MainActivity D onStop
MainActivity D onSaveInstanceState
MainActivity D onDestroy
MainActivity D onCreate
MainActivity D getSaveInstanceState:Value
MainActivity D onStart
MainActivity D onRestoreInstanceState: Value
MainActivity D onResume

3. Transparent Activity

투명 Activity를 사용할 일이 얼마나 있을지는 잘 모르겠습니다. 아마 직접적으로 Dialog를 호출할 수 없는 상황에서 커스텀해서 사용하지 않을까 싶습니다. 투명 Activity도 어쨌든 Activity이기 때문에 호출 시 스택의 최상단에 위치할 것입니다. 하지만 첫번째 케이스에서 살펴본 로그와 살짝 다릅니다.

투명 Activity가 화면에 보이더라도 여전히 A Activity를 사용자가 보게 됩니다. A와는 상호작용할 수 없지만 여전히 포그라운드상에 보이고 있기 때문에 A는 onStop이 호출되지 않고 여전히 STARTED 상태에 남게 됩니다. 백버튼을 눌러 다시 A로 돌아가더라도 A는 onStop이 호출되지 않았기 때문에 곧바로 RESUMED 상태로 돌아갑니다. 투명 Activity는 마찬가지로 스택에서 pop되며 DESTROYED 됩니다.

MainActivity             D  onCreate
MainActivity D onStart
MainActivity D onResume

<-- 투명 Activity 전환 -->
MainActivity D onPause
TransparentActivity D onCreate
TransparentActivity D onStart
TransparentActivity D onResume

<-- 기존 Activity 전환 -->
TransparentActivity D onPause
MainActivity D onResume
TransparentActivity D onStop
TransparentActivity D onDestroy

4. Dialog

Dialog는 작은 window입니다. 즉, Activity와는 별개의 화면 구성 요소이기 때문에 Activity위에 Dialog가 생성되어도 스택에 push되지 않습니다. 아래 로그를 통해 쉽게 확인할 수 있습니다. Activity는 여전히 RESUMED 상태입니다.

MainActivity             D  onCreate
MainActivity D onStart
MainActivity D onResume

<-- 다이얼로그 실행 -->
---

<-- 기존 Activity 전환 -->
---

5. System Dialog (e.g. Permission Dialog)

앞서 살펴본 기본적인 Dialog와는 살짝 다릅니다. 일반적으로 권한 Dialog에 해당합니다. 개발자 문서에서는 권한을 요청하는 requestPermissions는 사용자가 허용할 권한과 거부할 권한을 선택할 수 있는 새로운 Activity를 시작한다고 설명하고 있습니다. 따라서, 기존 Activity가 중단될 수 있습니다.

MainActivity             D  onCreate
MainActivity D onStart
MainActivity D onResume

<-- 권한 Dialog 실행 -->
MainActivity D onPause

<-- 기존 Activity 전환 -->
MainActivity D onResume

6. Themed Activity

Activity에 Dialog 테마를 입혀 사용하는 경우도 있을 것 같습니다. 투명 Activity와 동일하게 동작합니다. 기존 Activity는 onPause에 의해 STARTED 상태에 들어가겠지만 여전히 사용자가 볼 수 있기 때문에 onStop까지 호출되지는 않습니다.

MainActivity             D  onCreate
MainActivity D onStart
MainActivity D onResume

<-- Dialog Activity 전환 -->
MainActivity D onPause
DialogThemetActivity D onCreate
DialogThemetActivity D onStart
DialogThemetActivity D onResume

<-- 기존 Activity 전환 -->
DialogThemetActivity D onPause
MainActivity D onResume
DialogThemetActivity D onStop
DialogThemetActivity D onDestroy

--

--