액티비티 생명주기를 이해하는 것은 매우 중요합니다. 이해하면 매끄러운 앱을 작성할 수 있습니다.
그리고 앱 리소스를 합리적으로 관리하는 데에도 큰 도움이 됩니다.
더 나은 사용자 경험을 제공할 수 있습니다.
Android의 액티비티는 서로 겹쳐서 쌓을 수 있습니다.
새 액티비티를 시작할 때마다 기존 액티비티 위에 덮어씌워집니다.
그리고 백 키를 누르면 현재 액티비티가 소멸되고 이전 액티비티가 다시 나타납니다.
사실 Android는 이를 관리하기 위해 ‘태스크(Task)’를 사용합니다.
태스크란 스택에 저장된 액티비티들의 집합입니다.
이 스택을 ‘백 스택(Back Stack)’이라고 부릅니다.
스택은 후입선출(LIFO) 데이터 구조입니다.
기본적으로 새 액티비티를 시작하면 스택에 푸시되어 맨 위에 놓입니다.
백 키를 누르거나 finish()를 실행하면 스택 최상단의 액티비티가 소멸합니다. 그런 다음 이전에 스택에 들어있던 액티비티가 다시 최상단으로 돌아옵니다. 시스템은 항상 스택 최상단의 액티비티를 사용자에게 표시합니다.
액티비티 상태
각 액티비티는 생명주기 동안 최대 4가지 상태 중 하나에 있을 수 있습니다.
1. 실행 상태 (Running)
스택 최상단에 있을 때입니다. 시스템은 사용자 경험 저하를 우려해 이를 회수하려 하지 않습니다.
2. 일시 정지 상태 (Paused)
스택 최상단에 있지는 않지만 여전히 보이는 상태입니다. 예를 들어 현재 액티비티 위에 다이얼로그 액티비티가 나타나 화면 일부만 차지하는 경우(모달 창과 비슷함)입니다. 시스템은 이것도 회수하려 하지 않습니다. 갑자기 사라지면 사용자에게 좋지 않은 경험을 주기 때문입니다.
3. 정지 상태 (Stopped)
액티비티가 최상단에 없고 완전히 보이지 않게 되면 정지 상태가 됩니다. 시스템은 상태와 멤버 변수를 보관하지만, 이것은 확실하지 않습니다. 다른 곳에서 메모리가 필요하면 정지된 액티비티도 회수 대상이 됩니다.
4. 소멸 상태 (Destroyed)
액티비티가 백 스택에서 제거되면 소멸 상태가 됩니다. 시스템은 메모리 확보를 위해 이들을 가장 먼저 회수합니다.
액티비티 수명 주기와 7가지 콜백 메서드
Activity 클래스에는 액티비티 생명주기의 모든 단계를 다루는 7가지 콜백 메서드가 정의되어 있습니다.
간단히 말해, 이 7가지 콜백 함수는 생명주기의 특정 시점에 호출되어 그 안의 코드를 실행합니다.
예를 들어 액티비티가 처음 생성될 때 onCreate가 트리거됩니다.
각 메서드를 하나씩 살펴보겠습니다.

onCreate:
액티비티가 처음 생성될 때 호출됩니다. 레이아웃 로드, 버튼 이벤트 연결 등 초기화 작업을 수행합니다.
onStart:
액티비티가 보이지 않던 상태에서 보이는 상태로 바뀔 때 트리거됩니다.
onResume:
액티비티가 사용자와 상호작용할 준비가 되었을 때 호출됩니다. 이때 액티비티는 백 스택 최상단에 있으며 실행 상태입니다. (사용자에게 보이고 있는 상태입니다)
onPause:
시스템이 다른 액티비티를 시작하거나 재개하려 할 때 호출됩니다. 보통 CPU를 많이 쓰는 리소스를 해제하거나 중요한 데이터를 저장하는 데 사용하지만, 실행 속도가 빨라야 합니다. 그렇지 않으면 새 액티비티의 표시가 지연됩니다. (현재 액티비티가 곧 숨겨지고 다음 액티비티로 넘어가기 직전의 상태입니다)
onStop:
액티비티가 완전히 보이지 않게 되었을 때 호출됩니다. onPause와의 주요 차이점은 새로 시작한 액티비티가 다이얼로그 형태라면 onPause는 실행되지만 onStop은 실행되지 않는다는 점입니다. (액티비티가 숨겨지고 새 액티비티가 나타납니다)
onDestroy:
액티비티가 소멸되기 직전에 호출됩니다. 이후 액티비티는 소멸 상태가 됩니다.
onRestart:
정지 상태에 있던 액티비티가 실행 상태로 돌아가기 직전에 호출됩니다. 즉, 액티비티가 다시 시작될 때입니다.
세 가지 수명 주기
위의 7가지 메서드 중 onRestart를 제외한 나머지는 두 개씩 짝을 이룹니다. 따라서 액티비티의 수명을 세 가지로 나눌 수 있습니다.
1. 전체 수명 (Full Lifetime)
onCreate부터 onDestroy까지의 기간을 전체 수명이라고 합니다.
2. 가시 수명 (Visible Lifetime)
onStart부터 onStop까지의 기간을 가시 수명이라고 합니다. 이 동안 액티비티는 사용자에게 계속 보입니다. 이 메서드들을 활용해 리소스를 관리할 수 있습니다. 예를 들어 onStart에서 리소스를 로드하고 onStop에서 해제하여, 정지된 액티비티가 불필요한 리소스를 점유하지 않도록 합니다.
3. 포그라운드 수명 (Foreground Lifetime)
onResume부터 onPause까지의 기간을 포그라운드 수명이라고 합니다. 액티비티는 실행 중이며 사용자와 상호작용할 수 있습니다. 우리가 평소 가장 많이 보게 되는 상태입니다.
메인 액티비티 열기 → 일반 액티비티 진입 → 메인 액티비티로 돌아가기 → 홈 화면으로 돌아가기
05-12 15:32:43.899 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onCreate:
05-12 15:32:43.926 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onStart:
05-12 15:32:43.930 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onResume:
05-12 15:33:00.973 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onPause:
05-12 15:33:01.078 4316-4316/com.foxdevelop.www.lifedemo E/일반 액티비티 로그: onCreate:
05-12 15:33:01.079 4316-4316/com.foxdevelop.www.lifedemo E/일반 액티비티 로그: onStart:
05-12 15:33:01.081 4316-4316/com.foxdevelop.www.lifedemo E/일반 액티비티 로그: onResume:
05-12 15:33:02.969 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onStop:
05-12 15:34:01.747 4316-4316/com.foxdevelop.www.lifedemo E/일반 액티비티 로그: onPause:
05-12 15:34:01.779 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onRestart:
05-12 15:34:01.779 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onStart:
05-12 15:34:01.780 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onResume:
05-12 15:34:02.749 4316-4316/com.foxdevelop.www.lifedemo E/일반 액티비티 로그: onStop:
05-12 15:34:02.750 4316-4316/com.foxdevelop.www.lifedemo E/일반 액티비티 로그: onDestroy:
05-12 15:35:26.827 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onPause:
05-12 15:35:28.534 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onStop:
05-12 15:34:01.779 4316-4316/com.foxdevelop.www.lifedemo E/메인 액티비티 로그: onDestroy:
프로젝트 실습
새 프로젝트를 만들어 생명주기를 직접 체험해 봅시다
액티비티 이름은 LifeDemo로 하겠습니다.
EmptyActivity를 메인 액티비티로 추가합니다.
그다음 NormalActivity와 DialogActivity 두 개의 일반 액티비티를 더 추가합니다.
그리고 각각의 레이아웃을 수정합니다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="일반 액티비티" />
</LinearLayout>Code language: HTML, XML (xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="다이얼로그 액티비티" />
</LinearLayout>Code language: HTML, XML (xml)
두 레이아웃은 표시되는 텍스트만 다를 뿐 기본적으로 동일합니다.
DialogActivity를 다이얼로그 형태의 액티비티로 변경해야 합니다.
AndroidManifest.xml을 수정합니다:
<activity android:name=".NormalActivity" />
<activity android:name=".DialogActivity" android:theme="@style/Base.Theme.AppCompat.Dialog"/>
</application>Code language: HTML, XML (xml)
이렇게 하면 DialogActivity가 다이얼로그 테마를 사용하게 됩니다.
메인 액티비티 레이아웃
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="일반 액티비티 열기" />
<Button
android:id="@+id/buttondialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="다이얼로그 액티비티 열기" />
</LinearLayout>Code language: HTML, XML (xml)
메인 액티비티의 두 버튼에 클릭 이벤트 추가
package com.foxdevelop.www.lifedemo;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b = (Button)findViewById(R.id.button);
b.setOnClickListener(
new View.OnClickListener(){
@Override
public void onClick(View view){
Intent it = new Intent(MainActivity.this,NormalActivity.class);
startActivity(it);
}
}
);
Button b2 = (Button)findViewById(R.id.buttondialog);
b2.setOnClickListener(
new View.OnClickListener(){
@Override
public void onClick(View view){
Intent it = new Intent(MainActivity.this,DialogActivity.class);
startActivity(it);
}
}
);
}
@Override
protected void onStart() {
super.onStart();
Log.e(TAG, "onStart: ");
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "onResume: ");
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG, "onPause: ");
}
@Override
protected void onStop() {
super.onStop();
Log.e(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: ");
}
@Override
protected void onRestart() {
super.onRestart();
Log.e(TAG, "onRestart: ");
}
}Code language: JavaScript (javascript)
1. 앱 실행
메인 액티비티가 생성될 때 onCreate, onStart, onResume이 순서대로 실행됩니다.

‘일반 액티비티 열기’ 버튼을 누르면 onPause와 onStop이 트리거됩니다.

NormalActivity가 MainActivity를 완전히 가리기 때문에 두 메서드 모두 호출됩니다.
백 키를 눌러 돌아갑니다.

이전 MainActivity가 정지 상태에 있었기 때문에 onRestart가 트리거되고, 이어서 onStart와 onResume이 순서대로 실행됩니다.
하지만 onCreate는 실행되지 않습니다. 다시 생성된 것이 아니기 때문입니다. 이때 NormalActivity는 자신의 onDestroy를 트리거했을 것입니다.
다시 ‘다이얼로그 열기’ 버튼을 누릅니다.
이번에는 onPause만 트리거됩니다.
onStop은 트리거되지 않은 것을 볼 수 있습니다.
그 이유는 DialogActivity가 MainActivity를 완전히 가리지 않기 때문입니다.

마찬가지로 이 상태에서 백 키를 누르면 onResume만 실행됩니다. 정지되지 않았으므로 onStart는 호출되지 않습니다.


이처럼 onPause, onStop, onDestroy가 순서대로 트리거되는 것을 볼 수 있습니다.
Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.Code language: CSS (css)
메인 액티비티 생성 → 다이얼로그 액티비티 열기 → 메인 액티비티로 돌아가기 → 홈 화면으로 돌아가기
05-13 02:10:51.635 4206-4206/com.foxdevelop.www.lifedemo E/메인 액티비티: onCreate: // 새 액티비티 생성
05-13 02:10:51.673 4206-4206/com.foxdevelop.www.lifedemo E/메인 액티비티: onStart: // 보이지 않던 상태에서 보임
05-13 02:10:51.701 4206-4206/com.foxdevelop.www.lifedemo E/메인 액티비티: onResume: // 액티비티가 화면에 나타남
05-13 02:12:01.993 4206-4206/com.foxdevelop.www.lifedemo E/메인 액티비티: onPause: // 현재 액티비티에서 다른 액티비티로 이동
05-13 02:12:02.115 4206-4206/com.foxdevelop.www.lifedemo E/다이얼로그 액티비티: onCreate:
05-13 02:12:02.116 4206-4206/com.foxdevelop.www.lifedemo E/다이얼로그 액티비티: onStart:
05-13 02:12:02.143 4206-4206/com.foxdevelop.www.lifedemo E/다이얼로그 액티비티: onResume:
05-13 02:14:26.078 4206-4206/com.foxdevelop.www.lifedemo E/다이얼로그 액티비티: onPause: // 백 키를 눌러 메인 액티비티로 복귀
05-13 02:14:26.367 4206-4206/com.foxdevelop.www.lifedemo E/메인 액티비티: onResume: // 정지되지 않았으므로 바로 나타남
05-13 02:14:26.803 4206-4206/com.foxdevelop.www.lifedemo E/다이얼로그 액티비티: onStop: // 다이얼로그 액티비티가 보이지 않게 됨
05-13 02:14:26.803 4206-4206/com.foxdevelop.www.lifedemo E/다이얼로그 액티비티: onDestroy: // 백 또는 finish()로 소멸됨
05-13 02:16:23.959 4206-4206/com.foxdevelop.www.lifedemo E/메인 액티비티: onPause:
05-13 02:16:25.606 4206-4206/com.foxdevelop.www.lifedemo E/메인 액티비티: onStop:
05-13 02:16:25.609 4206-4206/com.foxdevelop.www.lifedemo E/메인 액티비티: onDestroy: // 소멸 전에는 항상 pause와 stop이 발생함Code language: JavaScript (javascript)
액티비티의 생명주기