본문 바로가기
Android

[Android] 액티비티 생명주기(Activity Lifecycle)

by Saerong2 2020. 9. 19.

 

 

[지난 포스팅 - 안드로이드 앱 구성 요소]에서 안드로이드의 진입점에 해당하는 네 가지 구성요소는 모두 시스템에 의해 관리된다고 배웠습니다. 이번 시간에는 네 가지 구성요소 중에서 액티비티의 수명주기에 대해서 알아보도록 하겠습니다.

 

게임을 하다가 전화를 받고 일정을 확인하 다시 게임으로 돌아가는 평범한 사용패턴에서 여러 액티비티는 활성화, 비활성화를 반복합니다. 눈으로 보기엔 활성화 되거나 비활성화 되거나 두 가지의 상태를 갖는듯 하지만 실제로 액티비티는 내부적으로 더 많은 상태를 가집니다. 액티비티는 앱에서 발생하는 여러 이벤트 때문에 계속해서 상태를 전이하게 되는데, 상태전이를 하기 전에 시스템은 콜백함수를 통해서 액티비티의 상태가 변했다는것을 알려줍니다.

따라서 우리는 이 콜백함수를 이용해서 액티비티를 적절히 다루어야합니다.

 

예를 들어, 게임을 하다가 중요한 연락을 받느라 게임 앱의 액티비티가 백그라운드로 이동을 했는데, 게임으로 다시 돌아왔더니 점수가 초기화되어 있으면 어떨까요? 시간을 들여서 점수를 차곡차곡 모아왔는데 그것만큼 허탈한 순간이 없겠죠. 이러한 상황을 막기 위해 시스템은 우리에게 "당신 앱이 지금 정지상태에 들어갔으니 정지상태에서 처리해야 할 작업을 하세요" 라고 onStop( ) 같은 콜백함수를 호출해줍니다. 그러면 개발자인 우리는 onStop( )에서 사용자가 지금것 쌓은 점수를 DB에 저장하도록 하는 것이죠.

 

이렇게 안드로이드에서는 액티비티의 상태변화에 따라 해당하는 콜백함수를 호출해주는 덕분에 개발자는 그 상태에서 처리해야하는 작업을 구현할 수 있습니다. 앱이 비정상적으로 종료되거나 사용자가 앱에서 나갔다 오더라도 이전의 작업을 이어서 할 수 있도록 처리를 할 수 있게 되었죠.

 

 

 

 

# 액티비티 수명주기(Activity Lifecycle)

 안드로이드 앱이 처음 실행되는 순간부터 여러가지 이유로 앱이 종료되는 시점까지의 액티비티의 상태변화를 액티비티 수명주기라고 합니다. 앞서 설명했듯이, 어떠한 이벤트가 발생해서 액티비티의 상태가 전이될 때 마다 시스템은 "얘 개발자야 액티비티가 OO상태에 진입했어!" 하고 해당하는 상태의 콜백함수를 호출합니다. 모든 콜백함수에 대해서 오버라이드 할 필요는 없고, 해당 액티비티에서 처리해주어야 하는 부분에 대해서만 구현을 하면 됩니다.

시스템에서 액티비티의 상태에 따라 콜백함수를 호출해주는 덕분에 액티비티를 관리할 수 있다는 것은 알겠는데 그렇다면 액티비티의 상태에는 어떠한 것이 있고 또 시스템이 호출해주는 콜백함수에는 어떠한 것들이 있을까요?

 

액티비티 수명주기 전이도

## 1. onCreate(saveInstanceState: Bundle)

 onCreate( ) 은 시스템이 액티비티를 생성할 때 실행되는 콜백함수이며 필수적으로 구현해야합니다. 액티비티가 생성되어 CREATED 상태에 들어와서 한 번만 호출되기 때문에 주로 액티비티의 수명 주기동안 한 번만 실행되는 기본 로직을 실행합니다.

예를 들어, xml파일에 작성한 레이아웃을 setContentView( )로 생성하는 작업을 하고, 생성된 레이아웃 안의 ButtonfindViewById( )를 통해 인스턴스화하고, 액티비티에 뿌려질 데이터베이스 정보를 준비해야합니다. 이처럼 액티비티 내에서 쓰이는 것들의 초기화는 onCreate( )에서 실행하게 됩니다.

 인자에 들어가는 saveInstanceState 에 대해서는 이 글의 말미에서 다루겠습니다.

 

## 2. onStart( )

 onCreate( ) 에서 초기화 작업을 마치면 시스템은 Started 상태에 진입하며 onStart( ) 를 호출합니다. onStart( )가 호출되며 액티비티는 사용자에게 보여지고, 액티비티를 포그라운드로 보내 사용자가 상호작용 할 수 있도록 준비합니다. 사용자는 UI를 통해 액티비티와 상호작용 하므로 onStart( ) 에서는 앱의 UI를 관리하는 코드를 초기화합니다. onStart( )는 매우 빠르게 완료되며 곧 onResume( ) 을 호출합니다.

 

 

## 3. onResume( )

 액티비티가 Resumed 상태로 들어오면 비로소 포그라운드에 표시되고 시스템은 onResume( )을 호출합니다.

앱은 Resumed 상태가 되어서야 사용자와 상호작용을 할 수 있습니다. 어떤 이벤트가 발생하여 다른 요소에 의해서 포커스가 떠날 때까지 앱은 Resumed 상태에 머무릅니다. 예를 들어, 전화가 오거나 홈 버튼을 누르기 직전까지 이 상태에 머물러 있습니다. 이벤트가 발생하여 포커스가 떠나게 되면 액티비티는 Paused 상태로 진입하고 시스템이 onPause( )를 호출합니다. 더 간단히 설명하면, 액티비티의 일부가 화면에 보이며 사용자와 상호작용을 하여 포커스를 얻고 있다면 RESUMED 상태입니다. 포커스를 얻는 것은 단 하나의 액티비티만 가능합니다. 하지만 안드로이드 10부터는 다중 재개(Multi-resumed)를 지원하여 화면에 있는 여러 액티비티가 동시에 RESUMED 상태에 머무를 수 있게 됩니다. 이에 대해서는 ## 4. onPause( )에서 더 자세히 다루도록 하겠습니다.

 

 

## 4. onPause( )

 더 이상 액티비티가 포그라운드에 있지 않다는 것을 알리기 위한 콜백함수입니다. 화면에는 액티비티가 보이지만 실제로 사용자에게 포커스를 받지 못하는 경우(상호작용을 못하는 경우)이죠. 일반적으로 안드로이드 환경에서 한 액티비티가 일시적으로 포커스를 잃는 것은 빈번한 일입니다. 투명한 액티비티가 기존 액티비티 위를 가리게 되면 기존의 액티비티는 눈으로는 보이지만 상호작용을 할 수 없기 때문에 Paused 상태에 진입하게 됩니다. 다이얼로그의 경우는 액티비티가 가려지는 것 같지만, 다이얼로그는 액티비티의 일부이므로 Paused 상태로 진입하지 않습니다. (참고한 글마다 설명이 달라서 직접 해보니 onPause( )가 호출되지 않았습니다.) Paused 상태에서 다시 사용자로부터 포커스를 받거나 혹은 화면에서 완전히 사라지게 되면 각각 Resumed 상태, Stopped 상태로 진입하게 됩니다.

 

 Paused 상태부터는 메모리가 부족할 시 메모리 확보를 위해 시스템이 프로세스를 강제로 종료할 수 있습니다. 액티비티 A에서 B로 이동할 때 A의 onPause( )가 리턴되어야 B가 시작될 수 있기 때문에 A의 onPause( )에서 오래걸리는 작업을 할 수 없습니다. 즉, Paused 상태에서 A의 데이터를 저장할 수는 있으나 시간이 오래걸리는 네트워크 호출이나 DB 트랜잭션은 하지 않고 Stopped 상태에서 하도록 공식문서에서 권고합니다.


### 다중 재개 (Multi-resumed)

 오늘날 멀티 윈도우나 팝업기능을 사용하면 여러 태스크를 동시에 화면에 띄울 수 있습니다. 메신저 앱과 지도 앱을 분할해서 화면에 채울경우 어떻게 될까요? 지금까지 설명한 것에 의하면 여러 액티비티가 화면에 노출되어 있더라도 하나만 포커스를 받을 수 있습니다. 따라서 현재 포커스를 받고 있는 액티비티만 Resumed 상태이며 나머지는 Paused 상태에 진입하겠지요. 하지만 안드로이드의 폼팩터가 다양해지고 있고 모바일 디바이스의 디스플레이 크기도 커짐에 따라서 사용자는 멀티 윈도우 환경에서 여러개의 앱을 나란히 두고 사용하고 싶기 마련입니다. 그래서 안드로이드 10 부터는 이러한 것을 다루기 위해 다중 재개(Multi-resumed)를 도입하였습니다.

 다중 재개에서는 포커스와 상관 없이 화면에 나오는 모든 액티비티들이 모두 Resumed 상태에 머무릅니다. 하지만 여전히 투명한 액티비티가 그 위를 가린다면 가려진 액티비티는 Paused 상태에 진입합니다. 안드로이드 10 이전에는 onResume( )을 호출하는 액티비티가 포커스를 받는 것이었지만 안드로이드 10 이후에는 화면에 나온 모든 앱들이 Resumed 상태에 있기 때문에 그 중에서 현재 포커스를 받는 액티비티가 무엇인지 찾을 수 있는 다른 콜백함수가 필요합니다. 그것이 바로 onTopResumedActivityChange( ) 콜백함수입니다.

 몇 년전 자료로 공부를 하다가 막상 현재 핸드폰으로 콜백함수를 따라가 보려니 변화한 부분이 많이 있었네요!

다중재개와 관련된 공식문서는 여기를 참고해주세요.

 

 

## 5. onStop( )

 액티비티가 완전히 사용자에게 표시되지 않게되면 액티비티는 Stopped 상태에 진입하고 onStop( ) 을 호출합니다. 앱이 사용자에게 보이지 않는 이때를 활용하여 언제 메모리에서 내려올지 모르는 이 프로세스의 사용자 데이터를 저장하고 성능을 위해 필요없는 리소스를 해제하거나 조정해야합니다. 예를 들어 지금것 쌓아온 게임 점수를 저장하고, CPU를 많이 잡아먹는 애니메이션과 같은 작업이 더 이상 화면에 나타나지 않으니 종료하거나 성능을 낮추는 등의 작업을 할 수 있죠.

우리가 휴대폰을 사용하는 패턴을 생각해보면, 두 가지 이상의 앱을 번갈아 사용하다보면 중단됨 상태에 자주 진입하게 됩니다. 그렇기 때문에 바로 프로세스를 종료하는 것이 아니라, 액티비티가 재 실행 될것을 기대하며 중단됨 상태에서도 프로세스는 여전히 메모리 위에 남아 있게됩니다. 하지만 재개되지 않는 액티비티들이 메모리에 쌓이다보면 메모리가 부족한 경우가 생깁니다. 이 때, 시스템은 메모리의 확보를 위해 중단됨 상태에 있는 액티비티의 프로세스를 중요도에 따라서 소멸시킬 수 있습니다. 소멸시키는 기준에 대한 자세한 내용은 아래 ##6. onDestroy( )를 참고해주세요.

 사용자가 직접 액티비티를 종료하거나 액티비티 안에서 finish( )가 호출되거나 위와 같이 메모리를 확보하기 위해 액티비티는 종료되며 소멸되기 직전에 onDestroy( )를 호출하여 액티비티가 종료됨을 알립니다. 이때, onDestroy( )는 액티비티가 확실히 종료되는 것이므로 onStop( )에서 아직 해제하지 않은 리소스를 해제해야합니다.

 

 

 

## 6. onDestroy( )

 사용자가 back버튼을 누르거나 액티비티 안에서 finish( )가 호출되어 정상적으로 종료되는 경우도 있지만, 시스템이 메모리가 부족할 때 메모리를 확보하기 위해서 중요도가 낮은 액티비티의 프로세스를 종료할 수도 있습니다. 이 때, 액티비티 뿐만 아니라 해당 프로세스에서 실행되는 작업 모두 소멸됩니다. 여기서 중요도가 낮은 액티비티는 무엇일까요? 당연히 메모리 확보를 위해 지금 프로세스를 소멸시키더라도 영향이 적은 액티비티를 의미합니다. 이는 액티비티의 수명주기와 밀접한 관련이 있습니다. 아무래도 현재 포그라운드에 있는 프로세스 보다는 중지되어 백그라운드에 있는 프로세스가 덜 중요하다고 볼 수 있겠죠.

종료될 가능성 프로세스의 상태 액티비티의 상태
낮음 포그라운드 ( 포커스가 있거나 곧 가져옴) created, started, resumed
보통 백그라운드 ( 포커스 상실 ) paused
높음 백그라운드 ( 완전히 보이지 않음) / 종료 stopped / destroyed

 

 

 

## 이것도 막아보시지!

액티비티의 수명주기에서 호출되는 모든 콜백함수를 알아보았는데요. 특별한 상황을 생각해 보며 정리해보겠습니다.

사용자가 앱을 강제로 종료할 때 어떤 콜백함수가 호출될까요? 저의 삼성 갤럭시 노트10+(안드로이드 10, One UI 2.1)를 기준으로 앱을 강제종료 하려면 홈버튼 옆의 Overview 버튼을 눌러서 종료하고 싶은 앱을 위로 스와이프 하거나 '모두 닫기' 버튼을 눌러서 모든 앱을 닫을 수 있습니다. 

 

연락처 앱을 강제종료!

 이번 글의 내용을 잘 따라오셨다면 연락처 앱의 아이콘을 눌렀을 때 onCreate( ) -> onStart( ) -> onResume( ) 의 호출이 이루어 질 것이란걸 알 수 있습니다. Overview 버튼을 누르면 액티비티는 포커스를 잃고 Paused 상태에 진입하며 onPause( )를 호출할 것입니다. 이 때 화면 상에 이전에 실행하던 액티비티가 보이게 되는데 (사진의 연락처 앱) 그렇다면 액티비티는 포커스만 잃었지 화면에 보이니까 Pasued 상태에 남아 있을까요?

 

 사실 저도 Paused 상태에 있지 않을까 생각했는데.. 실제로는 onStop( )까지 호출이 되었습니다..

띠로리

 onStop( )은 다른 액티비티가 상단에 노출되었을 때 호출된다고 배웠습니다. 이 경우 Overview 버튼을 누르게 되면 나타나는 Task 리스트가 시스템에서 제공하는 또 다른 앱이고 우리가 이전에 실행하던 액티비티가 화면에 보이는듯 하지만 실은 Task리스트라는 하나의 앱에 의해 가려진다. 라고 해석하면 될 것 같네요.

 아무튼 지금 Task리스트가 상단에 있는 이 상태에서 앱을 스와이프하여 종료하면 어떻게 될까요? 당연히 onDestroy( )의 호출이 되는거 아니야? 생각할 수 있지만 실제로 여러번 해보면 호출이 될 때도 있고 안 될 때도 있습니다.

 이와 관련된 내용은 공식문서에도 명시되어 있습니다. "시스템이 액티비티의 호스팅 프로세스를 강제로 종료시키는 경우 onDestroy( )가 호출되는 것을 보장할 수 없다". 실제로 유저가 강제로 종료시키는 것뿐만 아니라 메모리 확보로 인해 시스템에 의해 백그라운드에서 종료되는 프로세스도 많이 있겠지요.

 

## onSaveInstanceState

 아니.. 이제것 우리는 콜백함수를 통해서 유일하게 액티비티를 관리할 수 있었는데 사실은 데이터가 날아가는것 조차 막을 수 없는걸까요? 이를 방지하기 위해서 시스템이 액티비티를 강제로 핸들해야하는 경우에는 onSaveInstanceState( )가 호출되어 액티비티의 이전 상태를 Bundle의 형태로 저장합니다. 이 메서드는 시스템이 필요에 따라 호출하는 것이기 때문에 생명주기에는 포함되지 않습니다.

어디서 많이 본 onSaveInstanceState은 사실 onCreate( )의 인자로 쓰입니다. onCreate( )가 호출되며 액티비티가 생성 될 때 이전에 종료 직전 상태를 복원함으로써 View와 같은 정보를 복원하게 되는 것이랍니다.

 

 

 

 

액티비티의 생명주기는 정말 기본적인 것이지만 예외적인 상황을 세세하게 파고 들어가면 그렇게 쉽지만은 않은 것 같습니다. 이번 포스팅을 통해 큰 맥락에서의 액티비티의 생명주기는 이해할 수 있었길 바랍니다.

 

댓글