활동 수명 주기

사용자가 앱을 탐색하고, 앱에서 나가고, 앱으로 다시 돌아가면, 앱의 Activity 인스턴스는 수명 주기 안에서 서로 다른 상태를 통해 전환됩니다. Activity 클래스는 상태가 변경되거나 시스템이 활동을 생성, 중지 또는 재개하거나 활동이 있는 프로세스를 삭제할 때 이를 활동에 알리는 여러 콜백을 제공합니다.

사용자가 활동을 벗어났다가 다시 돌아왔을 때 활동이 작동하는 방식을 수명 주기 콜백 메서드에서 선언할 수 있습니다. 예를 들어 스트리밍 동영상 플레이어를 빌드하는 경우 사용자가 다른 앱으로 전환할 때 동영상을 일시중지하고 네트워크 연결을 종료할 수 있습니다. 사용자가 돌아오면 네트워크에 다시 연결하고 사용자가 같은 지점에서 동영상을 재개하도록 할 수 있습니다.

각 콜백을 사용하면 지정된 상태 변경에 적합한 특정 작업을 실행할 수 있습니다. 적시에 알맞은 작업을 하고 적절하게 전환을 처리하면 앱이 더욱 안정적으로 기능할 수 있습니다. 예를 들어 수명 주기 콜백을 잘 구현하면 앱에서 다음을 피하는 데 도움이 될 수 있습니다.

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

이 문서에서는 활동 수명 주기에 관해 자세히 설명합니다. 먼저 수명 주기 패러다임에 관해 설명하고 그런 다음, 각 콜백, 즉 콜백이 실행되는 동안 내부적으로 어떤 일이 발생하는지, 그리고 콜백 중에 무엇을 구현해야 하는지 설명합니다.

그런 다음 시스템에 의해 종료되는 프로세스의 취약점과 활동 상태 간의 관계에 관해 간략히 소개합니다. 마지막으로 활동 상태 간 전환과 관련된 여러 주제를 설명합니다.

권장사항 안내를 비롯하여 수명 주기 처리에 관한 자세한 내용은 수명 주기 인식 구성요소로 수명 주기 처리UI 상태 저장을 참고하세요. 아키텍처 구성요소와 활동을 함께 사용하여 강력한 프로덕션 품질의 앱을 설계하는 방법을 알아보려면 앱 아키텍처 가이드를 참고하세요.

활동 수명 주기 개념

활동 수명 주기의 단계 간 전환을 탐색하기 위해 Activity 클래스는 6가지 콜백으로 구성된 핵심 집합인 onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy()를 제공합니다. 활동이 새 상태로 전환될 때 시스템은 이러한 각 콜백을 호출합니다.

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

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

사용자가 활동을 벗어나기 시작하면 시스템은 활동을 해체할 메서드를 호출합니다. 경우에 따라 활동은 사용자가 다른 앱으로 전환할 때와 같이 부분적으로만 해체되고 여전히 메모리에 남아 있습니다. 이러한 경우 활동은 여전히 포그라운드로 돌아올 수 있습니다.

사용자가 활동으로 돌아오면 활동은 사용자가 중지한 지점에서 다시 시작됩니다. 몇 가지 예외를 제외하고 앱은 백그라운드에서 실행될 때 활동을 시작할 수 없습니다.

시스템이 특정 프로세스와 프로세스 내의 활동을 종료할 가능성은 그 시점의 활동 상태에 따라 다릅니다. 상태와 제거 취약성의 관계에 관한 자세한 내용은 활동 상태 및 메모리에서 제거 섹션을 참고하세요.

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

수명 주기 콜백

이 섹션에서는 활동 수명 주기 동안 사용되는 콜백 메서드에 관한 개념 및 구현 정보를 제공합니다.

일부 작업은 활동 수명 주기 메서드에 속합니다. 그러나 활동 수명 주기 메서드가 아닌 구성요소에 종속된 구성요소의 작업을 구현하는 코드를 배치합니다. 이렇게 하려면 종속 구성요소가 수명 주기를 인식하도록 해야 합니다. 종속 구성요소가 수명 주기를 인식하도록 하는 방법을 알아보려면 수명 주기 인식 구성요소로 수명 주기 처리를 참고하세요.

onCreate()

이 콜백은 시스템이 먼저 활동을 생성할 때 실행되는 것으로, 필수적으로 구현해야 합니다. 활동이 생성되면 생성됨 상태가 됩니다. onCreate() 메서드에서 활동의 전체 수명 주기 동안 한 번만 발생하는 기본 애플리케이션 시작 로직을 실행합니다.

예를 들어 onCreate() 구현은 데이터를 목록에 바인딩하고 활동을 ViewModel에 연결하며 일부 클래스 범위 변수를 인스턴스화할 수 있습니다. 이 메서드는 활동의 이전에 저장된 상태를 포함하는 Bundle 객체인 savedInstanceState 매개변수를 수신합니다. 이전에 활동이 없었던 경우 Bundle 객체의 값은 null입니다.

활동의 수명 주기와 연결된 수명 주기 인식 구성요소가 있다면 이 구성요소는 ON_CREATE 이벤트를 수신합니다. 수명 주기 인식 구성요소가 생성된 상태에 필요한 모든 설정 코드를 실행할 수 있도록 @OnLifecycleEvent 주석이 달린 메서드가 호출됩니다.

다음 onCreate() 메서드 예는 사용자 인터페이스 선언(XML 레이아웃 파일에 정의됨), 멤버 변수 정의, 일부 UI 구성 등 활동의 기본 설정을 보여줍니다. 이 예에서 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 superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState)

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

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

    // Initialize member TextView so it is available later.
    textView = findViewById(R.id.text_view)
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored 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 might 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 superclass onCreate to complete the creation of
    // the activity, like the view hierarchy.
    super.onCreate(savedInstanceState);

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

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

    // Initialize member TextView so it is available later.
    textView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). Some state is restored in onCreate(). Other state can optionally
// be restored 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 might 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()에 전달하는 대신 활동 코드에서 새 View 객체를 만들고 ViewGroup에 새 View 객체를 삽입하여 뷰 계층 구조를 빌드할 수 있습니다. 그런 다음 루트 ViewGroupsetContentView()에 전달하여 이 레이아웃을 사용합니다. 사용자 인터페이스 만들기에 관한 자세한 내용은 사용자 인터페이스 문서를 참고하세요.

활동은 '생성됨' 상태로 유지되지 않습니다. onCreate() 메서드가 실행을 완료하면 Started 상태가 되고 시스템은 onStart()onResume() 메서드를 연속으로 호출합니다.

onStart()

활동이 시작됨 상태로 전환되면 시스템은 onStart()를 호출합니다. 이 호출을 통해 앱에서 활동이 포그라운드로 전환되어 상호작용할 수 있도록 준비할 때 사용자에게 활동이 표시됩니다. 예를 들어 이 메서드에서 UI를 유지하는 코드가 초기화됩니다.

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

onStart() 메서드는 빠르게 완료되며, 생성됨 상태와 마찬가지로 활동은 시작됨 상태로 유지되지 않습니다. 이 콜백이 완료되면 활동이 재개됨 상태로 전환되고 시스템에서 onResume() 메서드를 호출합니다.

onResume()

활동이 재개됨 상태로 전환되면 포그라운드로 이동하고 시스템에서 onResume() 콜백을 호출합니다. 이 상태에 들어갔을 때 앱이 사용자와 상호작용합니다. 앱은 전화가 걸려오거나 사용자가 다른 활동으로 이동하거나 기기 화면이 꺼지는 등 어떤 일이 발생하여 앱에서 포커스를 잃을 때까지 이 상태를 유지합니다.

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

방해가 되는 이벤트가 발생하면 활동은 일시중지됨 상태가 되고 시스템은 onPause() 콜백을 호출합니다.

활동이 일시중지됨 상태에서 재개됨 상태로 돌아오면 시스템은 onResume() 메서드를 다시 한번 호출합니다. 따라서 onResume()를 구현하여 onPause() 중에 해제하는 구성요소를 초기화하고 활동이 재개됨 상태로 전환될 때마다 발생해야 하는 다른 초기화를 실행합니다.

다음은 구성요소가 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();
        }
    }
    ...
}

위 코드는 LifecycleObserverON_RESUME 이벤트를 수신하면 카메라를 초기화합니다. 그러나 멀티 윈도우 모드에서는 활동이 일시중지됨 상태에 있더라도 완전히 표시될 수 있습니다. 예를 들어 앱이 멀티 윈도우 모드에 있을 때 사용자가 활동이 포함되지 않은 창을 탭하면 활동이 일시중지됨 상태로 전환됩니다.

앱이 재개됨 (포그라운드에 표시되고 활성화된 상태)인 경우에만 카메라를 활성화하려면 앞에서 설명한 ON_RESUME 이벤트 후에 카메라를 초기화합니다. 활동이 일시중지됨 상태이지만 표시되는 동안(예: 멀티 윈도우 모드) 카메라를 활성 상태로 유지하려면 ON_START 이벤트 후에 카메라를 초기화하세요.

그러나 활동이 일시중지됨 상태일 때 카메라를 활성화하면 멀티 윈도우 모드에서 재개된 다른 앱이 카메라에 액세스하지 못할 수도 있습니다. 활동이 일시중지됨 상태일 때 카메라를 활성 상태로 유지해야 할 때도 있지만, 그렇게 하면 실제로는 전반적인 사용자 환경이 저하될 수 있습니다.

따라서 수명 주기 중 멀티 윈도우 모드의 컨텍스트에서 공유 시스템 리소스를 제어하는 것이 가장 적절한 위치를 신중하게 생각해야 합니다. 멀티 윈도우 모드 지원에 관한 자세한 내용은 멀티 윈도우 지원을 참고하세요.

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

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

onPause()

시스템은 사용자가 활동을 떠난다는 첫 번째 신호로 이 메서드를 호출하지만, 이것이 항상 활동이 소멸된다는 의미는 아닙니다. 이는 활동이 더 이상 포그라운드에 있지 않음을 나타내지만 사용자가 멀티 윈도우 모드에 있는 경우에는 계속 표시됩니다. 활동이 이 상태로 전환되는 데에는 몇 가지 이유가 있습니다.

  • onResume() 콜백에 관한 섹션에 설명된 대로 앱 실행을 중단하는 이벤트는 현재 활동을 일시중지합니다. 이것이 가장 일반적인 사례입니다.
  • 멀티 윈도우 모드에서는 언제든 하나의 앱만 포커스를 가지며 시스템이 다른 모든 앱을 일시중지합니다.
  • 대화상자와 같은 새로운 반투명 활동이 열리면 포함된 활동이 일시중지됩니다. 활동이 부분적으로 보이지만 포커스 상태가 아닌 한 일시중지 상태로 유지됩니다.

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

onPause() 메서드를 사용하여 Activity가 일시중지됨 상태에 있고 곧 재개될 것으로 예상되는 작업을 계속할 수 없거나 적절하게 진행될 수 있는 작업을 일시중지하거나 조정합니다.

또한 onPause() 메서드를 사용하여 시스템 리소스, 센서 핸들 (예: GPS) 또는 활동이 일시중지되어 사용자에게 필요하지 않은 동안 배터리 수명에 영향을 미치는 모든 리소스를 해제할 수도 있습니다.

그러나 onResume()에 관한 섹션에서 언급했듯이 앱이 멀티 윈도우 모드에 있다면 일시중지된 활동이 여전히 완전히 표시될 수 있습니다. 멀티 윈도우 모드를 더 효과적으로 지원하기 위해 onPause() 대신 onStop()을 사용하여 UI 관련 리소스와 작업을 완전히 해제하거나 조정해 보세요.

ON_PAUSE 이벤트에 반응하는 다음 LifecycleObserver 예는 이전 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() 중에 실행하기에 적합한 작업에 관한 자세한 내용은 다음 섹션을 참고하세요. 데이터 저장에 관한 자세한 내용은 상태 저장 및 복원 섹션을 참고하세요.

onPause() 메서드의 실행이 완료되더라도 활동이 일시중지됨 상태로 남아 있을 수 있습니다. 오히려 활동은 활동이 재개되거나 사용자에게 완전히 표시되지 않을 때까지 이 상태에 머무릅니다. 활동이 다시 시작되면 시스템은 다시 한번 onResume() 콜백을 호출합니다.

활동이 일시중지됨 상태에서 재개됨 상태로 돌아오면 시스템은 Activity 인스턴스를 메모리에 유지하여 시스템이 onResume()를 호출할 때 인스턴스를 다시 호출합니다. 이 시나리오에서는 재개됨 상태가 되는 콜백 메서드 중에 생성된 구성요소는 다시 초기화할 필요가 없습니다. 활동이 완전히 보이지 않게 되면 시스템은 onStop()를 호출합니다.

onStop()

활동이 사용자에게 더 이상 표시되지 않으면 중지됨 상태로 전환되고 시스템에서 onStop() 콜백을 호출합니다. 이는 새로 시작된 활동이 전체 화면을 차지할 때 발생할 수 있습니다. 또한 시스템은 활동 실행이 완료되고 종료되려고 할 때 onStop()를 호출합니다.

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

onStop() 메서드에서 앱이 사용자에게 표시되지 않는 동안 필요하지 않은 리소스를 해제하거나 조정합니다. 예를 들어 앱은 애니메이션을 일시중지하거나, 세밀한 위치 업데이트에서 대략적인 위치 업데이트로 전환할 수 있습니다. onPause() 대신 onStop()를 사용하면 사용자가 멀티 윈도우 모드에서 활동을 보고 있더라도 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 객체는 메모리에 상주합니다. 즉, 모든 상태 및 멤버 정보를 유지하지만 창 관리자에 연결되지는 않습니다. 활동이 재개되면 이 정보를 리콜합니다.

재개됨 상태로 이어지는 콜백 메서드 중에 생성된 구성요소는 다시 초기화할 필요가 없습니다. 또한 시스템은 레이아웃에 있는 각 View 객체의 현재 상태를 추적하므로 사용자가 EditText 위젯에 텍스트를 입력해도 콘텐츠가 유지되므로 저장하고 복원할 필요가 없습니다.

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

활동은 정지됨 상태에서 다시 시작되어 사용자와 상호작용하거나, 실행을 종료하고 사라집니다. 활동이 다시 시작되면 시스템은 onRestart()를 호출합니다. Activity가 실행을 종료하면 시스템은 onDestroy()를 호출합니다.

onDestroy()

onDestroy()는 활동이 소멸되기 전에 호출됩니다. 시스템은 다음 두 가지 이유 중 하나로 이 콜백을 호출합니다.

  1. 사용자가 활동을 완전히 닫았기 때문에 또는 활동에서 finish()가 호출되어 활동이 종료되고 있는 것입니다.
  2. 기기 회전 또는 멀티 윈도우 모드 전환과 같은 구성 변경으로 인해 시스템이 일시적으로 활동을 소멸하는 경우

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

Activity에 소멸되는 이유를 결정하는 로직을 입력하는 대신 ViewModel 객체를 사용하여 Activity의 관련 뷰 데이터를 포함합니다. 구성 변경으로 인해 Activity가 다시 생성되는 경우 ViewModel는 보존되어 다음 Activity 인스턴스에 제공되므로 아무 작업도 수행할 필요가 없습니다.

Activity가 다시 생성되지 않으면 ViewModel에는 onCleared() 메서드가 호출되어, 소멸되기 전에 필요한 모든 데이터를 정리할 수 있습니다. 이러한 두 시나리오는 isFinishing() 메서드로 구분할 수 있습니다.

활동이 완료되는 경우 onDestroy()는 활동이 수신하는 최종 수명 주기 콜백입니다. 구성 변경의 결과로 onDestroy()가 호출되면 시스템은 즉시 새 활동 인스턴스를 만든 다음 새 구성의 이 새 인스턴스에 onCreate()를 호출합니다.

onDestroy() 콜백은 onStop()와 같이 이전 콜백에서 해제되지 않은 모든 리소스를 해제합니다.

Activity 상태 및 메모리에서 제거

시스템은 RAM을 확보해야 할 때 프로세스를 종료합니다. 시스템이 특정 프로세스를 종료할 가능성은 그 시점의 프로세스 상태에 따라 다릅니다. 그리고 프로세스 상태는 프로세스에서 실행되는 활동 상태에 따라 달라집니다. 표 1은 프로세스 상태, 활동 상태, 시스템이 프로세스를 종료할 가능성 간의 상관관계를 보여줍니다. 이 표는 프로세스가 다른 유형의 애플리케이션 구성요소를 실행하지 않는 경우에만 적용됩니다.

종료될 가능성 프로세스 상태 최종 활동 상태
가장 낮음 포그라운드(포커스가 있거나 포커스를 가져올 예정) 재개됨
낮음 보임 (포커스 없음) 시작됨/일시중지됨
더 높은 화질 배경 (숨김) 중지됨
가장 높음 공석 소멸됨

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

시스템은 메모리 공간을 확보하기 위해 절대 활동을 직접 종료하지 않습니다. 대신 활동이 실행되는 프로세스를 종료하여 활동뿐만 아니라 프로세스에서 실행 중인 다른 모든 항목도 소멸합니다. 시스템에서 시작된 프로세스가 종료될 때 활동의 UI 상태를 보존하고 복원하는 방법을 알아보려면 상태 저장 및 복원 섹션을 참고하세요.

사용자는 설정의 애플리케이션 관리자를 사용하여 프로세스를 종료하여 해당 앱을 종료할 수도 있습니다.

프로세스에 관한 자세한 내용은 프로세스 및 스레드 개요를 참고하세요.

임시 UI 상태 저장 및 복원

사용자는 활동의 UI 상태가 회전 또는 멀티 윈도우 모드로의 전환과 같은 구성 변경사항이 발생하더라도 동일하게 유지될 것으로 기대합니다. 그러나 시스템은 이런 구성 변경이 발생하면 기본적으로 활동을 소멸시켜 활동 인스턴스에 저장된 모든 UI 상태를 제거합니다.

마찬가지로 사용자는 일시적으로 앱에서 다른 앱으로 전환했다가 다시 앱으로 돌아왔을 때도 UI 상태가 그대로 유지되기를 기대합니다. 그러나 사용자가 나가서 활동이 중지되면 시스템은 애플리케이션의 프로세스를 소멸할 수 있습니다.

시스템 제약 조건으로 인해 활동이 소멸되는 경우 ViewModel, onSaveInstanceState() 또는 로컬 저장소를 조합하여 사용자의 일시적인 UI 상태를 보존하세요. 시스템 동작과 비교한 사용자 기대치 및 시스템에서 시작된 활동 및 프로세스 종료 전반에서 복잡한 UI 상태 데이터를 가장 효과적으로 보존하는 방법을 자세히 알아보려면 UI 상태 저장을 참고하세요.

이 섹션에서는 인스턴스 상태가 무엇인지, 활동 자체의 콜백인 onSaveInstance() 메서드를 구현하는 방법을 간략하게 설명합니다. UI 데이터가 가벼우면 onSaveInstance()만 사용하여 구성 변경과 시스템에서 시작된 프로세스 종료 시 UI 상태를 유지할 수 있습니다. 그러나 onSaveInstance()로 인해 직렬화/역직렬화 비용이 발생하므로 대부분의 경우 UI 상태 저장에 설명된 대로 ViewModelonSaveInstance()를 모두 사용합니다.

참고: 구성 변경사항, 필요한 경우 Activity 재생성을 제한하는 방법, 뷰 시스템 및 Jetpack Compose에서 이러한 구성 변경에 반응하는 방법에 관한 자세한 내용은 구성 변경사항 처리 페이지를 참고하세요.

인스턴스 상태

정상적인 앱 동작으로 인해 활동이 소멸되는 시나리오는 몇 가지가 있습니다. 예를 들어 사용자가 뒤로 버튼을 누르거나 활동이 finish() 메서드를 호출하여 자체적인 소멸 신호를 보내는 경우입니다.

사용자가 뒤로 버튼을 누르거나 활동이 자체적으로 종료되어 활동이 소멸되면 해당 Activity 인스턴스에 관한 시스템과 사용자의 개념이 모두 영원히 사라집니다. 이러한 시나리오에서는 사용자의 기대치가 시스템의 동작과 일치하므로 추가로 해야 할 작업이 없습니다.

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

시스템에서 이전 상태를 복원하기 위해 사용하는 저장된 데이터를 인스턴스 상태라고 합니다. Bundle 객체에 저장된 키-값 쌍 모음입니다. 기본적으로 시스템은 Bundle 인스턴스 상태를 사용하여 EditText 위젯에 입력된 텍스트 값과 같은 활동 레이아웃의 각 View 객체에 관한 정보를 저장합니다.

따라서 활동 인스턴스가 소멸되고 재생성된 경우, 레이아웃의 상태는 별도의 코드 요청 없이 이전 상태로 복원됩니다. 하지만 활동에서 사용자 진행 상태를 추적하는 멤버 변수처럼 활동에 복원하고자 하는 상태 정보가 더 많이 있는 경우도 있습니다.

참고: Android 시스템이 활동에서 뷰 상태를 복원할 수 있으려면 android:id 속성에서 제공하는 고유 ID가 각 뷰에 있어야 합니다.

Bundle 객체는 기본 스레드에서 직렬화가 필요하고 시스템 프로세스 메모리를 사용하므로 소량의 데이터를 보존하는 데 적합하지 않습니다. 소량의 데이터를 보존하려면 UI 상태 저장에 설명된 대로 영구 로컬 저장소, onSaveInstanceState() 메서드, ViewModel 클래스를 사용하여 데이터를 보존하는 결합 방식을 사용합니다.

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

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

활동의 추가 인스턴스 상태 정보를 저장하려면 onSaveInstanceState()를 재정의하고 활동이 예기치 않게 소멸될 경우 저장되는 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);
}

참고: 사용자가 활동을 명시적으로 닫을 때나 finish()가 호출되는 경우에는 onSaveInstanceState()가 호출되지 않습니다.

영구 데이터(예: 사용자 환경설정 또는 데이터베이스 데이터)를 저장하려면 활동이 포그라운드에 있을 때 적절한 기회를 포착합니다. 이러한 기회가 발생하지 않으면 onStop() 메서드 중에 영구 데이터를 저장하세요.

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

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

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

다음 코드 스니펫은 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() 중에 상태를 복원하는 대신 시스템에서 onStart() 메서드 뒤에 호출하는 onRestoreInstanceState()를 구현하도록 선택할 수 있습니다. 시스템은 복원할 저장된 상태가 있는 경우에만 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()의 슈퍼클래스 구현을 호출하여 기본 구현에서 뷰 계층 구조의 상태를 복원할 수 있도록 합니다.

활동 간 이동

앱은 사용자가 기기의 뒤로 버튼을 탭하거나 활동이 다른 활동을 실행하는 경우와 같이 앱의 전체 기간 동안 여러 번 활동을 시작하거나 종료할 수 있습니다.

이 섹션에서는 성공적인 활동 전환을 구현하기 위해 알아야 할 주제를 다룹니다. 여기에는 다른 활동에서 활동 시작하기, 액티비티 상태 저장하기, 액티비티 상태 복원하기 등이 포함되어 있습니다.

다른 활동에서 활동시작하기

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

활동이 시작하려는 새 활동의 결과를 원하는지 여부에 따라 startActivity() 메서드 또는 startActivityForResult() 메서드를 사용하여 새 활동을 시작합니다. 두 가지 경우 모두 Intent 객체를 전달합니다.

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

startActivity()

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

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

Kotlin

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

Java

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

또한 애플리케이션에서 다른 몇 가지 작업을 수행하고자 할 수도 있습니다. 예를 들어 이메일 보내기, SMS 보내기 또는 상태 업데이트 등의 작업을 활동의 데이터를 사용하여 수행할 수 있습니다. 이 경우, 본인의 애플리케이션에 그러한 동작을 실행할 자체 활동이 없을 수도 있습니다. 따라서 기기에 있는 다른 애플리케이션이 제공하는 활동을 대신 활용하여 동작을 실행하게 할 수 있습니다.

바로 이 지점에서 인텐트가 정말 중요합니다. 실행하려는 작업을 설명하는 인텐트를 만들 수 있습니다. 그러면 시스템이 다른 애플리케이션에서 적절한 활동을 시작합니다. 인텐트를 처리할 수 있는 활동이 여러 개 있는 경우, 사용자는 어느 것을 사용할지 선택할 수 있습니다. 예를 들어 사용자가 이메일 메시지를 보낼 수 있게 하려면 다음과 같은 인텐트를 만들면 됩니다.

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는 이메일이 전송될 이메일 주소의 문자열 배열입니다. 이메일 애플리케이션이 이 인텐트에 응답하면 애플리케이션은 추가 항목에서 제공된 문자열 배열을 읽고 이메일 작성 양식의 '받는 사람' 필드에 주소를 배치합니다. 이 상황에서 이메일 애플리케이션의 활동이 시작되고 사용자가 작업을 끝내면 활동이 재개됩니다.

startActivityForResult()

활동이 종료될 때 결과를 반환받고자 할 수도 있습니다. 예를 들어 사용자가 연락처 목록에서 사람을 선택할 수 있게 하는 활동을 시작할 수 있습니다. 종료되면 선택한 사람이 반환됩니다. 이렇게 하려면 startActivityForResult(Intent, int) 메서드를 호출해야 합니다. 이 메서드에서는 정수 매개변수가 호출을 식별합니다.

이 식별자는 동일한 활동에서 여러 번의 startActivityForResult(Intent, int) 호출을 구분하기 위한 것입니다. 이는 전역 식별자가 아니며 다른 앱 또는 활동과 충돌할 위험이 없습니다. 결과는 onActivityResult(int, int, Intent) 메서드를 통해 반환됩니다.

하위 활동이 존재하면 setResult(int)를 호출하여 상위 활동으로 데이터를 반환할 수 있습니다. 하위 활동은 결과 코드를 제공해야 합니다. 결과 코드는 표준 결과 RESULT_CANCELED, RESULT_OK 또는 RESULT_FIRST_USER로 시작하는 맞춤 값일 수 있습니다.

또한 하위 활동은 원하는 추가 데이터가 포함된 Intent 객체를 선택적으로 반환할 수 있습니다. 상위 활동은 원래 상위 활동이 제공한 정수 식별자와 함께 onActivityResult(int, int, Intent) 메서드를 사용하여 정보를 수신합니다.

어떤 이유로(예: 비정상 종료) 하위 활동이 실패하면 상위 활동은 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) {
                    // A contact was picked. Display it to the user.
                    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. Display it to the user.
                 startActivity(new Intent(Intent.ACTION_VIEW, data));
             }
         }
     }
 }

활동 조정

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

수명 주기 콜백은 명확히 정의되어 있습니다. 특히 두 활동이 동일한 프로세스(즉, 동일한 앱)에 있고 한 활동이 다른 활동을 시작하는 경우에 더욱 그렇습니다. 활동 A가 활동 B를 시작할 때 발생하는 작업 순서는 아래와 같습니다.

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

이러한 수명 주기 콜백 시퀀스를 사용하면 한 활동에서 다른 활동으로 정보 전환을 관리할 수 있습니다.