The Android Developer Challenge is back! Submit your idea before December 2.

Activity 수명 주기에 대한 이해

사용자가 앱을 탐색하고, 앱에서 나가고, 앱으로 다시 돌아가면, 앱의 Activity 인스턴스는 수명 주기 안에서 서로 다른 상태를 통해 전환됩니다. Activity 클래스는 Activity가 상태 변화(시스템이 Activity를 생성, 중단 또는 다시 시작하거나, Activity가 있는 프로세스를 종료하는 등)를 알아차릴 수 있는 여러 콜백을 제공합니다.

사용자가 Activity를 떠났다가 다시 돌아왔을 때 Activity가 어떤 식으로 동작할지에 대해 수명 주기 콜백 메서드 내에 선언할 수 있습니다. 예를 들어 스트리밍 동영상 플레이어를 빌드하는 경우, 사용자가 다른 앱으로 전환할 때 비디오를 일시 중지하고 네트워크 연결을 종료할 수 있습니다. 사용자가 돌아오면, 네트워크를 다시 연결하고 사용자가 일시 중지한 지점에서 동영상을 다시 시작할 수 있게 할 수 있습니다. 즉, 각 콜백은 상태 변화에 적합한 특정 작업을 수행할 수 있도록 합니다. 적절한 시점에 적절한 작업을 수행하고 적절하게 전환을 처리하면 앱이 더욱 안정적이고 성능을 잘 발휘할 수 있습니다. 예를 들어 수명 주기 콜백을 잘 구현하면 앱에서 다음과 같은 문제가 발생하지 않도록 예방하는 데 도움이 될 수 있습니다.

  • 사용자가 앱을 사용하는 도중에 전화가 걸려오거나 다른 앱으로 전환할 때 비정상 종료됩니다.
  • 사용자가 앱을 활발하게 사용하지 않는 경우, 소중한 시스템 리소스가 소비됩니다.
  • 사용자가 앱에서 나갔다가 나중에 돌아왔을 때 사용자의 진행 상태가 손실됩니다.
  • 화면이 가로 방향과 세로 방향 간에 회전할 경우, 비정상 종료되거나 사용자의 진행 상태가 손실됩니다.

이 문서에서는 Activity 수명 주기에 대해 자세히 설명합니다. 먼저 수명 주기 패러다임에 대해 설명하고, 그다음에는 콜백에 대해 설명합니다. 콜백이 실행되는 동안 내부에서 어떤 일이 일어나고, 그 과정에서 무엇을 구현해야 하는지 알려드립니다. 그런 다음, 시스템에 의해 종료되는 프로세스의 취약점과 Activity 상태 간의 관계에 대해 간략히 소개합니다. 마지막으로, Activity 상태 간 전환에 대한 여러 가지 주제를 다룹니다.

권장사항에 대한 지침을 포함한 수명 주기 처리와 관련된 자세한 내용은 수명 주기 인식 구성요소로 수명 주기 처리UI 상태 저장을 참조하세요. 아키텍처 구성요소와 Activity를 함께 사용하여 안정적인 프로덕션 품질의 앱을 설계하는 방법에 대한 자세한 내용은 앱 아키텍처 가이드를 참조하세요.

Activity-수명 주기 개념

Activity 수명 주기 상태 간의 전환하기 위해 Activity 클래스는 6가지 콜백으로 구성된 핵심 세트(onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy())를 제공합니다. Activity가 새로운 상태에 들어가면 시스템은 각 콜백을 호출합니다.

그림 1은 이 패러다임을 시각적으로 나타낸 것입니다.

그림 1. Activity 수명 주기를 간략하게 표현한 그림.

사용자가 Activity를 떠나기 시작하면 시스템은 Activity를 해체할 메서드를 호출합니다. 어떤 경우에는 부분적으로만 해체하기도 합니다. 이때 Activity는 여전히 메모리 안에 남아 있으며(예: 사용자가 다른 앱으로 전환할 경우) 포그라운드로 다시 돌아올 수 있습니다. 사용자가 해당 Activity로 돌아오는 경우 Activity는 사용자가 종료한 지점에서 다시 시작됩니다. 몇 가지 예외를 제외하고 앱은 백그라운드에서 실행될 때 Activity를 실행할 수 없습니다.

시스템은 그 시점의 Activity 상태에 따라 특정 프로세스와 그 안의 Activity를 함께 종료할지 여부를 결정합니다. Activity 상태 및 메모리에서 제거는 Activity 상태와 제거 취약성과의 관계에 대한 자세한 정보를 제공합니다.

Activity의 복잡한 정도에 따라, 모든 수명 주기 메서드를 구현할 필요가 없는 경우도 있습니다. 하지만 각각의 수명 주기 메서드를 이해하고, 사용자가 예상한 대로 앱이 동작하도록 필요한 수명 주기 메서드를 구현하는 것이 중요합니다.

이 문서의 다음 섹션에서는 상태 간 전환을 처리할 때 사용하는 콜백에 대한 자세한 내용을 다룹니다.

수명 주기 콜백

이 섹션에서는 Activity 수명 주기에 사용하는 콜백 메서드의 개념 및 구현 정보를 제공합니다.

setContentView()를 호출하는 등 일부 작업은 Activity 수명 주기 메서드 그 자체에 속해 있습니다. 그러나 종속적인 구성요소의 작업을 구현하는 코드는 해당 구성요소 안에 넣어야 합니다. 이를 위해서는 종속적인 구성요소가 수명 주기를 인식하도록 해야 합니다. 종속적인 구성요소가 수명 주기를 인식하게 하는 방법은 수명 주기 인식 구성요소로 수명 주기 처리를 참조하세요.

onCreate()

이 콜백은 시스템이 먼저 Activity를 생성할 때 실행되는 것으로, 필수적으로 구현해야 합니다. Activity가 생성되면 Activity는 생성됨 상태가 됩니다. onCreate() 메서드에서 Activity의 전체 수명 주기 동안 한 번만 발생해야 하는 기본 애플리케이션 시작 로직을 수행합니다. 예를 들어 onCreate()를 구현하면 데이터를 목록에 바인딩하고, Activity를 ViewModel과 연결하고, 일부 클래스 범위 변수를 인스턴스화할 수 있습니다. 이 메서드는 savedInstanceState 매개변수를 수신하는데, 이는 Activity의 이전 저장 상태가 포함된 Bundle 객체입니다. 이번에 처음 생성된 Activity인 경우 Bundle 객체의 값은 null입니다.

Activity의 수명 주기와 연결된 수명 주기를 인식하는 구성요소가 있다면 이 구성요소는 ON_CREATE 이벤트를 수신합니다. 따라서 @OnLifecycleEvent라는 주석이 있는 메서드가 호출되고, 수명 주기를 인식하는 구성요소는 생성됨 상태에 필요한 모든 설정 코드를 실행할 수 있게 됩니다.

onCreate() 메서드에 대한 다음 예시는 사용자 인터페이스 선언(XML 레이아웃 파일에 정의됨), 멤버 변수 정의, 일부 UI 구성 등의 Activity에 대한 기본 설정을 보여줍니다. 이 예시에서 XML 레이아웃 파일은 파일의 리소스 ID인 R.layout.main_activitysetContentView()에 전달하여 지정합니다.

Kotlin

lateinit var textView: TextView

// some transient state for the activity instance
var gameState: String? = null

override fun onCreate(savedInstanceState: Bundle?) {
    // call the super class onCreate to complete the creation of activity like
    // the view hierarchy
    super.onCreate(savedInstanceState)

    // recovering the instance state
    gameState = savedInstanceState?.getString(GAME_STATE_KEY)

    // set the user interface layout for this activity
    // the layout file is defined in the project res/layout/main_activity.xml file
    setContentView(R.layout.main_activity)

    // initialize member TextView so we can manipulate it later
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance that is previously saved by using
// onSaveInstanceState(). We restore some state in onCreate(), while we can optionally restore
// other state here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)
}

// invoked when the activity may be temporarily destroyed, save the instance state here
override fun onSaveInstanceState(outState: Bundle?) {
    outState?.run {
        putString(GAME_STATE_KEY, gameState)
        putString(TEXT_VIEW_KEY, textView.text.toString())
    }
    // call superclass to save any view hierarchy
    super.onSaveInstanceState(outState)
}

Java

TextView textView;

// some transient state for the activity instance
String gameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // call the super class onCreate to complete the creation of activity like
    // the view hierarchy
    super.onCreate(savedInstanceState);

    // recovering the instance state
    if (savedInstanceState != null) {
        gameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // set the user interface layout for this activity
    // the layout file is defined in the project res/layout/main_activity.xml file
    setContentView(R.layout.main_activity);

    // initialize member TextView so we can manipulate it later
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance that is previously saved by using
// onSaveInstanceState(). We restore some state in onCreate(), while we can optionally restore
// other state here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    textView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// invoked when the activity may be temporarily destroyed, save the instance state here
@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString(GAME_STATE_KEY, gameState);
    outState.putString(TEXT_VIEW_KEY, textView.getText());

    // call superclass to save any view hierarchy
    super.onSaveInstanceState(outState);
}

XML 파일을 정의하고 setContentView()에 전달하는 대신, Activity 코드에 새로운 View 객체를 생성하고 ViewViewGroup에 넣어서 뷰 계층 구조를 빌드할 수 있습니다. 그런 다음, 루트 ViewGroupsetContentView()에 전달하여 그 레이아웃을 사용합니다. 사용자 인터페이스 생성에 대한 자세한 내용은 사용자 인터페이스 문서를 참조하세요.

Activity는 생성됨 상태에 있지 않습니다. onCreate() 메서드가 실행을 완료하면 시작됨 상태가 되고, 시스템이 연달아 onStart()onResume() 메서드를 호출합니다. 다음 섹션에서는 onStart() 콜백에 대해 설명합니다.

onStart()

Activity가 시작됨 상태에 들어가면 시스템은 이 콜백을 호출합니다. onStart()가 호출되면 Activity가 사용자에게 보이게 되고, 앱은 Activity를 포그라운드에 보내 상호작용할 수 있도록 준비합니다. 예를 들어 이 메서드에서 앱이 UI를 관리하는 코드를 초기화합니다.

Activity가 시작됨 상태로 전환하면 이 Activity의 수명 주기와 연결된 모든 수명 주기 인식 구성요소는 ON_START 이벤트를 수신합니다.

onStart() 메서드는 매우 빠르게 완료되고, 생성됨 상태와 마찬가지로 Activity는 시작됨 상태에 머무르지 않습니다. 이 콜백이 완료되면 Activity가 재개됨 상태에 들어가고, 시스템이 onResume() 메서드를 호출합니다.

onResume()

Activity가 재개됨 상태에 들어가면 포그라운드에 표시되고 시스템이 onResume() 콜백을 호출합니다. 이 상태에 들어갔을 때 앱이 사용자와 상호작용합니다. 어떤 이벤트가 발생하여 앱에서 포커스가 떠날 때까지 앱이 이 상태에 머무릅니다. 예를 들어 전화가 오거나, 사용자가 다른 Activity로 이동하거나, 기기 화면이 꺼지는 등 이벤트가 이에 해당합니다.

Activity가 재개됨 상태로 전환되면 이 Activity의 수명 주기와 연결된 모든 수명 주기 인식 구성요소는 ON_RESUME 이벤트를 수신합니다. 이 상태에서 수명 주기 구성요소가 포그라운드에서 사용자에게 보이는 동안 실행해야 하는 모든 기능을 활성화할 수 있습니다(예: 카메라 미리보기 시작).

방해되는 이벤트가 발생하면 Activity는 일시정지됨 상태에 들어가고, 시스템이 onPause() 콜백을 호출합니다.

Activity가 일시정지됨 상태에서 재개됨 상태로 돌아오면 시스템이 onResume() 메서드를 다시 한번 호출합니다. 따라서 onResume()을 구현하여 onPause() 중에 해제하는 구성요소를 초기화하고, Activity가 재개됨 상태로 전환될 때마다 필요한 다른 초기화 작업도 수행해야 합니다.

구성요소가 ON_RESUME 이벤트를 수신할 때 카메라에 액세스하는 수명 주기 인식 구성요소의 예시는 다음과 같습니다.

Kotlin

class CameraComponent : LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun initializeCamera() {
        if (camera == null) {
            getCamera()
        }
    }

    ...
}

Java

public class CameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void initializeCamera() {
        if (camera == null) {
            getCamera();
        }
    }

    ...
}

위의 코드는 LifecycleObserver가 ON_RESUME 이벤트를 수신하면 카메라를 초기화합니다. 그러나 멀티 윈도우 모드에서는 Activity가 일시정지됨 상태에 있더라도 완전히 보이는 상태일 수 있습니다. 예를 들어 사용자가 멀티 윈도우 모드에 있을 때 Activity를 포함하지 않는 다른 창을 탭하면 Activity가 일시정지됨 상태로 전환됩니다. 앱이 재개됨 상태(포그라운드에 표시되고 활성화된 상태)인 경우에만 카메라를 활성화하고 싶다면 위의 ON_RESUME 이벤트가 실행된 다음에 카메라를 초기화합니다. Activity가 일시정지됨 상태이지만 표시되어 있는 동안(예: 멀티 윈도우 모드) 카메라를 활성 상태로 유지하고 싶다면 ON_START 이벤트가 실행된 이후에 카메라를 초기화해야 합니다. 그러나 Activity가 일시정지됨 상태일 때 카메라를 활성화하면 멀티 윈도우 모드에서 재개됨 상태에 있는 다른 앱이 카메라에 액세스하지 못할 수도 있습니다. Activity가 일시정지됨 상태일 때 카메라를 활성 상태로 유지해야 하는 경우도 있지만, 이 경우 전반적인 사용자 환경이 실질적으로 저하될 수 있습니다. 이 수명 주기의 경우 언제 멀티 윈도우 환경에서 공유된 시스템 리소스를 제어하는 것이 보다 적절한지 신중하게 생각하세요. 멀티 윈도우 모드 지원에 대한 자세한 내용은 멀티 윈도우 지원을 참조하세요.

어떤 빌드업 이벤트에서 초기화 작업을 실행하든 그에 상응하는 수명 주기 이벤트를 사용하여 리소스를 해제하세요. ON_START 이벤트 이후에 무언가를 초기화하는 경우, ON_STOP 이벤트 이후에 이를 해제하거나 종료하세요. ON_RESUME 이벤트 이후에 초기화하는 경우, ON_PAUSE 이벤트 이후에 해제하세요.

위의 코드 스니펫은 카메라 초기화 코드를 수명 주기 인식 구성요소에 넣습니다. 이 방법 대신 Activity 수명 주기 콜백(예: onStart(), onStop())을 직접 넣을 수는 있지만 권장하지는 않습니다. 이 로직을 독립적인 수명 주기 인식 구성요소에 넣으면 코드를 복사하지 않고도 여러 Activity에서 구성요소를 다시 사용할 수 있습니다. 수명 주기 인식 구성요소를 생성하는 방법에 대해 알아보려면 수명 주기 인식 구성요소로 수명 주기 처리를 참조하세요.

onPause()

시스템은 사용자가 Activity를 떠나는 것을 나타내는 첫 번째 신호로 이 메서드를 호출합니다(하지만 해당 Activity가 항상 소멸되는 것은 아닙니다). Activity가 포그라운드에 있지 않게 되었다는 것을 나타냅니다(다만 사용자가 멀티 윈도우 모드에 있을 경우에는 여전히 보일 수도 있습니다). onPause() 메서드를 사용하여 Activity가 일시정지됨 상태일 때 계속 실행(또는 적절히 계속 실행)되어서는 안 되지만 잠시 후 다시 시작할 작업을 일시정지하거나 조정합니다. Activity가 이 상태에 들어가는 이유는 여러 가지가 있습니다. 예를 들면 다음과 같습니다.

  • onResume() 섹션에서 설명하였듯이, 일부 이벤트가 앱 실행을 방해합니다. 이것이 가장 일반적인 사례입니다.
  • Android 7.0(API 레벨 24) 이상에서는 여러 앱이 멀티 윈도우 모드에서 실행됩니다. 언제든 그중 하나의 앱(창)만 포커스를 가질 수 있기 때문에 시스템이 그 외에 모든 다른 앱을 일시정지시킵니다.
  • 새로운 반투명 Activity(예: 대화상자)가 열립니다. Activity가 여전히 부분적으로 보이지만 포커스 상태가 아닌 경우에는 일시정지됨 상태로 유지됩니다.

Activity가 일시정지됨 상태로 전환하면 이 Activity의 수명 주기와 연결된 모든 수명 주기 인식 구성요소는 ON_PAUSE 이벤트를 수신합니다. 여기에서 수명 주기 구성요소는 구성요소가 포그라운드에 있지 않을 때 실행할 필요가 없는 기능을 모두 정지할 수 있습니다(예: 카메라 미리보기 정지).

또한 onPause() 메서드를 사용하여 시스템 리소스, 센서 핸들(예: GPS) 또는 Activity가 일시정지 중이고 사용자가 필요로 하지 않을 때 배터리 수명에 영향을 미칠 수 있는 모든 리소스를 해제할 수도 있습니다. 그러나 앞서 onResume() 섹션에서 언급했듯이 일시정지된 Activity는 멀티 윈도우 모드에서 여전히 완전히 보이는 상태일 수 있습니다. 그러므로 멀티 윈도우 모드를 더욱 잘 지원하기 위해 UI 관련 리소스와 작업을 완전히 해제하거나 조정할 때는 onPause() 대신 onStop()을 사용하는 것이 좋습니다.

다음과 같이 LifecycleObserver가 ON_PAUSE 이벤트에 응답하는 예는 위의 ON_RESUME 이벤트에 대응하며, ON_RESUME 이벤트가 수신된 후 초기화된 카메라를 해제합니다.

Kotlin

class CameraComponent : LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun releaseCamera() {
        camera?.release()
        camera = null
    }

    ...
}

Java

public class JavaCameraComponent implements LifecycleObserver {

    ...

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }

    ...
}

위의 코드 스니펫은 LifecycleObserver가 ON_PAUSE 이벤트를 수신한 이후에 카메라 해제 코드를 넣습니다. 앞서 언급했듯이, 수명 주기 인식 구성요소를 생성하는 방법에 대해 알아보려면 수명 주기 인식 구성요소로 수명 주기 처리를 참조하세요.

onPause()는 아주 잠깐 실행되므로 저장 작업을 수행하기에는 시간이 부족할 수 있습니다. 그러므로 onPause()를 사용하여 애플리케이션 또는 사용자 데이터를 저장하거나, 네트워크 호출을 하거나, 데이터베이스 트랜잭션을 실행해서는 안 됩니다. 이러한 작업은 메서드 실행이 끝나기 전에 완료되지 못할 수도 있습니다. 그 대신, 부하가 큰 종료 작업은 onStop() 상태일 때 실행해야 합니다. onStop() 상태일 때 실행하기에 적절한 작업에 대한 자세한 내용은 onStop()을 참조하세요. 데이터 저장에 대한 자세한 내용은 Activity 상태 저장 및 복원을 참조하세요.

onPause() 메서드의 실행이 완료되더라도 Activity가 일시정지됨 상태로 남아 있을 수 있습니다. 오히려 Activity는 다시 시작되거나 사용자에게 완전히 보이지 않게 될 때까지 이 상태에 머무릅니다. Activity가 다시 시작되면 시스템은 다시 한번 onResume() 콜백을 호출합니다. Activity가 일시정지됨 상태에서 재개됨 상태로 돌아오면 시스템은 Activity 인스턴스를 메모리에 남겨두고, 시스템이 onResume()을 호출할 때 인스턴스를 다시 호출합니다. 이 시나리오에서는 최상위 상태가 재개됨 상태인 콜백 메서드 중에 생성된 구성요소는 다시 초기화할 필요가 없습니다. Activity가 완전히 보이지 않게 되면 시스템은 onStop()을 호출합니다. 다음 섹션에서는 onStop() 콜백에 대해 설명합니다.

onStop()

Activity가 사용자에게 더 이상 표시되지 않으면 중단됨 상태에 들어가고, 시스템은 onStop() 콜백을 호출합니다. 이는 예를 들어 새로 시작된 Activity가 화면 전체를 차지할 경우에 적용됩니다. 시스템은 Activity의 실행이 완료되어 종료될 시점에 onStop()을 호출할 수도 있습니다.

Activity가 중단됨 상태로 전환하면 이 Activity의 수명 주기와 연결된 모든 수명 주기 인식 구성요소는 ON_STOP 이벤트를 수신합니다. 여기에서 수명 주기 구성요소는 구성요소가 화면에 보이지 않을 때 실행할 필요가 없는 기능을 모두 정지할 수 있습니다.

onStop() 메서드에서는 앱이 사용자에게 보이지 않는 동안 앱은 필요하지 않은 리소스를 해제하거나 조정해야 합니다. 예를 들어 앱은 애니메이션을 일시정지하거나, 세밀한 위치 업데이트에서 대략적인 위치 업데이트로 전환할 수 있습니다. onPause() 대신 onStop()을 사용하면 사용자가 멀티 윈도우 모드에서 Activity를 보고 있더라도 UI 관련 작업이 계속 진행됩니다.

또한 onStop()을 사용하여 CPU를 비교적 많이 소모하는 종료 작업을 실행해야 합니다. 예를 들어 정보를 데이터베이스에 저장할 적절한 시기를 찾지 못했다면 onStop() 상태일 때 저장할 수 있습니다. 다음 예시는 초안 내용을 영구 저장소에 저장하는 onStop()을 구현한 것입니다.

Kotlin

override fun onStop() {
    // call the superclass method first
    super.onStop()

    // save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    val values = ContentValues().apply {
        put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText())
        put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle())
    }

    // do this update in background on an AsyncQueryHandler or equivalent
    asyncQueryHandler.startUpdate(
            token,     // int token to correlate calls
            null,      // cookie, not used here
            uri,       // The URI for the note to update.
            values,    // The map of column names and new values to apply to them.
            null,      // No SELECT criteria are used.
            null       // No WHERE columns are used.
    )
}

Java

@Override
protected void onStop() {
    // call the superclass method first
    super.onStop();

    // save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // do this update in background on an AsyncQueryHandler or equivalent
    asyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            uri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

참고로 위의 코드 샘플은 SQLite를 직접 사용합니다. 여러분은 대신 SQLite에 대한 추상화 레이어를 제공하는 영구 라이브러리인 Room을 사용해야 합니다. Room을 사용하는 장점과 앱에서 Room을 구현하는 방법에 대한 자세한 내용은 Room 영구 라이브러리 가이드를 참조하세요.

Activity가 중단됨 상태에 들어가면 Activity 객체는 메모리 안에 머무르게 됩니다. 이 객체가 모든 상태 및 멤버 정보를 관리하지만 창 관리자와 연결되어 있지는 않습니다. Activity가 다시 시작되면 이 정보를 다시 호출합니다. 최상위 상태가 재개됨 상태인 콜백 메서드 중에 생성된 구성요소는 다시 초기화할 필요가 없습니다. 또한 시스템은 레이아웃에 있는 각 View 객체의 현재 상태도 기록합니다. 따라서 사용자가 EditText 위젯에 텍스트를 입력하면 해당 내용이 저장되기 때문에 이를 저장 및 복원할 필요가 없습니다.

참고: Activity가 중단되면 시스템은 해당 Activity가 포함된 프로세스를 소멸시킬 수 있습니다(시스템이 메모리를 복구해야 하는 경우). Activity가 중단된 동안 시스템이 프로세스를 소멸시키더라도 Bundle(키-값 쌍의 blob)에 있는 View 객체(예: EditText 위젯의 텍스트) 상태가 그대로 유지되고, 사용자가 이 Activity로 돌아오면 이를 복원합니다. 사용자가 돌아올 때의 Activity 복원에 대한 자세한 내용은 Activity 상태 저장 및 복원을 참조하세요.

Activity는 정지됨 상태에서 다시 시작되어 사용자와 상호작용하거나, 실행을 종료하고 사라집니다. Activity가 다시 시작되면 시스템은 onRestart()를 호출합니다. Activity가 실행을 종료하면 시스템은 onDestroy()를 호출합니다. 다음 섹션에서는 onDestroy() 콜백에 대해 설명합니다.

onDestroy()

onDestroy()는 Activity가 소멸되기 전에 호출됩니다. 시스템은 다음 중 하나에 해당할 때 이 콜백을 호출합니다.

  1. (사용자가 Activity를 완전히 닫거나 Activity에서 finish()가 호출되어) Activity가 종료되는 경우
  2. 구성 변경(예: 기기 회전 또는 멀티 윈도우 모드)으로 인해 시스템이 일시적으로 Activity를 소멸시키는 경우

Activity가 소멸됨 상태로 전환하면 이 Activity의 수명 주기와 연결된 모든 수명 주기 인식 구성요소는 ON_DESTROY 이벤트를 수신합니다. 여기서 수명 주기 구성요소는 Activity가 소멸되기 전에 필요한 것을 정리할 수 있습니다.

Activity에 소멸되는 이유를 결정하는 로직을 입력하는 대신 ViewModel 객체를 사용하여 Activity의 관련 뷰 데이터를 포함해야 합니다. Activity가 구성 변경으로 인해 다시 생성될 경우 ViewModel은 그대로 보존되어 다음 Activity 인스턴스에 전달되므로 추가 작업이 필요하지 않습니다. Activity가 다시 생성되지 않을 경우 ViewModel은 onCleared() 메서드를 호출하여 Activity가 소멸되기 전에 모든 데이터를 정리해야 합니다.

이와 같은 두 가지 시나리오는 isFinishing() 메서드로 구분할 수 있습니다.

Activity가 종료되는 경우 onDestroy()는 Activity가 수신하는 마지막 수명 주기 콜백이 됩니다. 구성 변경으로 인해 onDestroy()가 호출되는 경우 시스템이 즉시 새 Activity 인스턴스를 생성한 다음, 새로운 구성에서 그 새로운 인스턴스에 대해 onCreate()를 호출합니다.

onDestroy() 콜백은 이전의 콜백에서 아직 해제되지 않은 모든 리소스(예: onStop())를 해제해야 합니다.

Activity 상태 및 메모리에서 제거

시스템은 RAM에 여유 공간이 필요할 때 프로세스를 종료합니다. 시스템이 특정 프로세스를 종료할 가능성은 그 시점의 프로세스 상태에 따라 달라집니다. 그리고 프로세스 상태는 프로세스에서 실행되는 Activity 상태에 따라 달라집니다. 표 1은 프로세스 상태, Activity 상태, 시스템이 프로세스를 종료할 가능성 사이의 상관관계를 나타냅니다.

종료될 가능성 프로세스 상태 Activity 상태
최소 포그라운드(포커스가 있거나 포커스를 가져올 예정) 생성됨
시작됨
재개됨
높음 백그라운드(포커스 상실) 일시정지됨
최대 백그라운드(보이지 않음) 정지됨
비어 있음 소멸됨

표 1. 프로세스 수명 주기와 Activity 상태 간의 관계

시스템은 메모리 공간을 확보하기 위해 절대 Activity를 직접 종료하지 않습니다. 그 대신, Activity를 실행하는 프로세스를 종료하여 Activity뿐만 아니라 프로세스에서 실행되는 다른 모든 작업을 함께 소멸시킵니다. 시스템이 시작한 프로세스가 종료될 때 Activity의 UI 상태를 보존하고 복원하는 방법에 대해 알아보려면 Activity 상태 저장 및 복원을 참조하세요.

사용자도 Settings의 Application Manager를 사용하여 해당 앱을 종료하는 방법으로 프로세스를 종료할 수 있습니다.

프로세스 전반에 대한 자세한 내용은 프로세스 및 스레드를 참조하세요. 프로세스의 수명 주기가 그 안의 Activity 상태와 어떻게 연결되는지 자세히 알아보려면 해당 페이지의 프로세스 수명 주기 섹션을 참조하세요.

임시 UI 상태 저장 및 복원

사용자는 구성이 변경되더라도(예: 회전 또는 멀티 윈도우 모드로 전환) Activity의 UI 상태가 그대로 유지되기를 기대합니다. 그러나 시스템은 이런 구성 변경이 발생하면 기본적으로 Activity를 소멸시켜 Activity 인스턴스에 저장된 모든 UI 상태를 제거합니다. 마찬가지로 사용자는 일시적으로 앱에서 다른 앱으로 전환했다가 다시 앱으로 돌아왔을 때도 UI 상태가 그대로 유지되기를 기대합니다. 그러나 사용자가 나가서 Activity가 중단되면 시스템은 애플리케이션의 프로세스를 소멸시킬 수 있습니다.

시스템 제약으로 인해 Activity가 소멸되면 ViewModel, onSaveInstanceState() 및/또는 로컬 저장소를 결합하여 사용자의 임시 UI 상태를 보존해야 합니다. 사용자의 기대와 시스템의 동작을 자세히 비교하고, 시스템이 시작한 Activity와 프로세스에서 복잡한 UI 상태 데이터를 가장 잘 보존하는 방법을 알아보려면 UI 상태 저장을 참조하세요.

이 섹션에서는 인스턴스 상태가 무엇인지, Activity 자체에 대한 콜백인 onSaveInstance() 메서드를 어떻게 구현하는지 간략히 설명합니다. 원시 데이터 유형이거나 간단한 객체(예: 문자열)와 같이 UI 데이터가 간단하고 가벼울 경우, onSaveInstanceState()만으로도 모든 구성 변경 및 시스템이 시작한 프로세스가 종료된 상황에서 UI 상태를 보존할 수 있습니다. 그러나 onSaveInstanceState()가 직렬화/역직렬화 비용을 발생시키기 때문에 대부분의 경우에는 ViewModel과 onSaveInstanceState()를 모두 사용해야 합니다(UI 상태 저장 참조).

인스턴스 상태

정상적인 앱 동작으로 인해 Activity가 소멸되는 시나리오는 몇 가지가 있습니다. 예를 들어 사용자가 Back 버튼을 누르거나 Activity가 finish() 메서드를 호출하여 자체적인 소멸 신호를 보내는 경우입니다. 사용자가 Back 버튼을 누르거나 Activity가 자체적으로 종료되어 Activity가 소멸되는 경우 해당 Activity 인스턴스에 대한 시스템과 사용자의 콘셉트가 모두 영구적으로 사라집니다. 이 시나리오에서 사용자의 기대가 시스템의 동작과 일치하므로 추가적인 작업이 필요하지 않습니다.

그러나 시스템이 시스템 제약(예: 구성 변경 또는 메모리 부족)으로 인해 Activity를 소멸시킬 경우, 실제 Activity 인스턴스는 사라지더라도 시스템에 존재했다는 정보는 남아 있습니다. 사용자가 Activity로 다시 돌아가려고 시도하면 시스템은 소멸 당시 Activity의 상태를 설명하는 저장된 데이터 세트를 사용하여 해당 Activity의 새로운 인스턴스를 생성합니다.

시스템이 이전 상태를 복원하기 위해 사용하는 저장된 데이터를 인스턴스 상태라고 하며, 이는 Bundle 객체에 저장된 키-값 쌍의 컬렉션입니다. 기본적으로 시스템은 Bundle 인스턴스 상태를 사용하여 Activity 레이아웃의 각 View 객체에 대한 정보를 저장합니다(예: EditText 위젯에 입력된 텍스트 값). 따라서 Activity 인스턴스가 소멸되고 재생성된 경우, 레이아웃의 상태는 별도의 코드 요청 없이 이전 상태로 복원됩니다. 하지만 Activity에서 사용자 진행 상태를 추적하는 멤버 변수처럼 Activity에 복원하고자 하는 상태 정보가 더 많이 있는 경우도 있습니다.

참고: Android 시스템이 Activity에서 뷰의 상태를 복원하기 위해서는 android:id 특성으로 제공되는 고유 ID가 각 뷰마다 있어야 합니다.

Bundle 객체는 메인 스레드에서 직렬화되어야 하고 시스템 프로세스 메모리를 사용하므로 소량의 데이터를 보존하는 데만 적합합니다. 극소량 이상의 데이터를 보존하려면 영구 로컬 저장소, onSaveInstanceState() 메서드, ViewModel 클래스로 데이터를 보존하는 복합적인 방법을 사용해야 합니다(UI 상태 저장 참조).

onSaveInstanceState()를 사용하여 간단하고 가벼운 UI 상태 저장

Activity가 정지되기 시작하면 인스턴스 상태 번들에 상태 정보를 저장할 수 있도록 시스템이 onSaveInstanceState() 메서드를 호출합니다. 이 메서드의 기본 구현은 EditText 위젯 내 텍스트 또는 ListView 위젯의 스크롤 위치와 같은 Activity의 뷰 계층 구조에 대한 임시 정보를 저장합니다.

Activity의 추가적인 인스턴스 상태 정보를 저장하려면 onSaveInstanceState()를 재정의하고, Activity가 예상치 못하게 소멸될 경우 저장되는 Bundle 객체에 키-값 쌍을 추가해야 합니다. onSaveInstanceState()를 재정의할 경우 기본 구현에서 뷰 계층 구조의 상태를 저장하고자 한다면 상위 클래스 구현을 호출해야 합니다. 예를 들면 다음과 같습니다.

Kotlin

override fun onSaveInstanceState(outState: Bundle?) {
    // Save the user's current game state
    outState?.run {
        putInt(STATE_SCORE, currentScore)
        putInt(STATE_LEVEL, currentLevel)
    }

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(outState)
}

companion object {
    val STATE_SCORE = "playerScore"
    val STATE_LEVEL = "playerLevel"
}

Java

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
// ...


@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, currentScore);
    savedInstanceState.putInt(STATE_LEVEL, currentLevel);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

참고: 사용자가 Activity를 명시적으로 닫는 경우 또는 finish()가 호출된 경우에는 onSaveInstanceState()가 호출되지 않습니다.

영구 데이터(예: 사용자 기본 설정 또는 데이터베이스 데이터)를 저장하려면 Activity가 포그라운드에 있을 때 적절한 기회를 잡아야 합니다. 그럴 기회가 없으면 onStop() 메서드가 호출되었을 때 해당 데이터를 저장해야 합니다.

저장된 인스턴스 상태를 사용하여 Activity UI 상태 복원

Activity가 이전에 소멸된 후 재생성되면, 시스템이 Activity에 전달하는 Bundle로부터 저장된 인스턴스 상태를 복구할 수 있습니다. onCreate()onRestoreInstanceState() 콜백 메서드 둘 다 인스턴스 상태 정보를 포함하는 동일한 Bundle을 수신합니다.

onCreate() 메서드는 시스템이 Activity의 새 인스턴스를 생성하든, 이전 인스턴스를 재생성하든 상관없이 호출되므로 읽기를 시도하기 전에 Bundle 상태가 null인지 반드시 확인해야 합니다. null일 경우, 시스템은 이전에 소멸된 Activity의 인스턴스를 복원하지 않고 새 인스턴스를 생성합니다.

예를 들어 다음 코드 스니펫은 onCreate()에서 일부 상태 데이터를 복원하는 방법을 보여줍니다.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState) // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        with(savedInstanceState) {
            // Restore value of members from saved state
            currentScore = getInt(STATE_SCORE)
            currentLevel = getInt(STATE_LEVEL)
        }
    } else {
        // Probably initialize members with default values for a new instance
    }
    // ...
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first

    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        // Restore value of members from saved state
        currentScore = savedInstanceState.getInt(STATE_SCORE);
        currentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance
    }
    // ...
}

onCreate() 중에 상태를 복원하는 대신, onRestoreInstanceState()를 구현하는 방법을 선택할 수 있습니다. 이는 시스템이 onStart() 메서드 다음에 호출합니다. 시스템은 복원할 저장 상태가 있을 경우에만 onRestoreInstanceState()를 호출합니다. 따라서 Bundle이 null인지 확인할 필요가 없습니다.

Kotlin

override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState)

    // Restore state members from saved instance
    savedInstanceState?.run {
        currentScore = getInt(STATE_SCORE)
        currentLevel = getInt(STATE_LEVEL)
    }
}

Java

public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from saved instance
    currentScore = savedInstanceState.getInt(STATE_SCORE);
    currentLevel = savedInstanceState.getInt(STATE_LEVEL);
}

주의: 항상 onRestoreInstanceState()의 상위 클래스 구현을 호출하여 기본 구현에서 뷰 계층 구조의 상태를 복원할 수 있도록 합니다.

Activity 간 이동

앱은 수명 주기 동안 Activity에 여러 번 들어가고 나올 가능성이 큽니다. 예를 들어 사용자가 기기의 Back 버튼을 누르거나 Activity가 다른 Activity를 시작할 수 있습니다. 이 섹션에서는 성공적인 Activity 전환을 구현하기 위해 알아야 할 주제를 다룹니다. 여기에는 다른 Activity에서 Activity 시작하기, Activity 상태 저장하기, Activity 상태 복원하기 등이 포함되어 있습니다.

다른 Activity에서 Activity 시작하기

Activity는 특정 시점에서 다른 Activity를 시작해야 하는 경우가 많습니다. 예를 들어 앱이 현재 화면에서 새로운 화면으로 옮겨갈 때가 이에 해당합니다.

Activity가 새로 시작하려는 Activity에서 결과를 받기를 원하는지 여부에 따라 startActivity()startActivityForResult() 메서드 중 하나를 사용하여 새 Activity를 시작합니다. 두 가지 경우 모두 Intent 객체를 전달합니다.

Intent 객체는 시작하고자 하는 Activity를 정확히 나타내거나, 수행하고자 하는 작업의 유형을 설명합니다(시스템이 적절한 Activity를 선택하며, 이는 다른 애플리케이션에서 가져온 것일 수도 있습니다). Intent 객체는 시작된 Activity에서 사용할 소량의 데이터를 포함할 수 있습니다. Intent 클래스에 대한 자세한 내용은 인텐트 및 인텐트 필터를 참조하세요.

startActivity()

새로 시작된 Activity가 결과를 반환할 필요가 없을 경우 현재 Activity가 startActivity() 메서드를 호출하면 이를 시작할 수 있습니다.

본인의 애플리케이션 내에서 작업하는 경우에는 알려진 Activity를 시작하기만 하면 되는 경우가 많습니다. 예를 들어 다음 코드 스니펫은 SignInActivity라는 Activity를 시작하는 방법을 보여줍니다.

Kotlin

val intent = Intent(this, SignInActivity::class.java)
startActivity(intent)

Java

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

또한 애플리케이션에서 다른 몇 가지 작업을 수행하고자 할 수도 있습니다. 예를 들어 이메일 보내기, SMS 보내기 또는 상태 업데이트 등의 작업을 Activity의 데이터를 사용하여 수행할 수 있습니다. 이 경우, 본인의 애플리케이션에 그러한 동작을 수행할 자체 Activity가 없을 수도 있습니다. 따라서 기기에 있는 다른 애플리케이션이 제공하는 Activity를 대신 활용하여 동작을 수행하게 할 수 있습니다. 바로 이 부분에서 인텐트의 진가가 발휘됩니다. 수행하고자 하는 작업을 설명하는 인텐트를 생성하면 시스템이 적절한 Activity를 다른 애플리케이션에서 시작합니다. 해당 인텐트를 처리할 수 있는 Activity가 여러 개 있는 경우, 사용자는 어느 것을 사용할지 선택할 수 있습니다. 예를 들어 사용자가 이메일 메시지를 보낼 수 있게 하려면, 다음과 같은 인텐트를 생성하면 됩니다.

Kotlin

val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)

Java

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

인텐트에 추가된 EXTRA_EMAIL은 이메일이 전송되어야 할 이메일 주소의 문자열 배열입니다. 이메일 애플리케이션이 이 인텐트에 응답하면, 애플리케이션은 엑스트라에서 제공된 문자열 배열을 읽은 다음 이를 이메일 작성 양식의 "수신" 필드에 배치합니다. 이 상황에서 이메일 애플리케이션의 Activity가 시작되고 사용자가 작업을 끝내면 여러분의 Activity가 재개됩니다.

startActivityForResult()

Activity가 종료될 때 결과를 반환받고자 할 수도 있습니다. 예를 들어 사용자가 연락처 목록에서 어떤 사람을 선택할 수 있도록 하는 Activity를 시작할 수 있습니다. 이 Activity가 종료되면 선택한 사람을 반환합니다. 이렇게 하려면 startActivityForResult(Intent, int) 메서드를 호출해야 합니다. 이 메서드에서는 정수 매개변수가 호출을 식별합니다. 이 식별자는 동일한 Activity에서 startActivityForResult(Intent, int)에 대한 여러 개의 호출을 명확히 구분하는 역할을 합니다. 이는 글로벌 식별자가 아니며, 다른 앱이나 Activity와 충돌할 위험이 없습니다. 결과는 onActivityResult(int, int, Intent) 메서드를 통해 반환됩니다.

하위 Activity가 존재할 경우 setResult(int)를 호출하여 상위 Activity로 데이터를 반환할 수 있습니다. 하위 Activity는 언제나 결과 코드를 제공합니다. 이는 표준 결과인 RESULT_CANCELED, RESULT_OK가 되거나 RESULT_FIRST_USER로 시작하는 임의의 맞춤 값이 될 수도 있습니다. 또한 하위 Activity는 원하는 모든 추가 데이터가 포함된 Intent 객체를 반환할 수도 있습니다. 상위 Activity는 원래 제공했던 정수 식별자와 함께 onActivityResult(int, int, Intent) 메서드를 사용하여 정보를 수신합니다.

어떤 이유(예: 비정상 종료)로든 하위 Activity가 실행되지 않으면 상위 Activity는 RESULT_CANCELED 코드가 포함된 결과를 수신합니다.

Kotlin

class MyActivity : Activity() {
    // ...

    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
            // When the user center presses, let them pick a contact.
            startActivityForResult(
                    Intent(Intent.ACTION_PICK,Uri.parse("content://contacts")),
                    PICK_CONTACT_REQUEST)
            return true
        }
        return false
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        when (requestCode) {
            PICK_CONTACT_REQUEST ->
                if (resultCode == RESULT_OK) {
                    startActivity(Intent(Intent.ACTION_VIEW, intent?.data))
                }
        }
    }

    companion object {
        internal val PICK_CONTACT_REQUEST = 0
    }
}

Java

public class MyActivity extends Activity {
     // ...

     static final int PICK_CONTACT_REQUEST = 0;

     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
             // When the user center presses, let them pick a contact.
             startActivityForResult(
                 new Intent(Intent.ACTION_PICK,
                 new Uri("content://contacts")),
                 PICK_CONTACT_REQUEST);
            return true;
         }
         return false;
     }

     protected void onActivityResult(int requestCode, int resultCode,
             Intent data) {
         if (requestCode == PICK_CONTACT_REQUEST) {
             if (resultCode == RESULT_OK) {
                 // A contact was picked.  Here we will just display it
                 // to the user.
                 startActivity(new Intent(Intent.ACTION_VIEW, data));
             }
         }
     }
 }

Activity 조정

한 Activity가 다른 Activity를 시작하면, 둘 모두 수명 주기가 전환됩니다. 다른 Activity가 생성되는 동안 첫 번째 Activity는 작동을 중단하고 일시정지됨 또는 정지됨 상태로 들어갑니다. 이와 같은 Activity가 디스크 또는 다른 곳에 저장된 데이터를 공유하는 경우, 첫 번째 Activity는 두 번째 Activity가 생성되기 전에 완전히 중단되지 않는다는 점을 이해하는 것이 중요합니다. 그렇다기보다는 두 번째 Activity의 시작 과정이 첫 번째 Activity 중단 과정과 겹쳐 일어납니다.

수명 주기 콜백은 분명히 정의된 순서가 있으며 특히 두 개의 Activity가 같은 프로세스(앱) 안에 있으면서 하나가 다른 하나를 시작하는 경우 순서가 더욱 확실합니다. Activity A가 Activity B를 시작할 때 발생하는 작업 순서는 아래와 같습니다.

  1. Activity A의 onPause() 메서드가 실행됩니다.
  2. Activity B의 onCreate(), onStart()onResume() 메서드가 순차적으로 실행됩니다. (이제 사용자 포커스는 Activity B에 있습니다.)
  3. 그런 다음, Activity A가 더 이상 화면에 표시되지 않는 경우 이 Activity의 onStop() 메서드가 실행됩니다.

이처럼 수명 주기 콜백의 순서를 예측할 수 있기 때문에 한 Activity에서 다른 Activity로 전환되는 정보를 관리할 수 있습니다.