활동 수명 주기 단계

소개

이 Codelab에서는 Android의 기초 부분인 활동에 관해 자세히 알아봅니다. 활동 수명 주기는 활동의 전체 기간 중 일련의 액티비티 상태입니다. 수명 주기는 활동이 생성되는 시점에 시작하여 활동이 소멸되어 시스템에서 활동 리소스가 회수될 때까지 이어집니다. 사용자는 앱에서 활동 간에 이동하므로(앱 안팎으로) 이러한 활동은 각각 활동 수명 주기의 다양한 상태로 전환됩니다.

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

기본 요건

  • 활동의 정의와 앱에서 활동을 만드는 방법을 알아야 합니다.
  • 활동의 onCreate() 메서드의 기능과 이 메서드에서 실행되는 작업의 종류를 알아야 합니다.

학습할 내용

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

실행할 작업

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

이 Codelab에서는 DessertClicker라는 시작 앱을 사용합니다. 이 앱에서는 사용자가 화면에서 디저트를 탭할 때마다 앱이 사용자를 위해 디저트를 '구매'합니다. 앱은 구매한 디저트 수와 사용자가 소비한 총 금액에 관해 레이아웃에서 값을 업데이트합니다.

8216c20f5571fc04.png

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

시작 앱 다운로드

DessertClicker 시작 코드를 다운로드하여 Android 스튜디오에서 엽니다.

GitHub의 시작 코드를 사용하는 경우 폴더 이름은 android-basics-kotlin-dessert-clicker-app-starter입니다. Android 스튜디오에서 프로젝트를 열 때 이 폴더를 선택합니다.

이 Codelab의 코드를 가져와서 Android 스튜디오에서 열려면 다음을 실행합니다.

코드 가져오기

  1. 제공된 URL을 클릭합니다. 브라우저에서 프로젝트의 GitHub 페이지가 열립니다.
  2. 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 대화상자를 엽니다.

Eme2bJP46u-pMpnXVfm-bS2N2dlyq6c0jn1DtQYqBaml7TUhzXDWpYoDI0lGKi4xndE_uJw8sKfwfOZ1fC503xCVZrbh10JKJ4iEHdLDwFfdvnOheNxkokITW1LW6UZTncVJJUZ5Fw

  1. 대화상자에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
  2. 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
  3. ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.

Android 스튜디오에서 프로젝트 열기

  1. Android 스튜디오를 시작합니다.
  2. Welcome to Android Studio 창에서 Open an existing Android Studio project를 클릭합니다.

Tdjf5eS2nCikM9KdHgFaZNSbIUCzKXP6WfEaKVE2Oz1XIGZhgTJYlaNtXTHPFU1xC9pPiaD-XOPdIxVxwZAK8onA7eJyCXz2Km24B_8rpEVI_Po5qlcMNN8s4Tkt6kHEXdLQTDW7mg

참고: Android 스튜디오가 이미 열려 있는 경우 File > New > Import Project 메뉴 옵션을 대신 선택합니다.

PaMkVnfCxQqSNB1LxPpC6C6cuVCAc8jWNZCqy5tDVA6IO3NE2fqrfJ6p6ggGpk7jd27ybXaWU7rGNOFi6CvtMyHtWdhNzdAHmndzvEdwshF_SG24Le01z7925JsFa47qa-Q19t3RxQ

  1. Import Project 대화상자에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
  2. 프로젝트 폴더를 더블클릭합니다.
  3. Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
  4. Run 버튼 j7ptomO2PEQNe8jFt4nKCOw_Oc_Aucgf4l_La8fGLCMLy0t9RN9SkmBFGOFjkEzlX4ce2w2NWq4J30sDaxEe4MaSNuJPpMgHxnsRYoBtIV3-GUpYYcIvRJ2HrqR27XGuTS4F7lKCzg을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.
  5. Project 도구 창에서 프로젝트 파일을 살펴보고 앱이 구현된 방식을 확인합니다.

모든 활동에는 수명 주기가 있습니다. 이는 식물이나 동물의 수명 주기와 비슷한 개념으로, 아래와 같은 나비의 수명 주기를 예로 들 수 있습니다. 나비는 부화하여 성충이 되어 죽음에 이르기까지 여러 단계를 거칩니다.

c685f48ff799f0c9.png

마찬가지로 활동 수명 주기도 활동이 처음 초기화될 때부터 마지막으로 소멸되어 시스템에서 메모리를 회수할 때까지 활동이 거쳐 갈 수 있는 여러 상태로 구성됩니다. 사용자가 앱을 시작하여 활동 간에 이동하고 앱 안팎으로 이동할 때 활동은 상태를 변경합니다. 아래 다이어그램은 모든 활동 수명 주기 상태를 보여줍니다. 이름에서 알 수 있듯이 이러한 상태는 활동의 상태를 나타냅니다.

c803811f4cb4034b.png

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

f6b25a71cec4e401.png

이러한 콜백이 호출되는 시점과 각 콜백 메서드에서 할 작업을 알아야 합니다. 그러나 이 두 다이어그램은 복잡하고 혼란스러울 수 있습니다. 이 Codelab에서는 각 상태와 콜백의 의미를 단순하게 알아보는 대신 철저히 탐구하여 진행되는 상황을 더 잘 파악할 수 있도록 합니다.

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

Android 수명 주기에서 어떤 일이 일어나는지 파악하려면 다양한 수명 주기 메서드가 호출되는 시점을 아는 것이 좋습니다. 이렇게 하면 DessertClicker에서 문제가 발생하는 위치를 파악할 수 있습니다.

Android 로깅 기능을 사용하면 쉽게 이 작업을 할 수 있습니다. 로깅을 사용하면 앱이 실행되는 동안 짧은 메시지를 콘솔에 쓸 수 있고 이를 사용하여 다양한 콜백이 트리거될 때를 표시할 수 있습니다.

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

전에 이 콜백을 사용했으므로 활동 수명 주기 다이어그램에서 onCreate() 메서드를 인식했을 수 있습니다. 모든 활동에서 구현해야 하는 메서드가 바로 이 메서드입니다. onCreate() 메서드에서 활동의 일회성 초기화를 실행해야 합니다. 예를 들어 onCreate()에서 레이아웃을 확장하거나 클릭 리스너를 정의하거나 뷰 결합을 설정합니다.

9be2255ff49e0af8.png

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

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

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

이 명령어에는 세 가지 부분이 있습니다.

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

컴파일 시간 상수는 변경되지 않는 값입니다. 변수 선언 앞에 const를 사용하여 컴파일 시간 상수로 표시합니다.

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

ff9c50376701877f.png

  1. Logcat 창의 검색창에 D/MainActivity를 입력합니다.

bb0b78600cd47789.png

Logcat에는 많은 메시지가 포함될 수 있지만 대부분은 유용하지 않습니다. Logcat 항목을 다양한 방식으로 필터링할 수 있지만 검색이 가장 쉬운 방법입니다. 코드에서 MainActivity를 로그 태그로 사용했으므로 이 태그를 사용하여 로그를 필터링할 수 있습니다. 시작 부분에 D/를 추가하면 Log.d()로 만든 디버그 메시지임을 나타냅니다.

로그 메시지에는 날짜 및 시간, 패키지 이름(com.example.android.dessertclicker), 로그 태그(시작 부분에 D/ 포함), 실제 메시지가 포함됩니다. 이 메시지가 로그에 표시되므로 onCreate()가 실행되었음을 알 수 있습니다.

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

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

385df4ce82ae2de9.png

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

  1. Android 스튜디오에서 MainActivity.kt가 열려 있고 MainActivity 클래스 내 커서가 있는 상태에서 Code > Override Methods를 선택하거나 Control+o(Mac은 Command+o)를 누릅니다. 이 클래스에서 재정의할 수 있는 모든 메서드의 긴 목록이 있는 대화상자가 표시됩니다. e1f2460242b2ae.png
  2. onStart를 입력하여 올바른 메서드를 검색합니다. 일치하는 다음 항목으로 스크롤하려면 아래쪽 화살표를 사용합니다. 목록에서 onStart()를 선택하고 OK를 클릭하여 상용구 재정의 코드를 삽입합니다. 코드는 다음과 같습니다.
override fun onStart() {
   super.onStart()
}
  1. 클래스 선언 class MainActivity. 위에 있는 MainActivity.kt의 최상위 수준에 다음 상수를 추가합니다.
const val TAG = "MainActivity"
  1. onStart() 메서드 내에서 로그 메시지를 추가합니다.
override fun onStart() {
   super.onStart()
   Log.d(TAG, "onStart Called")
}
  1. DessertClicker 앱을 컴파일하고 실행한 다음 Logcat 창을 엽니다. 검색창에 D/MainActivity를 입력하여 로그를 필터링합니다. onCreate() 메서드와 onStart() 메서드가 모두 차례로 호출된 후 활동이 화면에 표시됩니다.
  2. 기기에서 홈 버튼을 누른 다음 최근 화면을 사용하여 활동으로 돌아갑니다. 활동은 모두 동일한 값으로 중단된 지점에서 다시 시작되고 onStart()는 Logcat에 두 번째로 기록됩니다. onCreate() 메서드는 일반적으로 다시 호출되지 않습니다.
16:19:59.125 31107-31107/com.example.android.dessertclicker D/MainActivity: onCreate Called
16:19:59.372 31107-31107/com.example.android.dessertclicker D/MainActivity: onStart Called
16:20:11.319 31107-31107/com.example.android.dessertclicker D/MainActivity: onStart Called

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

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

  1. MainActivity에서 나머지 수명 주기 메서드를 재정의하고 메서드마다 로그 구문을 추가합니다. 코드는 다음과 같습니다.
override fun onResume() {
   super.onResume()
   Log.d(TAG, "onResume 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")
}

override fun onRestart() {
   super.onRestart()
   Log.d(TAG, "onRestart Called")
}
  1. DessertClicker를 다시 컴파일하고 실행하여 Logcat을 확인합니다. 이번에는 onCreate()onStart() 외에도 onResume() 수명 주기 콜백의 로그 메시지가 있습니다.
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

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

  • onCreate() - 앱을 만듭니다.
  • onStart() - 활동을 시작하고 화면에 표시되게 합니다.
  • onResume() - 활동 포커스를 제공하고 사용자가 상호작용할 수 있도록 활동을 준비합니다.

이름에도 불구하고 onResume() 메서드는 다시 시작할 대상이 없어도 시작 시 호출됩니다.

160054d59f67519.png

이제 DessertClicker 앱이 로깅에 설정되었으므로 다양한 방식으로 앱을 사용할 수 있고 이러한 사용에 응답하여 수명 주기 콜백이 트리거되는 방식을 탐색할 수 있습니다.

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

앱을 처음 시작하여 완전히 닫는 가장 기본적인 사용 사례부터 시작합니다.

  1. DessertClicker 앱이 아직 실행되고 있지 않다면 앱을 컴파일하고 실행합니다. 확인했듯이 활동이 처음 시작되면 onCreate(), onStart(), onResume() 콜백이 호출됩니다.
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called
  1. 컵케이크를 몇 번 탭합니다.
  2. 기기에서 뒤로 버튼을 탭합니다. Logcat에서 onPause(), onStop(), onDestroy()가 순서대로 호출됩니다.
2020-10-16 10:31:53.850 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 10:31:54.620 22064-22064/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 10:31:54.622 22064-22064/com.example.android.dessertclicker D/MainActivity: onDestroy Called

이 경우 뒤로 버튼을 사용하면 활동 및 앱이 완전히 닫힙니다. onDestroy() 메서드의 실행은 활동이 완전히 종료되었으며 가비지 컬렉션될 수 있음을 의미합니다. 가비지 컬렉션은 더 이상 사용하지 않을 객체의 자동 정리를 나타냅니다. onDestroy()가 호출되면 시스템은 이러한 리소스가 삭제될 수 있음을 인식하고 메모리 정리를 시작합니다. 2dcc4d9c6478a9f4.png 활동은 코드에서 수동으로 활동의 finish() 메서드를 호출하거나 사용자가 앱을 강제 종료하는 경우에도 완전히 종료될 수 있습니다. 예를 들어 사용자는 최근 화면에서 앱을 강제 종료하거나 닫을 수 있습니다. Android 시스템은 앱이 오랫동안 화면에 표시되지 않으면 자체적으로 활동을 종료할 수도 있습니다. Android는 배터리를 보존하고 앱의 리소스를 다른 앱에서 사용할 수 있도록 이 작업을 실행합니다.

  1. 개요 화면에서 열려 있는 모든 앱을 찾아 DessertClicker 앱으로 돌아갑니다. 최근 화면 또는 최근 앱이라고도 합니다. Logcat은 다음과 같습니다.
2020-10-16 10:31:54.622 22064-22064/com.example.android.dessertclicker D/MainActivity: onDestroy Called
2020-10-16 10:38:00.733 22064-22064/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 10:38:00.787 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:38:00.788 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

이전 단계에서 활동이 소멸되었으므로 앱으로 돌아가면 Android는 새 활동을 시작하고 onCreate(), onStart(), onResume() 메서드를 호출합니다. 이전 활동의 DessertClicker 로그가 유지되지 않았습니다.

onCreate() 메서드는 중요한 단계입니다. 여기서 첫 초기화가 모두 이루어지고 레이아웃을 확장하여 처음으로 레이아웃을 설정하며 변수를 초기화합니다.

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

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

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

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

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

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

  1. DessertClicker 앱이 실행되는 상태에서 컵케이크를 몇 번 클릭합니다.
  2. 기기에서 버튼을 누르고 Android 스튜디오에서 Logcat을 관찰합니다. 홈 화면으로 돌아오면 앱이 완전히 종료되는 대신 백그라운드로 전환됩니다. onPause() 메서드와 onStop() 메서드가 호출되지만 onDestroy()는 호출되지 않습니다.
2020-10-16 10:41:05.383 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 10:41:05.966 22064-22064/com.example.android.dessertclicker D/MainActivity: onStop Called

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

  1. 최근 화면을 사용하여 앱으로 돌아갑니다. Logcat에서 활동은 onRestart()onStart()로 다시 시작된 후 onResume()으로 재개됩니다.
2020-10-16 10:42:18.144 22064-22064/com.example.android.dessertclicker D/MainActivity: onRestart Called
2020-10-16 10:42:18.158 22064-22064/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 10:42:18.158 22064-22064/com.example.android.dessertclicker D/MainActivity: onResume Called

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

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

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

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

이제 onRestart()를 보겠습니다. onRestart() 메서드는 onCreate()와 매우 유사합니다. onCreate()onRestart()는 활동이 표시되기 전에 호출됩니다. onCreate() 메서드는 처음에만 호출되고 onRestart()는 그 후에 호출됩니다. onRestart() 메서드는 활동이 처음으로 시작되지 않은 경우에만 호출하려는 코드를 배치하는 위치입니다.

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

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

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

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

  1. DessertClicker 앱이 실행되는 상태에서 화면 오른쪽 상단에 있는 공유 버튼을 클릭합니다.
  2. 공유 활동은 화면 아래쪽 절반에 표시되지만 활동은 계속 위쪽 절반에 표시됩니다. e2319779260eb5ee.png

9ddc8b1dc79b1bff.png

  1. Logcat을 확인하면 onPause()만 호출되었습니다.
2020-10-16 11:00:53.857 22064-22064/com.example.android.dessertclicker D/MainActivity: onPause Called

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

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

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

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

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

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

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

기기 회전 시 데이터 손실

  1. 앱을 컴파일하고 실행한 후 Logcat을 엽니다.
  2. 기기나 에뮬레이터를 가로 모드로 회전합니다. 회전 버튼을 사용하거나 Control과 화살표 키(Mac은 Command와 화살표 키)를 사용하여 에뮬레이터를 왼쪽이나 오른쪽으로 회전할 수 있습니다. 623fce7c623d42bd.png
  3. Logcat에서 출력을 확인합니다. MainActivity에서 출력을 필터링합니다.
2020-10-16 11:03:09.618 23206-23206/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 11:03:09.806 23206-23206/com.example.android.dessertclicker D/MainActivity: onStart Called
2020-10-16 11:03:09.808 23206-23206/com.example.android.dessertclicker D/MainActivity: onResume Called
2020-10-16 11:03:24.488 23206-23206/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 11:03:24.490 23206-23206/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 11:03:24.493 23206-23206/com.example.android.dessertclicker D/MainActivity: onDestroy Called
2020-10-16 11:03:24.520 23206-23206/com.example.android.dessertclicker D/MainActivity: onCreate Called
2020-10-16 11:03:24.569 23206-23206/com.example.android.dessertclicker D/MainActivity: onStart Called

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

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

onSaveInstanceState()를 사용하여 번들 데이터 저장

onSaveInstanceState() 메서드는 Activity가 소멸되면 필요할 수 있는 데이터를 저장하는 데 사용하는 콜백입니다. 수명 주기 콜백 다이어그램에서 onSaveInstanceState()는 활동이 중지된 후 호출됩니다. 또한 앱이 백그라운드로 전환될 때마다 호출됩니다.

c259ab6beca0ca88.png

onSaveInstanceState() 호출을 안전 조치라고 생각하세요. 활동이 포그라운드를 벗어날 때 소량의 정보를 번들에 저장할 수 있습니다. 이제 시스템은 이 데이터를 저장합니다. 앱이 종료될 때까지 기다리면 시스템이 리소스 압력을 받을 수 있기 때문입니다.

매번 데이터를 저장하면 번들의 업데이트된 데이터를 필요에 따라 복원에 사용할 수 있습니다.

  1. MainActivity에서 onSaveInstanceState() 콜백을 재정의하고 로그 구문을 추가합니다.
override fun onSaveInstanceState(outState: Bundle) {
   super.onSaveInstanceState(outState)

   Log.d(TAG, "onSaveInstanceState Called")
}
  1. 앱을 컴파일하고 실행한 다음 버튼을 클릭하여 앱을 백그라운드로 전환합니다. onSaveInstanceState() 콜백은 onPause()onStop() 바로 다음에 발생합니다.
2020-10-16 11:05:21.726 23415-23415/com.example.android.dessertclicker D/MainActivity: onPause Called
2020-10-16 11:05:22.382 23415-23415/com.example.android.dessertclicker D/MainActivity: onStop Called
2020-10-16 11:05:22.393 23415-23415/com.example.android.dessertclicker D/MainActivity: onSaveInstanceState Called
  1. 파일 상단에서 클래스 정의 바로 앞에 다음 상수를 추가합니다.
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"

이러한 키는 인스턴스 상태 번들에서 데이터를 저장하고 검색하는 데 사용합니다.

  1. onSaveInstanceState()까지 아래로 스크롤하면 outState 매개변수가 Bundle 유형입니다.

Bundle은 키-값 쌍 모음으로, 키가 항상 문자열입니다. IntBoolean 값과 같은 간단한 데이터를 번들에 넣을 수 있습니다. 시스템이 이 번들을 메모리에 유지하므로 번들의 데이터를 작게 유지하는 것이 좋습니다. 이 번들의 크기도 제한되지만 기기마다 크기는 다릅니다. 너무 많은 데이터를 저장하면 TransactionTooLargeException 오류로 인해 앱이 비정상 종료될 수 있습니다. 5. onSaveInstanceState()에서 revenue 값(정수)을 putInt() 메서드를 사용하여 번들에 넣습니다.

outState.putInt(KEY_REVENUE, revenue)

putInt() 메서드와 putFloat()putString()과 같은 Bundle 클래스의 유사한 메서드는 두 가지 인수를 사용합니다. 키 문자열(KEY_REVENUE 상수)과 저장할 실제 값입니다.

  1. 판매된 디저트 수로 동일한 프로세스를 반복합니다.
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)

onCreate()를 사용하여 번들 데이터 복원

액티비티 상태는 onCreate(Bundle)이나 onRestoreInstanceState(Bundle)에서 복원할 수 있습니다. onSaveInstanceState() 메서드로 채워진 Bundle은 두 수명 주기 콜백 메서드에 모두 전달됩니다.

  1. onCreate()까지 위로 스크롤하여 메서드 서명을 확인합니다.
override fun onCreate(savedInstanceState: Bundle) {

onCreate()는 호출될 때마다 Bundle을 가져옵니다. 프로세스 종료로 인해 활동이 다시 시작되면 저장한 번들이 onCreate()에 전달됩니다. 활동이 새로 시작되었다면 onCreate()의 이 Bundlenull입니다. 따라서 번들이 null이 아니면 이전에 알려진 지점에서 활동을 '다시 생성'하고 있음을 알 수 있습니다.

  1. binding 변수가 설정된 직후에 다음 코드를 onCreate()에 추가합니다.
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}

null 테스트는 번들에 데이터가 있는지 또는 번들이 null인지 확인하므로 결과적으로 앱이 새로 시작되었는지 또는 종료 후 다시 만들어졌는지 알려줍니다. 이 테스트는 번들에서 데이터를 복원하는 일반적인 패턴입니다.

여기에서 사용한 키(KEY_REVENUE)는 putInt()에 사용한 키와 같습니다. 매번 같은 키를 사용하도록 하려면 이러한 키를 상수로 정의하는 것이 좋습니다. putInt()를 사용하여 데이터를 번들에 넣은 것처럼 getInt()를 사용하여 번들에서 데이터를 가져옵니다. getInt() 메서드는 두 가지 인수를 사용합니다.

  • 키 역할을 하는 문자열(예: 수익 값의 "key_revenue")
  • 번들의 키에 값이 없는 경우를 위한 기본값

그러면 번들에서 가져온 정수가 revenue 변수에 할당되고 UI에서 이 값을 사용합니다.

  1. getInt() 메서드를 추가하여 수익과 판매된 디저트 수를 복원합니다.
if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
}
  1. 앱을 컴파일하고 실행합니다. 컵케이크가 도넛으로 바뀔 때까지 다섯 번 이상 누릅니다.
  2. 기기를 회전합니다. 이번에는 앱이 번들에서 올바른 수익 및 판매된 디저트 값을 표시합니다. 그러나 디저트가 컵케이크로 돌아왔습니다. 4179956182ffc634.png 앱이 정확히 종료된 상태 그대로 종료에서 돌아오도록 하기 위해 한 가지 작업이 더 남았습니다.
  3. MainActivity에서 showCurrentDessert() 메서드를 확인합니다. 이 메서드는 현재 판매된 디저트 수와 allDesserts 변수의 디저트 목록에 기반하여 어떤 디저트 이미지를 활동에 표시할지 결정합니다.
for (dessert in allDesserts) {
   if (dessertsSold >= dessert.startProductionAmount) {
       newDessert = dessert
   }
    else break
}

이 메서드는 판매된 디저트 수를 사용하여 올바른 이미지를 선택합니다. 따라서 onSaveInstanceState()의 번들에 이미지 참조를 저장하려고 별다른 작업을 하지 않아도 됩니다. 번들에 이미 판매된 디저트 수가 저장되고 있습니다.

  1. onCreate() 내 번들에서 상태를 복원하는 블록에서 showCurrentDessert()를 호출합니다.
 if (savedInstanceState != null) {
   revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
   dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
   showCurrentDessert()
}
  1. 앱을 컴파일하고 실행한 다음 화면을 회전합니다. 이제 판매된 디저트 값과 총 수익 값, 디저트 이미지가 올바르게 복원됩니다.

활동 수명 주기

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

로그로 로깅

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

액티비티 상태 유지

  • 앱이 백그라운드로 전환되면 onStop()이 호출된 직후 앱 데이터를 번들에 저장할 수 있습니다. EditText의 콘텐츠와 같은 일부 앱 데이터는 자동으로 저장됩니다.
  • 번들은 키와 값의 모음인 Bundle의 인스턴스입니다. 키는 항상 문자열입니다.
  • onSaveInstanceState() 콜백을 사용하여 앱이 자동으로 종료된 경우에도 유지하려는 번들에 기타 데이터를 저장합니다. 번들에 데이터를 넣으려면 put으로 시작하는 번들 메서드(예: putInt())를 사용합니다.
  • onRestoreInstanceState() 메서드 또는 더 일반적인 onCreate()의 번들에서 데이터를 다시 가져올 수 있습니다. onCreate() 메서드에는 번들을 보유하는 savedInstanceState 매개변수가 있습니다.
  • savedInstanceState 변수가 null이면 활동이 상태 번들 없이 시작되어 검색할 상태 데이터가 없습니다.
  • 키를 사용하여 번들에서 데이터를 검색하려면 get으로 시작하는 Bundle 메서드(예: getInt())를 사용합니다.

구성 변경

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