활동 수명 주기 단계

1. 시작하기 전에

이 Codelab에서는 Android의 기본 부분인 활동 수명 주기에 관해 알아봅니다.

활동은 전체 기간 동안 여러 상태로 전환되고 때에 따라 이전 상태로 돌아가기도 합니다. 이러한 상태 전환을 활동 수명 주기라고 합니다.

Android에서 활동은 사용자와 상호작용하는 진입점입니다.

이전에는 하나의 활동이 앱에서 하나의 화면을 표시했습니다. 현재 권장사항에 따르면 한 활동이 필요에 따라 화면을 교체하여 여러 화면을 표시할 수 있습니다.

활동 수명 주기는 활동 생성부터 시스템이 활동의 리소스를 회수하는 활동의 소멸까지 이어집니다. 사용자가 활동을 탐색할 때 각 활동은 활동 수명 주기의 여러 상태 간에 전환됩니다.

Android 개발자는 활동 수명 주기를 파악해야 합니다. 활동이 수명 주기 상태 변경에 올바르게 응답하지 않으면 앱에 이상한 버그가 발생하여 사용자가 동작을 혼동하거나 앱에서 Android 시스템 리소스를 너무 많이 사용할 수 있습니다. Android 수명 주기를 이해하고 수명 주기 상태 변경에 올바르게 응답하는 것은 Android 개발에서 중요한 부분입니다.

기본 요건

  • 활동의 정의와 앱에서 활동을 만드는 방법에 관한 지식
  • 활동의 onCreate() 메서드의 기능과 이 메서드에서 실행되는 작업의 종류에 관한 지식

학습할 내용

  • 로깅 정보를 Logcat에 출력하는 방법
  • Activity 수명 주기 기본사항과 활동이 상태 간에 이동할 때 호출되는 콜백
  • 수명 주기 콜백 메서드를 재정의하여 활동 수명 주기의 다양한 시점에 작업을 실행하는 방법

빌드할 항목

  • Dessert Clicker라는 시작 앱을 수정하여 Logcat에 표시되는 로깅 정보를 추가합니다.
  • 수명 주기 콜백 메서드를 재정의하고 액티비티 상태의 변경사항을 기록합니다.
  • 앱을 실행하고 활동이 시작, 중지, 재개될 때 표시되는 로깅 정보를 기록합니다.
  • rememberSaveable을 구현하여 기기 설정이 변경되면 손실될 수 있는 앱 데이터를 유지합니다.

2. 앱 개요

이 Codelab에서는 Dessert Clicker라는 시작 앱을 사용합니다. Dessert Clicker에서는 사용자가 화면에서 디저트를 탭할 때마다 앱이 사용자를 위해 디저트를 '구매'합니다. 앱은 다음에 관해 레이아웃에서 값을 업데이트합니다.

  • '구매한' 디저트 수
  • '구매한' 디저트의 총수익

245d0bdfc09f4d54.png

이 앱에는 Android 수명 주기와 관련된 몇 가지 버그가 포함되어 있습니다. 예를 들어 특정 상황에서 앱은 디저트 값을 0으로 재설정합니다. Android 수명 주기를 이해하면 이러한 문제가 발생하는 이유와 해결 방법을 알 수 있습니다.

시작 코드 다운로드하기

Android 스튜디오에서 basic-android-kotlin-compose-training-dessert-clicker 폴더를 엽니다.

3. 수명 주기 메서드 살펴보기 및 기본 로깅 추가

모든 활동에는 수명 주기가 있습니다. 이는 식물이나 동물의 수명 주기와 비슷한 개념으로, 아래와 같은 나비의 수명 주기를 예로 들 수 있습니다. 나비는 알로 태어나 애벌레, 번데기를 거쳐 나비가 되고 죽음에 이르기까지 여러 단계를 거칩니다.

나비 수명 주기 - 알에서 애벌레, 번데기를 거쳐 나비로 성장하고 죽음에 이름

마찬가지로 활동 수명 주기도 활동이 처음 초기화될 때부터 소멸(이때 운영체제(OS)에서 메모리를 회수함)될 때까지 활동이 거쳐가는 여러 상태로 구성됩니다. 일반적으로 프로그램의 진입점은 main() 메서드입니다. 그러나 Android 활동은 onCreate() 메서드로 시작합니다. 이 메서드는 위 나비 예에서 알 단계에 해당합니다. 이 과정에서는 여러 차례 이미 활동을 사용했으므로 onCreate() 메서드를 알고 있을 수 있습니다. 사용자가 앱을 시작하여 활동 간에 이동하고 앱 안팎으로 이동할 때 활동은 상태를 변경합니다.

다음 다이어그램은 모든 활동 수명 주기 상태를 보여줍니다. 이름에서 알 수 있듯이 이러한 상태는 활동의 상태를 나타냅니다. 나비 수명 주기와 달리 활동은 한 방향으로만 이동하는 것이 아니라 수명 주기 내내 상태 간에 전환할 수 있습니다.

ca808edb1c95f07a.png

활동 수명 주기 상태가 변경될 때, 일부 동작을 변경하거나 코드를 실행하려고 할 때가 많습니다. 따라서 Activity 클래스 자체와 Activity의 모든 서브클래스(예: ComponentActivity)는 일련의 수명 주기 콜백 메서드를 구현합니다. Android에서는 활동이 한 상태에서 다른 상태로 이동할 때 이러한 콜백을 호출하고 개발자는 이러한 메서드를 자체 활동에서 재정의하여 수명 주기 상태 변경에 응답해 작업을 실행할 수 있습니다. 다음 다이어그램은 사용할 수 있는 재정의 가능한 콜백과 함께 수명 주기 상태를 보여줍니다.

활동 수명 주기 체계

Android가 재정의 가능한 콜백을 호출하는 시점과 각 콜백 메서드에서 해야 할 작업을 알아야 합니다. 하지만 이 두 다이어그램은 모두 복잡하며 혼란을 줄 수 있습니다. 이 Codelab에서는 각 상태와 콜백의 의미를 단순하게 설명하는 대신 Android 활동 수명 주기를 자세히 알아보고 파악합니다.

1단계: onCreate() 메서드 확인 및 로깅 추가

Android 수명 주기에서 어떤 일이 일어나는지 파악하려면 다양한 수명 주기 메서드가 호출되는 시점을 아는 것이 좋습니다. 이 정보는 Dessert Clicker 앱에서 문제가 발생하는 위치를 확인하는 데 도움이 됩니다.

이 정보를 확인하는 간단한 방법은 Android 로깅 기능을 사용하는 것입니다. 로깅을 사용하면 앱이 실행되는 동안 콘솔에 짧은 메시지를 작성하고 이를 사용하여 다양한 콜백이 트리거되는 시점을 확인할 수 있습니다.

  1. Dessert Clicker 앱을 실행하고 디저트 사진을 여러 번 탭합니다. Desserts sold의 값과 총 달러 금액이 어떻게 변경되는지 확인하세요.
  2. MainActivity.kt를 열고 이 활동의 onCreate() 메서드를 확인합니다.
override fun onCreate(savedInstanceState: Bundle?) {
    // ...
}

전에 이 콜백을 사용했으므로 활동 수명 주기 다이어그램에서 onCreate() 메서드를 인식하고 있을 수 있습니다. 모든 활동에서 구현해야 하는 메서드가 바로 이 메서드입니다. onCreate() 메서드에서 활동의 일회성 초기화를 실행해야 합니다. 예를 들어 onCreate()에서 활동의 UI 레이아웃을 지정하는 setContent()를 호출합니다.

onCreate 수명 주기 메서드

onCreate() 수명 주기 메서드는 활동이 초기화된 직후(OS에서 메모리에 새 Activity 객체를 만들 때) 한 번 호출됩니다. onCreate()가 실행되면 활동이 생성됨으로 간주됩니다.

  1. 클래스 선언 class MainActivity 위에 있는 MainActivity.kt의 최상위 수준에 다음 상수를 추가합니다.

TAG 상수는 파일에 선언하는 것이 좋습니다. 값이 변경되지 않기 때문입니다.

이를 컴파일 시간 상수로 표시하려면 변수를 선언할 때 const를 사용합니다. 컴파일 시간 상수는 컴파일 중에 알려진 값입니다.

private const val TAG = "MainActivity"
  1. onCreate() 메서드에서 super.onCreate() 호출 바로 뒤에 다음 줄을 추가합니다.
Log.d(TAG, "onCreate Called")
  1. 필요한 경우 Log 클래스를 가져옵니다(Mac에서는 Alt+Enter 또는 Option+Enter를 누르고 가져오기를 선택). 자동 가져오기를 사용 설정했다면 자동으로 실행됩니다.
import android.util.Log

Log 클래스는 Logcat에 메시지를 씁니다. Logcat은 메시지를 기록하는 콘솔입니다. Log.d() 메서드나 기타 Log 클래스 메서드를 사용하여 개발자가 로그에 명시적으로 전송하는 메시지를 비롯하여, 앱에 관한 Android의 메시지가 여기에 표시됩니다.

Log 명령에는 세 가지 중요한 측면이 있습니다.

  • 로그 메시지의 우선순위, 즉 메시지의 중요도. 여기서는 Log.v()가 상세 메시지를 기록합니다. Log.d() 메서드는 디버그 메시지를 작성합니다. Log 클래스의 다른 메서드에는 정보 메시지의 경우 Log.i(), 경고의 경우 Log.w(), 오류 메시지의 경우 Log.e()가 있습니다.
  • 로그 tag(첫 번째 매개변수). 여기서는 "MainActivity"입니다. 태그는 Logcat에서 로그 메시지를 더 쉽게 찾을 수 있는 문자열입니다. 태그는 보통 클래스 이름입니다.
  • 실제 로그 메시지 msg(두 번째 매개변수). 짧은 문자열로, 여기서는 "onCreate Called"입니다.

a4ff4aa74384ff6.png

  1. Dessert Clicker 앱을 컴파일하고 실행합니다. 디저트를 탭할 때 앱에 동작 차이가 없습니다. Android 스튜디오의 화면 하단에서 Logcat 탭을 클릭합니다.

ed03d4bb1f020995.png

  1. Logcat 창의 검색창에 tag:MainActivity를 입력합니다.

961ea44c4b9ee3c.png

Logcat에는 많은 메시지가 포함될 수 있지만 대부분은 유용하지 않습니다. Logcat 항목을 다양한 방식으로 필터링할 수 있지만 검색이 가장 쉬운 방법입니다. 코드에서 MainActivity를 로그 태그로 사용했으므로 이 태그를 사용하여 로그를 필터링할 수 있습니다. 로그 메시지에는 날짜 및 시간, 로그 태그, 패키지 이름(com.example.dessertclicker), 실제 메시지가 포함됩니다. 이 메시지는 로그에 표시되므로 onCreate()가 실행되었음을 알 수 있습니다.

2단계: onStart() 메서드 구현

onStart() 수명 주기 메서드는 onCreate() 직후에 호출됩니다. onStart()가 실행되면 활동이 화면에 표시됩니다. 활동을 초기화하는 데 한 번만 호출되는 onCreate()와 달리 onStart()는 시스템에서 활동의 수명 주기 동안 여러 번 호출할 수 있습니다.

a357d2291de472d9.png

onStart()는 상응하는 onStop() 수명 주기 메서드와 페어링됩니다. 사용자가 앱을 시작한 후 기기 홈 화면으로 돌아오면 활동이 중지되고 더 이상 화면에 표시되지 않습니다.

  1. Android 스튜디오에서 MainActivity.kt가 열려 있고 MainActivity 클래스 내 커서가 있는 상태에서 Code > Override Methods...를 선택하거나 Control+O를 누릅니다. 이 클래스에서 재정의할 수 있는 모든 메서드의 긴 목록이 있는 대화상자가 표시됩니다.

11ff93bee1c3940f.png

  1. onStart를 입력하여 올바른 메서드를 검색합니다. 일치하는 다음 항목으로 스크롤하려면 아래쪽 화살표를 사용합니다. 목록에서 onStart()를 선택하고 OK를 클릭하여 상용구 재정의 코드를 삽입합니다. 코드는 다음 예와 같습니다.
override fun onStart() {
    super.onStart()
}
  1. onStart() 메서드 내에서 로그 메시지를 추가합니다.
override fun onStart() {
    super.onStart()
    Log.d(TAG, "onStart Called")
}
  1. Dessert Clicker 앱을 컴파일하고 실행한 다음 Logcat 창을 엽니다.
  2. 검색창에 tag:MainActivity를 입력하여 로그를 필터링합니다. onCreate() 메서드와 onStart() 메서드가 모두 차례로 호출된 후 활동이 화면에 표시됩니다.
  3. 기기에서 버튼을 누른 다음 최근 화면을 사용하여 활동으로 돌아갑니다. 활동은 모두 동일한 값으로 중단된 지점에서 다시 시작되고 onStart()는 Logcat에 두 번째로 기록됩니다. onCreate() 메서드는 다시 호출되지 않습니다.
2024-02-20 10:30:00.231  5684-5684  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:30:00.278  5684-5684  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:30:39.020  5684-5684  MainActivity            com.example.dessertclicker           D  onStart Called

3단계: 더 많은 로그 구문 추가

이 단계에서는 다른 모든 수명 주기 메서드의 로깅을 구현합니다.

  1. 다음 코드와 같이 MainActivity의 나머지 수명 주기 메서드를 재정의하고 각 메서드의 로그 문을 추가합니다.
override fun onResume() {
    super.onResume()
    Log.d(TAG, "onResume Called")
}

override fun onRestart() {
    super.onRestart()
    Log.d(TAG, "onRestart Called")
}

override fun onPause() {
    super.onPause()
    Log.d(TAG, "onPause Called")
}

override fun onStop() {
    super.onStop()
    Log.d(TAG, "onStop Called")
}

override fun onDestroy() {
    super.onDestroy()
    Log.d(TAG, "onDestroy Called")
}
  1. Dessert Clicker를 다시 컴파일하고 실행하여 Logcat을 확인합니다.

이번에는 onCreate()onStart() 외에 onResume() 수명 주기 콜백의 로그 메시지가 있습니다.

2024-02-20 10:33:36.033  5789-5789  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:33:36.073  5789-5789  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:33:36.075  5789-5789  MainActivity            com.example.dessertclicker           D  onResume Called

활동이 처음부터 시작되면 다음 세 가지 수명 주기 콜백이 순서대로 모두 호출됩니다.

  • onCreate(): 시스템이 앱을 생성할 때 호출됩니다.
  • onStart(): 앱이 화면에 표시되도록 하지만 사용자는 아직 앱과 상호작용할 수 없습니다.
  • onResume(): 앱을 포그라운드로 가져오고 사용자는 이제 앱과 상호작용할 수 있습니다.

이름과 달리 onResume() 메서드는 다시 시작할 대상이 없어도 시작 시 호출됩니다.

활동 수명 주기 체계

4. 수명 주기 사용 사례 살펴보기

로깅을 위해 Dessert Clicker 앱을 설정했으므로 이제 앱을 사용하여 수명 주기 콜백이 트리거되는 방식을 살펴보겠습니다.

사용 사례 1: 활동 열기 및 닫기

앱을 처음 시작한 후 닫는 가장 기본적인 사용 사례로 시작합니다.

  1. Dessert Clicker 앱이 아직 실행되고 있지 않다면 앱을 컴파일하고 실행합니다. 확인했듯이 활동이 처음 시작되면 onCreate(), onStart(), onResume() 콜백이 호출됩니다.
2024-02-20 10:33:36.033  5789-5789  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:33:36.073  5789-5789  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:33:36.075  5789-5789  MainActivity            com.example.dessertclicker           D  onResume Called
  1. 컵케이크를 몇 번 탭합니다.
  2. 기기에서 뒤로 버튼을 탭합니다.

Logcat에서 onPause(), onStop()이 순서대로 호출됩니다.

2024-02-20 10:34:41.974  5789-5789  MainActivity            com.example.dessertclicker           D  onPause Called
2024-02-20 10:34:42.411  5789-5789  MainActivity            com.example.dessertclicker           D  onStop Called

이 경우 뒤로 버튼을 사용하면 활동(및 앱)이 화면에서 삭제되고 활동 스택의 뒤로 이동합니다.

코드에서 활동의 finish() 메서드를 수동으로 호출하거나 사용자가 앱을 강제 종료하면 Android OS에서는 활동을 닫을 수 있습니다. 예를 들어 사용자는 최근 화면에서 앱을 강제 종료하거나 닫을 수 있습니다. OS는 또한 앱이 장시간 화면에 표시되지 않으면 자체적으로 활동을 종료할 수도 있습니다. 이를 통해 Android는 배터리 수명을 보존하고 앱이 사용하던 리소스를 회수하여 다른 앱에서 사용할 수 있도록 합니다. 이는 Android 시스템이 활동을 소멸하는 이유를 보여주는 몇 가지 예에 불과합니다. Android 시스템에서 경고 없이 활동을 소멸하는 사례는 더 있습니다.

사용 사례 2: 활동에서 이동 및 활동으로 다시 이동

이제 앱을 시작했다가 닫았으므로 활동이 처음 만들어질 때의 수명 주기 단계를 대부분 확인했습니다. 활동이 닫힐 때 거치는 수명 주기 상태도 대부분 확인했습니다. 그러나 사용자가 Android 기기와 상호작용할 때는 앱 간에 전환하거나 홈으로 돌아가거나 새 앱을 시작하거나 다른 활동(전화 통화 등)으로 인한 중단을 처리합니다.

활동은 사용자가 활동에서 벗어날 때마다 완전히 닫히지 않습니다.

  • 활동이 화면에 더 이상 표시되지 않으면 이 상태는 활동이 백그라운드에 배치되는 것입니다. 이와 반대의 경우는 활동이 포그라운드에 있거나 화면에 표시되는 것입니다.
  • 사용자가 앱으로 돌아오면 동일한 활동이 다시 시작되어 화면에 다시 표시됩니다. 수명 주기에서 이 부분을 앱의 표시 수명 주기라고 합니다.

앱은 백그라운드에 있을 때 시스템 리소스와 배터리 수명을 보존하기 위해 일반적으로 활발히 실행되지 않아야 합니다. Activity 수명 주기와 그 콜백을 사용하여 앱이 백그라운드로 이동하는 시점을 알 수 있어 진행 중인 작업을 일시중지할 수 있습니다. 그런 다음 앱이 포그라운드로 전환될 때 작업을 다시 시작합니다.

이 단계에서는 앱이 백그라운드로 전환되었다가 다시 포그라운드로 돌아올 때 활동 수명 주기를 살펴봅니다.

  1. Dessert Clicker 앱이 실행되는 상태에서 컵케이크를 몇 번 클릭합니다.
  2. 기기에서 버튼을 누르고 Android 스튜디오에서 Logcat을 관찰합니다. 홈 화면으로 돌아오면 앱이 완전히 종료되는 대신 백그라운드로 전환됩니다. onPause()onStop() 메서드가 호출됩니다.
2024-02-20 10:35:26.832  5789-5789  MainActivity            com.example.dessertclicker           D  onPause Called
2024-02-20 10:35:27.233  5789-5789  MainActivity            com.example.dessertclicker           D  onStop Called

onPause()가 호출되면 앱에 더 이상 포커스가 없습니다. onStop() 이후에는 앱이 더 이상 화면에 표시되지 않습니다. 활동이 중지되었지만 Activity 객체는 여전히 백그라운드에서 메모리에 있습니다. Android OS는 활동을 소멸하지 않았습니다. 사용자가 앱으로 돌아올 수 있으므로 Android는 활동 리소스를 유지합니다.

c470ee28ab7f8a1a.png

  1. 최근 화면을 사용하여 앱으로 돌아갑니다. 에뮬레이터에서 최근 화면은 아래 이미지에 표시된 정사각형 시스템 버튼으로 액세스할 수 있습니다.

Logcat에서 활동은 onRestart()onStart()로 다시 시작된 후 onResume()으로 재개됩니다.

bc156252d977e5ae.png

2024-02-20 10:36:05.837  5789-5789  MainActivity            com.example.dessertclicker           D  onRestart Called
2024-02-20 10:36:05.839  5789-5789  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:36:05.842  5789-5789  MainActivity            com.example.dessertclicker           D  onResume Called

활동이 포그라운드로 돌아오면 onCreate() 메서드가 다시 호출되지 않습니다. 활동 객체는 소멸되지 않았으므로 다시 만들지 않아도 됩니다. onCreate() 대신 onRestart() 메서드가 호출됩니다. 이번에는 활동이 포그라운드로 돌아올 때 Desserts sold 수가 유지됩니다.

  1. Dessert Clicker 외의 앱을 하나 이상 시작하여 기기의 최근 화면에 앱이 몇 개 있도록 합니다.
  2. 최근 화면을 불러와서 다른 최근 활동을 엽니다. 그런 다음 최근 앱으로 돌아가서 Dessert Clicker를 다시 포그라운드로 가져옵니다.

여기 Logcat에서 버튼을 누를 때와 동일한 콜백이 표시됩니다. onPause()onStop()은 앱이 백그라운드로 전환될 때 호출되고 다시 돌아올 때는 onRestart(), onStart(), onResume()이 호출됩니다.

이러한 메서드는 앱이 중지되어 백그라운드로 이동하거나 앱이 다시 시작되어 포그라운드로 돌아올 때 호출됩니다. 이러한 경우에 앱에서 작업을 실행해야 한다면 관련 수명 주기 콜백 메서드를 재정의합니다.

사용 사례 3: 부분적으로 활동 숨기기

앱이 시작되고 onStart()가 호출되면 앱이 화면에 표시된다는 것을 알아봤습니다. onResume()이 호출되면 앱은 사용자 포커스를 획득합니다. 즉, 사용자가 앱과 상호작용할 수 있습니다. 앱이 완전히 화면에 표시되고 사용자 포커스를 보유하는 수명 주기 부분을 포그라운드 전체 기간이라고 합니다.

앱이 백그라운드로 이동하면 onPause() 후에 포커스가 상실되고 onStop() 후에 더 이상 앱이 표시되지 않습니다.

포커스와 가시성의 차이가 중요합니다. 활동이 화면에 부분적으로 표시되지만 사용자 포커스는 없을 수 있습니다. 이 단계에서는 활동이 부분적으로 표시되지만 사용자 포커스가 없는 한 가지 사례를 살펴봅니다.

  1. Dessert Clicker 앱이 실행되는 상태에서 화면 오른쪽 상단에 있는 공유 버튼을 클릭합니다.

공유 활동은 화면 아래쪽 절반에 표시되지만 활동은 계속 위쪽 절반에 표시됩니다.

677c190d94e57447.pngca6285cbbe3801cf.png

  1. Logcat을 확인하면 onPause()만 호출되었습니다.
2024-02-20 10:36:42.886  5789-5789  MainActivity            com.example.dessertclicker           D  onPause Called

이 사용 사례에서 onStop()은 호출되지 않습니다. 활동이 계속 부분적으로 표시되기 때문입니다. 그러나 활동에 사용자 포커스가 없어 사용자가 상호작용할 수 없습니다. 포그라운드에 있는 '공유' 활동에 사용자 포커스가 있습니다.

이 차이가 중요한 이유는 무엇인가요? onPause()만 사용한 중단은 보통 활동으로 돌아가거나 다른 활동 또는 앱으로 이동하기 전에 잠시 지속됩니다. 일반적으로 UI를 계속 업데이트하여 나머지 앱이 멈춘 것처럼 보이지 않도록 하는 것이 좋습니다.

onPause()에서 실행되는 모든 코드가 다른 항목이 표시되는 것을 차단하므로 onPause()의 코드를 가볍게 유지합니다. 예를 들어 전화가 걸려오면 onPause()의 코드는 수신 전화 알림을 지연시킬 수 있습니다.

  1. 공유 대화상자 외부를 클릭하여 앱으로 돌아가면 onResume()이 호출됩니다.

onResume()onPause()는 모두 포커스와 관련이 있습니다. onResume() 메서드는 활동에 포커스가 있을 때 호출되고 onPause()는 활동에 포커스가 없을 때 호출됩니다.

5. 구성 변경사항 살펴보기

활동 수명 주기 관리에서 파악해야 할 중요한 또 다른 사례로는 구성 변경이 활동의 수명 주기에 미치는 영향이 있습니다.

구성 변경은 기기 상태가 매우 급격하게 변경되어 시스템이 변경사항을 확인하는 가장 쉬운 방법이 활동을 완전히 종료하고 다시 빌드하는 것일 때 발생합니다. 예를 들어 사용자가 기기 언어를 변경하면 다른 텍스트 방향과 문자열 길이를 수용하도록 전체 레이아웃을 변경해야 할 수 있습니다. 사용자가 기기를 도크에 연결하거나 물리적 키보드를 추가하면 앱 레이아웃은 다른 디스플레이 크기나 레이아웃을 활용해야 할 수 있습니다. 기기 방향이 변경되면(기기가 세로 모드에서 가로 모드로 또는 이와 반대로 회전하는 경우) 새 방향에 맞게 레이아웃을 변경해야 할 수 있습니다. 이러한 시나리오에서 앱이 어떻게 동작하는지 살펴보겠습니다.

설명할 마지막 수명 주기 콜백은 onDestroy()이며 onStop() 후에 호출됩니다. 활동이 소멸되기 직전에 호출됩니다. 이는 앱의 코드에서 finish()를 호출하거나, 구성 변경으로 인해 시스템에서 활동을 소멸하고 다시 만들어야 할 때 발생할 수 있습니다.

구성 변경으로 인한 onDestroy() 호출

화면 회전은 활동이 종료되었다가 다시 시작되도록 하는 구성 변경의 한 유형입니다. 이 구성 변경을 시뮬레이션하고 그 효과를 확인하려면 다음 단계를 완료하세요.

  1. 앱을 컴파일하고 실행합니다.
  2. 에뮬레이터의 화면 회전 잠금이 사용 중지되어 있는지 확인합니다.
  3. 기기나 에뮬레이터를 가로 모드로 회전합니다. 회전 버튼을 사용하여 에뮬레이터를 왼쪽이나 오른쪽으로 회전할 수 있습니다.
  4. Logcat을 검토하고 활동이 종료될 때 onPause(), onStop(), onDestroy()가 순서대로 호출되는 것을 파악합니다.
2024-02-20 10:37:57.078  5987-5987  MainActivity            com.example.dessertclicker           D  onPause Called
2024-02-20 10:37:57.087  5987-5987  MainActivity            com.example.dessertclicker           D  onStop Called
2024-02-20 10:37:57.102  5987-5987  MainActivity            com.example.dessertclicker           D  onDestroy Called

기기 회전 시 데이터 손실

  1. 앱을 컴파일하고 실행한 후 Logcat을 엽니다.
  2. 컵케이크를 몇 번 클릭하고 판매된 디저트와 총수익이 0이 아님을 확인합니다.
  3. 에뮬레이터의 화면 회전 잠금이 사용 중지되어 있는지 확인합니다.
  4. 기기나 에뮬레이터를 가로 모드로 회전합니다. 회전 버튼을 사용하여 에뮬레이터를 왼쪽이나 오른쪽으로 회전할 수 있습니다.

f745d0e2697415fd.png

  1. Logcat에서 출력을 확인합니다. MainActivity에서 출력을 필터링합니다.
2024-02-20 10:39:22.724  6087-6087  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:39:22.752  6087-6087  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:39:22.753  6087-6087  MainActivity            com.example.dessertclicker           D  onResume Called
2024-02-20 10:39:40.508  6087-6087  MainActivity            com.example.dessertclicker           D  onPause Called
2024-02-20 10:39:40.540  6087-6087  MainActivity            com.example.dessertclicker           D  onStop Called
2024-02-20 10:39:40.549  6087-6087  MainActivity            com.example.dessertclicker           D  onDestroy Called
2024-02-20 10:39:40.582  6087-6087  MainActivity            com.example.dessertclicker           D  onCreate Called
2024-02-20 10:39:40.584  6087-6087  MainActivity            com.example.dessertclicker           D  onStart Called
2024-02-20 10:39:40.585  6087-6087  MainActivity            com.example.dessertclicker           D  onResume Called

기기나 에뮬레이터에서 화면이 회전되면 시스템은 모든 수명 주기 콜백을 호출하여 활동을 종료합니다. 그런 다음 활동이 다시 만들어질 때 시스템은 모든 수명 주기 콜백을 호출하여 활동을 시작합니다.

기기가 회전되어 활동이 종료되고 다시 만들어지면 활동은 기본값(디저트 이미지, 판매된 디저트 수, 총수익이 0으로 재설정됨)으로 다시 시작됩니다.

이러한 값이 재설정되는 이유와 값을 수정하는 방법을 알아보려면 컴포저블의 수명 주기와 그 상태를 관찰하고 유지하는 방법을 알아야 합니다.

컴포저블의 수명 주기

앱의 UI는 처음에 컴포지션이라는 프로세스에서 구성 가능한 함수를 실행하여 빌드됩니다.

앱 상태가 변경되면 리컴포지션이 예약됩니다. 리컴포지션은 상태가 변경되었을 수 있는 구성 가능한 함수를 Compose에서 다시 실행하고 업데이트된 UI를 만드는 것을 의미합니다. 컴포지션은 이러한 변경사항을 반영하도록 업데이트됩니다.

컴포지션을 만들거나 업데이트하는 유일한 방법은 초기 컴포지션 및 후속 리컴포지션을 통해서입니다.

구성 가능한 함수에는 활동 수명 주기와 별개인 자체 수명 주기가 있습니다. 수명 주기는 컴포지션을 시작하고 0회 이상 재구성한 다음 컴포지션을 종료하는 이벤트로 구성됩니다.

Compose가 리컴포지션을 추적하고 트리거하려면 상태가 변경된 시점을 알아야 합니다. 객체의 상태를 추적해야 한다고 Compose에 나타내려면 객체의 유형이 State 또는 MutableState여야 합니다. State 유형은 변경할 수 없으며 읽기만 가능합니다. MutableState 유형은 변경 가능하며 읽기 및 쓰기를 허용합니다.

이전 Codelab의 Lemonade 앱Tip Time 앱에서 MutableState를 이미 확인하고 사용했습니다.

변경 가능한 변수 revenue를 만들려면 mutableStateOf를 사용하여 선언합니다. 0은 초기 기본값입니다.

var revenue = mutableStateOf(0)

이는 수익 값이 변경될 때 Compose가 리컴포지션을 트리거하도록 하는 데 충분하지만 업데이트된 값을 유지하는 데는 충분하지 않습니다. 컴포저블이 다시 실행될 때마다 수익 값이 초기 기본값 0으로 다시 초기화됩니다.

리컴포지션 중에 값을 유지하고 재사용하도록 Compose에 지시하려면 remember API를 사용하여 이를 선언해야 합니다.

var revenue by remember { mutableStateOf(0) }

revenue 값이 변경되면 Compose는 리컴포지션을 위해 이 값을 읽는 모든 구성 가능한 함수를 예약합니다.

Compose는 리컴포지션 중에 수익 상태를 기억하지만 구성 변경 중에는 이 상태를 유지하지 않습니다. Compose가 구성 변경 중에 상태를 유지하려면 rememberSaveable을 사용해야 합니다.

추가 연습 및 정보는 Compose의 상태 소개 Codelab을 참고하세요.

rememberSaveable을 사용하여 구성 변경 시 값 저장

Android OS에서 활동을 소멸하고 다시 만드는 경우 rememberSaveable 함수를 사용하여 필요한 값을 저장합니다.

리컴포지션 중에 값을 저장하려면 remember를 사용해야 합니다. rememberSaveable을 사용하여 리컴포지션 및 구성 변경 중에 값을 저장합니다.

rememberSaveable을 사용하여 값을 저장하면 필요한 경우 활동이 복원될 때 사용할 수 있습니다.

  1. MainActivity에서 현재 remember를 사용하는 5개 변수로 된 그룹을 rememberSaveable로 업데이트합니다.
var revenue by remember { mutableStateOf(0) }
...
var currentDessertImageId by remember {
    mutableStateOf(desserts[currentDessertIndex].imageId)
}
var revenue by rememberSaveable { mutableStateOf(0) }
...
var currentDessertImageId by rememberSaveable {
    mutableStateOf(desserts[currentDessertIndex].imageId)
}
  1. 앱을 컴파일하고 실행합니다.
  2. 컵케이크를 몇 번 클릭하고 판매된 디저트와 총수익이 0이 아님을 확인합니다.
  3. 기기나 에뮬레이터를 가로 모드로 회전합니다.
  4. 활동이 소멸되고 다시 만들어진 후 디저트 이미지, 판매된 디저트, 총수익이 이전 값으로 복원됩니다.

6. 솔루션 코드

7. 요약

활동 수명 주기

  • 활동 수명 주기는 활동이 전환되는 일련의 상태입니다. 활동 수명 주기는 Android OS에서 처음 활동을 만들 때 시작되고 OS에서 활동을 소멸할 때 끝납니다.
  • 사용자가 활동 간에 그리고 앱 안팎으로 이동할 때 각 활동은 활동 수명 주기의 상태 간에 이동합니다.
  • 활동 수명 주기의 각 상태에는 Activity 클래스에서 재정의할 수 있는 상응하는 콜백 메서드가 있습니다. 수명 주기 메서드의 핵심 집합은 다음과 같습니다. onCreate(), onRestart(), onStart(), onResume(), onPause(), onStop(), onDestroy()
  • 활동이 수명 주기 상태로 전환될 때 발생하는 동작을 추가하려면 상태의 콜백 메서드를 재정의합니다.
  • Android 스튜디오에서 스켈레톤 재정의 메서드를 클래스에 추가하려면 Code > Override Methods...를 선택하거나 Control+O를 누릅니다.

로그로 로깅

  • Android Logging API와 특히 Log 클래스를 사용하여 Android 스튜디오 내 Logcat에 표시되는 짧은 메시지를 작성할 수 있습니다.
  • Log.d()를 사용하여 디버그 메시지를 작성합니다. 이 메서드는 두 가지 인수를 사용합니다. 로그 태그(일반적으로 클래스 이름)와 로그 메시지(짧은 문자열)입니다.
  • Android 스튜디오에서 Logcat 창을 사용하여 작성한 메시지를 비롯한 시스템 로그를 확인합니다.

구성 변경

  • 구성 변경은 기기 상태가 매우 급격하게 변경되어 시스템이 변경사항을 확인하는 가장 쉬운 방법이 활동을 소멸시키고 다시 빌드하는 것일 때 발생합니다.
  • 구성 변경은 사용자가 기기를 세로 모드에서 가로 모드로 또는 가로 모드에서 세로 모드로 회전할 때 가장 흔하게 발생합니다. 기기 언어가 변경되거나 사용자가 하드웨어 키보드를 연결할 때도 구성이 변경될 수 있습니다.
  • 구성 변경이 발생하면 Android는 모든 활동 수명 주기의 종료 콜백을 호출합니다. 그러면 Android는 활동을 처음부터 다시 시작하여 모든 수명 주기 시작 콜백을 실행합니다.
  • Android는 구성 변경으로 인해 앱을 종료할 때 onCreate()를 사용하여 활동을 다시 시작합니다.
  • 구성 변경 이후에도 효력을 유지해야 하는 값을 저장하려면 rememberSaveable을 사용해 변수를 선언합니다.

자세히 알아보기