본문 바로가기

Program/Android

Android - Activity Life Cycle

** 개인적 메모입니다. 태클 사절 **

출처 = http://tigerwoods.tistory.com/30
 

1. Activity의 4가지 주요 상태 (4 essential states of Activity)

 

Active/Running 상태

Activity A가 화면의 가장 앞(foreground)에 있어 사용자가 직접 볼 수 있고, 포커스를 가지고 있는 상태.

다시 말하자면 입/출력이 가능한 상태.

 

 

 

Pause 상태

Active상태의 activity A가 화면의 foreground를 새로 점유한 activity N에게 포커스를 잃었지만 아직은 A의 일부가 보이는 상태 (foreground를 획득한 activity N이 화면 전체를 사용하지 않거나, 반투명하게 구현 되어 있을 때).

 

 

 

Stop 상태

Active/Pause상태의 activity A가 full-screen크기의 activity N에게 화면 foreground를 선점 당한 상태.

 

 

 

Killed (Dead) 상태

Activity A가 생성되지도 않았거나 생성 후 소멸된 상태.

 

 

 

 

2. Activity의 상태 변이와 callback 메소드

 

Activity의 상태변이도

 

위에서 설명한 4가지 Activity상태간 변이 시에 코드의 흐름을 나타낸 그림이다. (클릭하면 크게 보임)


 

참고. onRestoreInstanceState(Bundle)와 onSaveInstanceState(Bundle)메소드들은 life cycle 에 직접적으로 관여하는 메소드는 아니며 상황에 따라 호출이 생략될 수 있다. 다음 단락: Activity의 강제 종료와 복구에서 자세히 설명.

 

 

위의 그림에서 파란색 실선은 정상적인 Actvity의 흐름이며 붉은색 점선은 비정상적인 Activity의 흐름이다.

위 그림을 이용하면 수 많은 Activity의 상태변이 case들에서 어떤 callback들이 어떤 차례로 호출되는지 쉽게 볼 수 있다.

 

예를 들어, Activity가 처음 실행돼서 사용자에게 사용되다 정상 종료 된다면 다음과 같은 callback이 차례로 호출된다.

onCreate(Bundle) -> onStart() -> onResume() -> onPause() -> onStop() -> onDestory()

 

 

또, Running상태의 Activity A가 다른 Activity B에게 완전히 가렸다 A가 다시 foreground로 와서 사용자에게 사용되는 경우는 다음과 같은 callback이 차례로 호출된다.

onSaveInstanceState(...) -> onPause() -> onStop() -> onRestart() -> onStart() ->onResume()

 

 

마지막 예로, 화면의 일부가 가린 pause 상태의 Activity A가 system 자원(메모리)의 부족으로 system에 의해 강제로 종료 당했다가 system 자원의 여유가 생겨 다시 복구 될 때는 다음과 같은 callback이 차례로 호출된다.

강제종료 -> onCreate(…) -> onStart() -> onRestoreInstanceState(...) -> onResume()

 

 

 

Life Cycle 관련 Callback 메소드 분석

 

Activity의 상태변이 시 호출되는 callback들을 각각 자세히 설명하면 다음과 같다. (클릭하면 크게 보임)



 

한가지 중요한 것은 위의 callback을 오버라이딩 할 때는 다음과 같이 super클래스의 원래 callback을 먼저 호출하여야 한다.

protected void onPause() {
super.onPause();
...
}


 

 

3. Activity의 강제 종료와 복구

 

System에 의한 Activity의 강제 종료

 

스마트폰 플랫폼은 PC와 달리 제한된 리소스(대표적으로RAM)을 가지고 있기 때문에 여러 가지 작업을 동시에 진행할 때 리소스 부족현상에 직면할 수 있다.

 

이는 심각한 문제를 유발 할 수도 있는데, 한가지 예를 들어 보자. 예를 들어 사용자가 이용하고 있는 게임 어플리케이션의 Activity A가 foreground서 사용자와 interact하고 있다고 치자. 이때 전화가 걸려오면 시스템은 incoming call Activity B를 foreground에 띄워 사용자에게 전화가 왔음을 알려야 한다. 하지만 만약에 A가 시스템의 리소스를 거의 전부 점유하고 있는 상황이라면 B Activity를 띄우는 것이 불가능 할 수 있다.

 

스마트폰에서 게임을 하는 것 보다 전화를 받는 Task가 훨씬 중요한 것이 일반적임으로, 이 경우 디바이스의 효율적인 운영이 이뤄진다고 볼 수 없고 이는 곧 user의 불편함으로 이어진다.

 

그래서 안드로이드에서는 여러 개의 Activity를 운영 중 시스템 리소스가 부족하면 특정 상태에 있는 Activity를 강제로 종료 할 수 있게 디자인 되어있다. Activity의 상태에 따른 강제 종료 우선 순위는 다음과 같다.

Running 상태: 절대 강제 종료 되지 않음

Pause 상태: 강제 종료 2순위

Stop 상태: 강제 종료 1 순위

 

전 단락의 life cycle 관련 callback 테이블을 보면 강제 종료 칼럼에 yes라고 표기된 onPause(), onStop(), onDestroy()가 있는데 이들 callback이 호출 된 뒤에는 Activity가 언제든지 강제 종료 될 수 있다는 뜻이다.

 

한가지 중요한 것은 onPause() callback이 실행 중에는 Activity는 아직 Running 상태이고 onPause()가 리턴 하자마자 Activity는 Pause상태가 되기 때문에 onPause() callback은 항상 return이 보장되는 반면 나머지 2 개의 callback, onStop(), onDestroy()이 실행 될 때는 Activity는 각각 Pause상태, Stop상태이기 때문에 return이 보장되지 않는다.

 

이런 이유(onPause() 이후에는 Activity가 강제 종료 당할 수 있음)로 onPause() callback에서 사용자가 진행하던 작업을 저장 하는 등의 강제 종료에 대비한 작업을 해주어야 한다.

 

 

 

강제 종료된 Activity의 복구

 

강제 종료된 Activity는 가동하는데 충분한 resource가 확보되면 (예를 들면 Activity A를 강제 종료 시켰던 Activity B의 종료 등) 자동으로 복구되지만, 한가지 작업이 이루어 지지 않는다면 사용자에게 심각한 불편을 초래할 수 있다. 다음과 같은 시나리오를 가정해 보자.

User가 계산기 어플리케이션의 Activity A를 통해 시간이 오래 걸리는 작업(예, 숫자 100개 더하기 중 50개를 더한 상황) 중에 전화가 걸려왔는데 시스템의 리소스 부족으로 incoming call Activity B를 띄울 수 없는 상황이 발생 했다고 쳐보자. 시스템은 A를 강제 종료 시켜 부족한 리소스를 확보하고 B를 사용자에게 보여줘 사용자가 전화를 받을 수 있게 한다. 전화 통화를 완료한 후 시스템은 강제종료 된 A를 자동으로 복구 했지만 사용자가 이미 수행하고 있던 작업(100개중 50를 더한 중간 결과)의 상태는 초기화 되어 버리고 사용자는 다시 처음부터 작업을 수행해야 한다.

 

위와 같은 불편을 막기 위해, 안드로이드의 Activity 클래스는 onSaveInstanceState(Bundle) 메소드와 onRestoreInstanceState(Bundle) callback 메소드를 제공한다.

 

우선 위의 Activity 상태변이도에서 본것과 같이,

onSaveInstanceState(Bundle)은 onPause()전에 호출되며 파라미터로 전달 받는 Bundle 인스턴스에 현재 activity의 상태를 저장하게 된다. 저장된 Bundle인스턴스는 시스템이 Activity를 자동으로 복원할 때 호출되는 onCreate(Bundle) -> onStart() -> onRestroreInstanceState(Bundle) -> onResume() callback 중 onCreate(…)와 onRestoreInstanceState(…) callback에 모두 파라미터로 전달 됨으로 양쪽 어디서건 사용해서 강제종료 되기 전의 상태로 Activity를 복구 시키면 된다.

 

한가지 참고할 사상은 onSaveInstanceState(Bundle)과 onRestroreInstanceState(Bundle)은 life cycle과 직접적으로 관련이 있는 callback이 아님으로 Activity의 상태 변화 시 항상 호출된다는 보장이 없다. 논리적으로 꼭 필요한 상황에서만 호출됨으로 Activity상태가 바뀔 때 예외 없이 호출 되어야 하는 루틴은 life cycle 관련 callback (onResume(), onPause() 등)에서 구현 하여야 한다.

 

예를 들면, 사용자가 Activity 를 디바이스의 BACK key 로 직접 종료하거나 Activity.finish() 메소드를 사용해 정상 종료되는 경우에는 Activity의 현 상태를 복구할 필요가 없기 때문에 onSaveInstanceState(Bundle) callback의 호출은 생략된다.

 

 

 

 

4. Activity 생명주기 예제 (Activity Life Cycle Demo)

어떠한 상황에서 Activity가 상태변이를 일으키는지 눈으로 직접 확인하기 위해 예제를 작성했지만, Activity의 강제 종료를 시뮬레이션 하기 위해서는 실제 메모리가 부족한 상황을 만들어야 하는데 이 부분은 추후 업데이트를 할 예정이다.

 

예제는 2개의 버튼을 가지고 있는 Main Activity 한 개와 sub Activity 2개로 이루어졌다.

Sub Activity중 하나는 Pause 상태를 재현하기 위한 반투명 배경의 Activity이며 (Pause 상태의 조건은 running 중인 activity가 반투명 Activity난 non-full screen Activity에 의해 일부가 가렸을 때 임을 상기), 두 번째 Activity는 Main Activity 의 Stop상태를 재현하기 위한 full screen창이다.

 

상태변이를 확인하기 위해서는 life cycle관련 callback이 호출될 때 마다 Logcat에 Log를 남기는 방법을 사용했다.

(Logcat 사용 방법은 링크 참조)

 

 

Running 상태 재현

 

Activity의 생성시를 보면 다음과 같은 callback들이 호출되는 것을 볼 수 있다.

 

Logcat Log 내용

 

 

 

Running 상태 -> Pause상태 -> Running 상태 재현

 

Running상태인 Main Activity를 Pause상태로 전환 시키기 위해 반투명 sub activity를 foreground로 불러오면 다음과 같은 순서로 callback들이 호출된다.

 

Logcat Log내용

 

눈 여겨 볼만한 점은 onSaveInstanceState(Bundle)의 호출이다. Pause상태의 main activity는 언제 강제 종료 당할지 모르기 때문에 onSaveInstanceState(Bundle)이 call 되고 있다.

 

 

다시 Main Activity로 돌아오기 위해 반투명 sub Activity를 종료하면 (finish() 호출) 다음과 같은 callback이 차례로 호출된다.

 

Logcat Log내용

 

Sub Activity를 우선 종료 후 Main Activity를 foreground로 되돌리는 것이 아니라 Sub Activity를 Pause상태로 만들고 Main Activity가 Sub Activity를 가리면 Sub Activity가 Stop상태로 전환 되고 소멸 된다.

 

 

 

Running 상태 -> Stop상태 -> Running 상태 재현

 

Running 상태인 Main Activity를 Stop상태로 전환 시키기 위해 full-screen size의 Activity를 main activity 앞으로 불러온다.

 

Logcat Log 내용

 

여기서도 Main Activity가 Pause상태에 들어가고 Sub Activity가 foreground를 점유하면서 Main Activity를 완전히 가리면 Main Activity가 Stop() 상태로 전환되는 것을 볼 수 있다.

 

 

Full screen sub Activity를 닫으면 (Activity.finish() 메소드 호출) main activity가 running 상태로 복귀 하는 것을 볼 수 있다.

 

Logcat Log 내용

 

Stop상태였던 Main Activity가 Running 상태로 전환되면서 onRestart()가 호출되는 것을 볼 수 있다.

또, Sub Activity는 Activity.finish() 메소드를 사용해 정상적으로 종료되는 상황이라 onPause()호출 전에 onSaveInstanceState(Bundle) 메소드가 생략된 것도 볼 수 있다.