이 Codelab은 Android Kotlin 기초 과정의 일부입니다. Codelab을 순서대로 진행하면 이 과정의 학습 효과를 극대화할 수 있습니다. 모든 과정 Codelab은 Android Kotlin 기본사항 Codelab 방문 페이지에 나열되어 있습니다.
소개
이전 Codelab에서는 Activity 및 Fragment 수명 주기를 알아보고 활동과 프래그먼트에서 수명 주기 상태가 변경될 때 호출되는 메서드를 살펴봤습니다. 이 Codelab에서는 활동 수명 주기를 자세히 알아봅니다. 또한 더 잘 정리되고 유지관리하기 쉬운 코드로 수명 주기 이벤트를 관리하는 데 도움이 되는 Android Jetpack의 수명 주기 라이브러리에 대해서도 알아봅니다.
기본 요건
- 활동의 정의와 앱에서 활동을 만드는 방법을 알아야 합니다.
Activity및Fragment수명 주기 기본사항과 활동이 상태 간에 이동할 때 호출되는 콜백onCreate()및onStop()수명 주기 콜백 메서드를 재정의하여 활동 또는 프래그먼트 수명 주기의 다양한 시점에 작업을 실행하는 방법
학습할 내용
- 수명 주기 콜백에서 앱의 일부를 설정, 시작, 중지하는 방법
- Android 수명 주기 라이브러리를 사용하여 수명 주기 관찰자를 만들고 활동 및 프래그먼트 수명 주기를 더 쉽게 관리하는 방법
- Android 프로세스 종료가 앱의 데이터에 미치는 영향과 Android에서 앱을 닫을 때 데이터를 자동으로 저장하고 복원하는 방법
- 기기 회전 및 기타 구성 변경이 수명 주기 상태를 변경하고 앱 상태에 영향을 미치는 방식
실행할 작업
- 타이머 기능을 포함하도록 DessertClicker 앱을 수정하고 활동 수명 주기의 다양한 시점에 타이머를 시작하고 중지합니다.
- Android 수명 주기 라이브러리를 사용하도록 앱을 수정하고
DessertTimer클래스를 수명 주기 관찰자로 변환합니다. - Android 디버그 브리지 (
adb)를 설정하고 사용하여 앱의 프로세스 종료와 이때 발생하는 수명 주기 콜백을 시뮬레이션합니다. onSaveInstanceState()메서드를 구현하여 앱이 예기치 않게 종료되면 손실될 수 있는 앱 데이터를 유지합니다. 앱이 다시 시작될 때 이 데이터를 복원하는 코드를 추가합니다.
이 Codelab에서는 이전 Codelab의 DessertClicker 앱을 확장합니다. 배경 타이머를 추가한 다음 Android 수명 주기 라이브러리를 사용하도록 앱을 변환합니다.

이전 Codelab에서는 다양한 수명 주기 콜백을 재정의하고 시스템에서 이러한 콜백을 호출할 때 로깅하여 활동 및 프래그먼트 수명 주기를 관찰하는 방법을 알아봤습니다. 이 작업에서는 DessertClicker 앱에서 수명 주기 작업을 관리하는 더 복잡한 예를 살펴봅니다. 1초마다 실행된 시간(초) 수를 로깅 문으로 출력하는 타이머를 사용합니다.
1단계: DessertTimer 설정하기
- 마지막 Codelab에서 DessertClicker 앱을 엽니다. (앱이 없는 경우 여기에서 DessertClickerLogs를 다운로드할 수 있습니다.)
- Project 뷰에서 java > com.example.android.dessertclicker를 펼치고
DessertTimer.kt를 엽니다. 현재 모든 코드가 주석 처리되어 있으므로 앱의 일부로 실행되지 않습니다. - 편집기 창에서 코드를 모두 선택합니다. 코드 > 행 주석으로 주석 처리를 선택하거나
Control+/(Mac의 경우Command+/)를 누릅니다. 이 명령어는 파일의 모든 코드에서 주석을 해제합니다. (앱을 다시 빌드할 때까지 Android 스튜디오에 해결되지 않은 참조 오류가 표시될 수 있습니다.) DessertTimer클래스에는 타이머를 시작하고 중지하는startTimer()및stopTimer()가 포함되어 있습니다.startTimer()가 실행되면 타이머는 시간이 실행된 총 초 수와 함께 매초 로그 메시지를 출력합니다.stopTimer()메서드는 타이머와 로그 문을 중지합니다.
MainActivity.kt를 엽니다. 클래스 상단에서dessertsSold변수 바로 아래에 타이머 변수를 추가합니다.
private lateinit var dessertTimer : DessertTimer;onCreate()까지 아래로 스크롤하여setOnClickListener()호출 바로 뒤에 새DessertTimer객체를 만듭니다.
dessertTimer = DessertTimer()
디저트 타이머 객체가 있으므로 활동이 화면에 표시될 때만 타이머가 실행되도록 타이머를 시작하고 중지해야 하는 위치를 고려하세요. 다음 단계에서 몇 가지 옵션을 살펴보겠습니다.
2단계: 타이머 시작 및 중지하기
onStart() 메서드는 활동이 표시되기 직전에 호출됩니다. onStop() 메서드는 활동이 더 이상 표시되지 않은 후 호출됩니다. 이러한 콜백은 타이머를 시작하고 중지할 시점을 파악하는 데 적합한 후보인 것 같습니다.
MainActivity클래스에서onStart()콜백의 타이머를 시작합니다.
override fun onStart() {
super.onStart()
dessertTimer.startTimer()
Timber.i("onStart called")
}onStop()에서 타이머 중지:
override fun onStop() {
super.onStop()
dessertTimer.stopTimer()
Timber.i("onStop Called")
}- 앱을 컴파일하고 실행합니다. Android 스튜디오에서 Logcat 창을 클릭합니다. Logcat 검색창에
dessertclicker를 입력하면MainActivity및DessertTimer클래스 모두를 기준으로 필터링됩니다. 앱이 시작되면 타이머도 즉시 실행됩니다.
- 뒤로 버튼을 클릭하면 타이머가 다시 중지됩니다. 활동과 활동이 제어하는 타이머가 모두 소멸되었기 때문에 타이머가 중지됩니다.
- 최근 화면을 사용하여 앱으로 돌아갑니다. Logcat에서 타이머가 0부터 다시 시작됩니다.
- 공유 버튼을 클릭합니다. Logcat에서 타이머가 계속 실행되고 있습니다.

- 홈 버튼을 클릭합니다. Logcat에서 타이머가 중지된 것을 확인할 수 있습니다.
- 최근 화면을 사용하여 앱으로 돌아갑니다. Logcat에서 타이머가 중단된 지점에서 다시 시작됩니다.
MainActivity의onStop()메서드에서stopTimer()호출을 주석 처리합니다.stopTimer()를 주석 처리하면onStart()에서 작업을 시작했지만onStop()에서 다시 중지하는 것을 잊은 사례가 표시됩니다.- 앱을 컴파일하고 실행한 다음 타이머가 시작되면 홈 버튼을 클릭합니다. 앱이 백그라운드에 있어도 타이머가 실행되고 시스템 리소스를 계속 사용합니다. 타이머가 계속 실행되면 앱의 메모리 누수가 발생하며 원하는 동작이 아닐 수 있습니다.
일반적인 패턴은 콜백에서 무언가를 설정하거나 시작할 때 해당 콜백에서 해당 항목을 중지하거나 삭제하는 것입니다. 이렇게 하면 더 이상 필요하지 않은 항목이 실행되지 않습니다.
- 타이머를 중지하는
onStop()의 줄에서 주석 처리를 삭제합니다. onStart()에서startTimer()호출을 잘라내어onCreate()에 붙여넣습니다. 이 변경사항은onCreate()를 사용하여 리소스를 초기화하고onStart()를 사용하여 리소스를 시작하는 대신onCreate()에서 리소스를 초기화하고 시작하는 사례를 보여줍니다.- 앱을 컴파일하고 실행합니다. 예상대로 타이머가 실행됩니다.
- 홈을 클릭하여 앱을 중지합니다. 예상대로 타이머가 중지됩니다.
- 최근 화면을 사용하여 앱으로 돌아갑니다. 이 경우 타이머가 다시 시작되지 않습니다.
onCreate()은 앱이 시작될 때만 호출되기 때문입니다. 앱이 포그라운드로 돌아올 때는 호출되지 않습니다.
기억해야 할 핵심 사항:
- 수명 주기 콜백에서 리소스를 설정할 때는 리소스를 해체하기도 합니다.
- 해당 메서드에서 설정 및 해제를 실행합니다.
onStart()에서 설정한 항목이 있으면onStop()에서 다시 중지하거나 해체하세요.
DessertClicker 앱에서는 onStart()에서 타이머를 시작한 경우 onStop()에서 타이머를 중지해야 한다는 것을 쉽게 알 수 있습니다. 타이머는 하나만 있으므로 타이머를 중지하는 것을 기억하기 어렵지 않습니다.
더 복잡한 Android 앱에서는 onStart() 또는 onCreate()에서 여러 항목을 설정한 다음 onStop() 또는 onDestroy()에서 모두 해체할 수 있습니다. 예를 들어 설정 및 해체, 시작 및 중지가 필요한 애니메이션, 음악, 센서 또는 타이머가 있을 수 있습니다. 하나라도 잊으면 버그와 문제가 발생합니다.
Android Jetpack의 일부인 수명 주기 라이브러리를 사용하면 이 작업이 간소화됩니다. 이 라이브러리는 특히 여러 이동 부분을 추적해야 하는 경우에 유용하며, 일부는 서로 다른 수명 주기 상태에 있습니다. 라이브러리는 수명 주기가 작동하는 방식을 뒤집습니다. 일반적으로 활동이나 프래그먼트는 수명 주기 콜백이 발생할 때 구성요소 (예: DessertTimer)에 해야 할 작업을 알려줍니다. 하지만 수명 주기 라이브러리를 사용하면 구성요소 자체가 수명 주기 변경을 모니터링한 다음 변경이 발생하면 필요한 작업을 실행합니다.
수명 주기 라이브러리에는 세 가지 주요 부분이 있습니다.
- 수명 주기를 보유하고 따라서 '소유'하는 구성요소인 수명 주기 소유자
Activity및Fragment는 수명 주기 소유자입니다. 수명 주기 소유자는LifecycleOwner인터페이스를 구현합니다. - 수명 주기 소유자의 실제 상태를 보유하고 수명 주기 변경이 발생할 때 이벤트를 트리거하는
Lifecycle클래스 - 수명 주기 상태를 관찰하고 수명 주기가 변경될 때 작업을 실행하는 수명 주기 관찰자 수명 주기 관찰자는
LifecycleObserver인터페이스를 구현합니다.
이 작업에서는 Android 수명 주기 라이브러리를 사용하도록 DessertClicker 앱을 변환하고 라이브러리를 사용하면 Android 활동 및 프래그먼트 수명 주기를 더 쉽게 관리할 수 있는 방법을 알아봅니다.
1단계: DessertTimer를 LifecycleObserver로 변환
수명 주기 라이브러리의 핵심 부분은 수명 주기 관찰 개념입니다. 관찰을 통해 클래스 (예: DessertTimer)가 활동 또는 프래그먼트 수명 주기를 파악하고 이러한 수명 주기 상태의 변경에 따라 자체적으로 시작 및 중지할 수 있습니다. 수명 주기 관찰자를 사용하면 활동 및 프래그먼트 메서드에서 객체를 시작하고 중지하는 책임을 삭제할 수 있습니다.
DesertTimer.kt클래스를 엽니다.DessertTimer클래스의 클래스 서명을 다음과 같이 변경합니다.
class DessertTimer(lifecycle: Lifecycle) : LifecycleObserver {이 새로운 클래스 정의는 다음 두 가지 작업을 실행합니다.
- 생성자는 타이머가 관찰하는 수명 주기인
Lifecycle객체를 가져옵니다. - 클래스 정의는
LifecycleObserver인터페이스를 구현합니다.
runnable변수 아래에 클래스 정의에init블록을 추가합니다.init블록에서addObserver()메서드를 사용하여 소유자 (활동)에서 전달된 수명 주기 객체를 이 클래스 (관찰자)에 연결합니다.
init {
lifecycle.addObserver(this)
}startTimer()에@OnLifecycleEvent annotation를 주석으로 달고ON_START수명 주기 이벤트를 사용합니다. 수명 주기 관찰자가 관찰할 수 있는 모든 수명 주기 이벤트는Lifecycle.Event클래스에 있습니다.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun startTimer() {ON_STOP이벤트를 사용하여stopTimer()에도 동일한 작업을 실행합니다.
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stopTimer()2단계: MainActivity 수정하기
FragmentActivity 슈퍼클래스가 LifecycleOwner를 구현하므로 MainActivity 클래스는 상속을 통해 이미 수명 주기 소유자입니다. 따라서 활동이 수명 주기를 인식하도록 하기 위해 별도로 취해야 할 조치는 없습니다. 활동의 수명 주기 객체를 DessertTimer 생성자에 전달하기만 하면 됩니다.
MainActivity를 엽니다.onCreate()메서드에서this.lifecycle를 포함하도록DessertTimer의 초기화를 수정합니다.
dessertTimer = DessertTimer(this.lifecycle)활동의 lifecycle 속성은 이 활동이 소유한 Lifecycle 객체를 보유합니다.
onCreate()의startTimer()호출과onStop()의stopTimer()호출을 삭제합니다. 이제DessertTimer가 수명 주기를 직접 관찰하고 수명 주기 상태가 변경되면 자동으로 알림을 받으므로 활동에서DessertTimer에 무엇을 해야 하는지 알려줄 필요가 없습니다. 이제 이러한 콜백에서 하는 일은 메시지를 로깅하는 것뿐입니다.- 앱을 컴파일하고 실행한 다음 Logcat을 엽니다. 타이머가 예상대로 실행되기 시작합니다.

- 홈 버튼을 클릭하여 앱을 백그라운드로 전환합니다. 예상대로 타이머가 중지되었습니다.
앱이 백그라운드에 있는 동안 Android에서 앱을 종료하면 앱과 데이터는 어떻게 되나요? 이 까다로운 특이 사례를 이해하는 것이 중요합니다.
앱이 백그라운드로 전환되면 소멸되지 않고 중지된 상태로 사용자가 앱으로 돌아올 때까지 대기합니다. 하지만 Android OS의 주요 관심사 중 하나는 포그라운드에 있는 활동을 원활하게 실행하는 것입니다. 예를 들어 사용자가 버스를 타는 데 도움이 되는 GPS 앱을 사용하고 있다면 GPS 앱을 빠르게 렌더링하고 길안내를 계속 표시하는 것이 중요합니다. 사용자가 며칠 동안 보지 않았을 수 있는 DessertClicker 앱이 백그라운드에서 원활하게 실행되도록 하는 것은 덜 중요합니다.
Android는 포그라운드 앱이 문제없이 실행될 수 있도록 백그라운드 앱을 조절합니다. 예를 들어 Android는 백그라운드에서 실행 중인 앱이 수행할 수 있는 처리량을 제한합니다.
Android가 앱 전체 프로세스를 종료하는 경우도 있는데 이 프로세스에는 앱과 관련된 모든 활동이 포함됩니다. Android는 시스템이 스트레스를 받고 시각적 지연의 위험이 있을 때 이와 같이 종료하므로 콜백이나 코드가 이 시점에서 추가로 실행되지 않습니다. 앱 프로세스는 백그라운드에서 자동으로 간단히 종료됩니다. 그러나 사용자에게는 앱이 닫힌 것처럼 보이지 않습니다. 사용자가 Android OS가 종료한 앱으로 다시 이동하면 Android는 앱을 다시 시작합니다.
이 작업에서는 Android 프로세스 종료를 시뮬레이션하고 앱이 다시 시작될 때 어떤 일이 발생하는지 살펴봅니다.
1단계: adb를 사용하여 프로세스 종료 시뮬레이션
Android 디버그 브리지 (adb)는 컴퓨터에 연결된 에뮬레이터와 기기에 명령어를 전송할 수 있는 명령줄 도구입니다. 이 단계에서는 adb를 사용하여 앱의 프로세스를 닫고 Android가 앱을 종료할 때 어떤 일이 발생하는지 확인합니다.
- 앱을 컴파일하고 실행합니다. 컵케이크를 몇 번 클릭합니다.
- 홈 버튼을 눌러 앱을 백그라운드로 전환합니다. 이제 앱이 중지되었으며 Android에서 앱이 사용하는 리소스가 필요한 경우 앱이 종료될 수 있습니다.
- Android 스튜디오에서 터미널 탭을 클릭하여 명령줄 터미널을 엽니다.

adb를 입력하고 Return 키를 누릅니다.Android Debug Bridge version X.XX.X로 시작하고tags to be used by logcat (see logcat —help)로 끝나는 출력이 많이 표시되면 모든 것이 정상입니다.adb: command not found가 표시되면 실행 경로에서adb명령어를 사용할 수 있는지 확인합니다. 자세한 내용은 유틸리티 챕터의 '실행 경로에 adb 추가'를 참고하세요.- 이 주석을 복사하여 명령줄에 붙여넣고 Return 키를 누릅니다.
adb shell am kill com.example.android.dessertclicker이 명령어는 연결된 기기나 에뮬레이터에 dessertclicker 패키지 이름으로 프로세스를 중지하라고 지시합니다. 단, 앱이 백그라운드에 있는 경우에만 해당합니다. 앱이 백그라운드에 있었기 때문에 프로세스가 중지되었음을 나타내는 항목이 기기나 에뮬레이터 화면에 표시되지 않습니다. Android 스튜디오에서 Run 탭을 클릭하면 'Application terminated'(애플리케이션이 종료됨)이라는 메시지가 표시됩니다. Logcat 탭을 클릭하면 onDestroy() 콜백이 실행되지 않았음을 알 수 있습니다. 활동이 종료되었기 때문입니다.
- 최근 화면을 사용하여 앱으로 돌아갑니다. 앱이 백그라운드로 전환되었든 완전히 중지되었든 최근에 표시됩니다. 최근 화면을 사용하여 앱으로 돌아가면 활동이 다시 시작됩니다. 활동은
onCreate()를 비롯한 전체 시작 수명 주기 콜백을 거칩니다. - 앱이 다시 시작되면 '점수' (판매된 디저트 수와 총 달러)가 기본값 (0)으로 재설정됩니다. Android에서 앱을 종료했는데 상태가 저장되지 않은 이유는 무엇인가요?
OS에서 앱을 다시 시작할 때 Android는 앱을 이전 상태로 재설정하려고 최선을 다합니다. 활동에서 벗어날 때마다 Android는 일부 뷰의 상태를 가져와 번들에 저장합니다. 자동으로 저장되는 데이터의 예로는 EditText의 텍스트 (레이아웃에 ID가 설정되어 있는 경우)와 활동의 백 스택이 있습니다.
하지만 Android OS가 모든 데이터를 알지는 못합니다. 예를 들어 DessertClicker 앱에revenue와 같은 맞춤 변수가 있는 경우 Android OS는 이 데이터나 활동에 대한 중요성을 알지 못합니다. 이 데이터는 직접 번들에 추가해야 합니다.
2단계: onSaveInstanceState()를 사용하여 번들 데이터 저장
onSaveInstanceState() 메서드는 Android OS에서 앱을 소멸할 때 필요할 수 있는 데이터를 저장하는 데 사용하는 콜백입니다. 수명 주기 콜백 다이어그램에서 onSaveInstanceState()는 활동이 중지된 후 호출됩니다. 또한 앱이 백그라운드로 전환될 때마다 호출됩니다.

onSaveInstanceState() 호출을 안전 조치라고 생각하세요. 활동이 포그라운드를 벗어날 때 소량의 정보를 번들에 저장할 수 있습니다. 이제 시스템은 이 데이터를 저장합니다. 앱이 종료될 때까지 기다리면 OS가 리소스 압력을 받을 수 있기 때문입니다. 매번 데이터를 저장하면 번들의 업데이트된 데이터를 필요에 따라 복원에 사용할 수 있습니다.
MainActivity에서onSaveInstanceState()콜백을 재정의하고Timber로그 구문을 추가합니다.
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.i("onSaveInstanceState Called")
}- 앱을 컴파일하고 실행한 다음 홈 버튼을 클릭하여 앱을 백그라운드로 전환합니다.
onSaveInstanceState()콜백은onPause()와onStop()바로 다음에 발생합니다.
- 파일 상단에서 클래스 정의 바로 앞에 다음 상수를 추가합니다.
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"이러한 키는 인스턴스 상태 번들에서 데이터를 저장하고 검색하는 데 사용합니다.
onSaveInstanceState()까지 아래로 스크롤하면outState매개변수가Bundle유형입니다.
번들은 키-값 쌍 모음으로, 키가 항상 문자열입니다.int및boolean값과 같은 기본 값을 번들에 넣을 수 있습니다.
시스템이 이 번들을 RAM에 유지하므로 번들의 데이터를 작게 유지하는 것이 좋습니다. 이 번들의 크기도 제한되지만 기기마다 크기는 다릅니다. 일반적으로 100k보다 훨씬 적게 저장해야 합니다. 그렇지 않으면TransactionTooLargeException오류로 인해 앱이 비정상 종료될 수 있습니다.onSaveInstanceState()에서revenue값 (정수)을putInt()메서드를 사용하여 번들에 넣습니다.
outState.putInt(KEY_REVENUE, revenue)putInt() 메서드와 putFloat() 및 putString()과 같은 Bundle 클래스의 유사한 메서드는 두 가지 인수를 사용합니다. 키 문자열(KEY_REVENUE 상수)과 저장할 실제 값입니다.
- 판매된 디저트 수와 타이머 상태로 동일한 프로세스를 반복합니다.
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)3단계: onCreate()를 사용하여 번들 데이터 복원
onCreate()까지 위로 스크롤하여 메서드 서명을 확인합니다.
override fun onCreate(savedInstanceState: Bundle) {onCreate()는 호출될 때마다 Bundle을 가져옵니다. 프로세스 종료로 인해 활동이 다시 시작되면 저장한 번들이 onCreate()에 전달됩니다. 활동이 새로 시작되었다면 onCreate()의 이 번들은 null입니다. 따라서 번들이 null이 아니면 이전에 알려진 지점에서 활동을 '다시 생성'하고 있음을 알 수 있습니다.
DessertTimer설정 후onCreate()에 다음 코드를 추가합니다.
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}null 테스트는 번들에 데이터가 있는지 또는 번들이 null인지 확인하므로 결과적으로 앱이 새로 시작되었는지 또는 종료 후 다시 만들어졌는지 알려줍니다. 이 테스트는 번들에서 데이터를 복원하는 일반적인 패턴입니다.
여기에서 사용한 키(KEY_REVENUE)는 putInt()에 사용한 키와 같습니다. 매번 같은 키를 사용하도록 하려면 이러한 키를 상수로 정의하는 것이 좋습니다. putInt()를 사용하여 데이터를 번들에 넣은 것처럼 getInt()를 사용하여 번들에서 데이터를 가져옵니다. getInt() 메서드는 두 가지 인수를 사용합니다.
- 키 역할을 하는 문자열(예: 수익 값의
"key_revenue") - 번들의 키에 값이 없는 경우를 위한 기본값
그러면 번들에서 가져온 정수가 revenue 변수에 할당되고 UI에서 이 값을 사용합니다.
getInt()메서드를 추가하여 판매된 디저트 수와 타이머 값을 복원합니다.
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}- 앱을 컴파일하고 실행합니다. 컵케이크가 도넛으로 바뀔 때까지 다섯 번 이상 누릅니다. 홈을 클릭하여 앱을 백그라운드로 전환합니다.
- Android 스튜디오 터미널 탭에서
adb를 실행하여 앱의 프로세스를 종료합니다.
adb shell am kill com.example.android.dessertclicker- 최근 화면을 사용하여 앱으로 돌아갑니다. 이번에는 앱이 번들에서 올바른 수익 및 판매된 디저트 값을 반환합니다. 그러나 디저트가 컵케이크로 돌아왔습니다. 앱이 정확히 종료된 상태 그대로 종료에서 돌아오도록 하기 위해 한 가지 작업이 더 남았습니다.
MainActivity에서showCurrentDessert()메서드를 확인합니다. 이 메서드는 현재 판매된 디저트 수와allDesserts변수의 디저트 목록에 기반하여 어떤 디저트 이미지를 활동에 표시할지 결정합니다.
for (dessert in allDesserts) {
if (dessertsSold >= dessert.startProductionAmount) {
newDessert = dessert
}
else break
}이 메서드는 판매된 디저트 수를 사용하여 올바른 이미지를 선택합니다. 따라서 onSaveInstanceState()의 번들에 이미지 참조를 저장하려고 별다른 작업을 하지 않아도 됩니다. 번들에 이미 판매된 디저트 수가 저장되고 있습니다.
onCreate()내 번들에서 상태를 복원하는 블록에서showCurrentDessert()를 호출합니다.
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
showCurrentDessert()
}- 앱을 컴파일하고 실행한 다음 백그라운드로 전환합니다.
adb를 사용하여 프로세스를 종료합니다. 최근 화면을 사용하여 앱으로 돌아갑니다. 이제 판매된 디저트 값과 총수익 값, 디저트 이미지가 올바르게 복원됩니다.
활동 및 프래그먼트 수명 주기 관리에서 파악해야 할 중요한 마지막 특수 사례로는 구성 변경이 활동 및 프래그먼트의 수명 주기에 미치는 영향이 있습니다.
구성 변경은 기기 상태가 매우 급격하게 변경되어 시스템이 변경사항을 확인하는 가장 쉬운 방법이 활동을 완전히 종료하고 다시 빌드하는 것일 때 발생합니다. 예를 들어 사용자가 기기 언어를 변경하면 다른 텍스트 방향을 수용하도록 전체 레이아웃을 변경해야 할 수 있습니다. 사용자가 기기를 도크에 연결하거나 물리적 키보드를 추가하면 앱 레이아웃은 다른 디스플레이 크기나 레이아웃을 활용해야 할 수 있습니다. 기기 방향이 변경되면(기기가 세로 모드에서 가로 모드로 또는 이와 반대로 회전하는 경우) 새 방향에 맞게 레이아웃을 변경해야 할 수 있습니다.
1단계: 기기 회전 및 수명 주기 콜백 살펴보기
- 앱을 컴파일하고 실행한 후 Logcat을 엽니다.
- 기기나 에뮬레이터를 가로 모드로 회전합니다. 회전 버튼을 사용하거나
Control과 화살표 키 (Mac은Command와 화살표 키)를 사용하여 에뮬레이터를 왼쪽이나 오른쪽으로 회전할 수 있습니다.
- Logcat에서 출력을 확인합니다.
MainActivity에서 출력을 필터링합니다.
기기나 에뮬레이터에서 화면이 회전되면 시스템은 모든 수명 주기 콜백을 호출하여 활동을 종료합니다. 그런 다음 활동이 다시 만들어질 때 시스템은 모든 수명 주기 콜백을 호출하여 활동을 시작합니다. MainActivity에서 전체onSaveInstanceState()메서드를 주석 처리합니다.- 앱을 다시 컴파일하고 실행합니다. 컵케이크를 몇 번 클릭하고 기기나 에뮬레이터를 회전합니다. 이번에는 기기가 회전되고 활동이 종료되고 다시 만들어지면 활동이 기본값으로 시작됩니다.
구성 변경이 발생하면 Android는 이전 작업에서 알아본 것과 동일한 인스턴스 상태 번들을 사용하여 앱의 상태를 저장하고 복원합니다. 프로세스 종료와 마찬가지로onSaveInstanceState()를 사용하여 앱의 데이터를 번들에 넣습니다. 그런 다음 기기가 회전될 때 활동 상태 데이터가 손실되지 않도록onCreate()에서 데이터를 복원합니다. MainActivity에서onSaveInstanceState()메서드의 주석을 해제하고 앱을 실행한 후 컵케이크를 클릭하고 앱이나 기기를 회전합니다. 이번에는 활동 회전 시 디저트 데이터가 유지됩니다.
Android 스튜디오 프로젝트: DessertClickerFinal
수명 주기 팁
- 수명 주기 콜백에서 무언가를 설정하거나 시작하는 경우 해당 콜백에서 중지하거나 삭제합니다. 사물을 중지하면 더 이상 필요하지 않을 때 계속 실행되지 않습니다. 예를 들어
onStart()에서 타이머를 설정한 경우onStop()에서 타이머를 일시중지하거나 중지해야 합니다. - 앱이 처음 시작될 때 한 번 실행되는 앱 부분을 초기화하는 데만
onCreate()를 사용하세요. 앱이 시작될 때와 앱이 포그라운드로 돌아올 때마다 실행되는 앱 부분을 시작하려면onStart()를 사용하세요.
수명 주기 라이브러리
- Android 수명 주기 라이브러리를 사용하여 수명 주기 제어를 활동이나 프래그먼트에서 수명 주기를 인식해야 하는 실제 구성요소로 이동합니다.
- 수명 주기 소유자는
Activity및Fragment를 비롯한 수명 주기를 보유하는 구성요소입니다. 수명 주기 소유자는LifecycleOwner인터페이스를 구현합니다. - 수명 주기 관찰자는 현재 수명 주기 상태에 주의를 기울이고 수명 주기가 변경될 때 작업을 실행합니다. 수명 주기 관찰자는
LifecycleObserver인터페이스를 구현합니다. Lifecycle객체는 실제 수명 주기 상태를 포함하며 수명 주기가 변경될 때 이벤트를 트리거합니다.
수명 주기 인식 클래스를 만들려면 다음 단계를 따르세요.
- 수명 주기를 인식해야 하는 클래스에서
LifecycleObserver인터페이스를 구현합니다. - 활동 또는 프래그먼트의 수명 주기 객체로 수명 주기 관찰자 클래스를 초기화합니다.
- 수명 주기 관찰자 클래스에서 관심 있는 수명 주기 상태 변경으로 수명 주기 인식 메서드에 주석을 답니다.
예를 들어@OnLifecycleEvent(Lifecycle.Event.ON_START)주석은 메서드가onStart수명 주기 이벤트를 모니터링하고 있음을 나타냅니다.
프로세스 종료 및 활동 상태 저장
- Android는 포그라운드 앱이 문제없이 실행될 수 있도록 백그라운드에서 실행되는 앱을 조절합니다. 이 규정에는 백그라운드에서 앱이 수행할 수 있는 처리량을 제한하는 내용이 포함되며, 때로는 전체 앱 프로세스를 종료하는 내용도 포함됩니다.
- 사용자는 시스템이 백그라운드에서 앱을 종료했는지 알 수 없습니다. 앱은 여전히 최근 화면에 표시되며 사용자가 종료한 상태와 동일한 상태로 다시 시작해야 합니다.
- Android 디버그 브리지 (
adb)는 컴퓨터에 연결된 에뮬레이터와 기기에 명령어를 전송할 수 있는 명령줄 도구입니다.adb를 사용하여 앱에서 프로세스 종료를 시뮬레이션할 수 있습니다. - Android에서 앱 프로세스를 종료하면
onDestroy()수명 주기 메서드가 호출되지 않습니다. 앱이 중지됩니다.
활동 및 프래그먼트 상태 유지
- 앱이 백그라운드로 전환되면
onStop()이 호출된 직후 앱 데이터가 번들에 저장됩니다.EditText의 콘텐츠와 같은 일부 앱 데이터는 자동으로 저장됩니다. - 번들은 키와 값의 모음인
Bundle의 인스턴스입니다. 키는 항상 문자열입니다. onSaveInstanceState()콜백을 사용하여 앱이 자동으로 종료된 경우에도 유지하려는 번들에 기타 데이터를 저장합니다. 번들에 데이터를 넣으려면put으로 시작하는 번들 메서드(예:putInt())를 사용합니다.onRestoreInstanceState()메서드 또는 더 일반적인onCreate()의 번들에서 데이터를 다시 가져올 수 있습니다.onCreate()메서드에는 번들을 보유하는savedInstanceState매개변수가 있습니다.savedInstanceState변수에null이 포함되어 있으면 활동이 상태 번들 없이 시작되어 검색할 상태 데이터가 없습니다.- 키를 사용하여 번들에서 데이터를 검색하려면
get으로 시작하는Bundle메서드(예:getInt())를 사용합니다.
구성 변경
- 구성 변경은 기기 상태가 매우 급격하게 변경되어 시스템이 변경사항을 확인하는 가장 쉬운 방법이 활동을 종료하고 다시 빌드하는 것일 때 발생합니다.
- 구성 변경은 사용자가 기기를 세로 모드에서 가로 모드로 또는 가로 모드에서 세로 모드로 회전할 때 가장 흔하게 발생합니다. 기기 언어가 변경되거나 하드웨어 키보드가 연결될 때도 구성 변경이 발생할 수 있습니다.
- 구성 변경이 발생하면 Android는 모든 활동 수명 주기의 종료 콜백을 호출합니다. 그런 다음 Android는 처음부터 활동을 다시 시작하여 모든 수명 주기 시작 콜백을 실행합니다.
- Android는 구성 변경으로 인해 앱을 종료할 때
onCreate()에서 사용할 수 있는 상태 번들로 활동을 다시 시작합니다. - 프로세스 종료와 마찬가지로 앱 상태를
onSaveInstanceState()의 번들에 저장합니다.
Udacity 과정:
Android 개발자 문서:
- 활동 (API 가이드)
Activity(API 참조)- Activity 수명 주기에 관한 이해
- 수명 주기 인식 구성요소로 수명 주기 처리
LifecycleOwnerLifecycleLifecycleObserveronSaveInstanceState()- 구성 변경 처리
- UI 상태 저장
기타:
- Timber (GitHub)
이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 진행하는 학생에게 출제할 수 있는 과제가 나열되어 있습니다. 다음 작업은 강사가 결정합니다.
- 필요한 경우 과제를 할당합니다.
- 과제 제출 방법을 학생에게 알립니다.
- 과제를 채점합니다.
강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 적절하다고 생각되는 다른 과제를 출제해도 됩니다.
이 Codelab을 직접 진행하는 경우 이러한 과제를 자유롭게 사용하여 배운 내용을 테스트해 보세요.
앱 변경하기
1단원의 DiceRoller 앱을 엽니다. (앱이 없는 경우 여기에서 다운로드할 수 있습니다.) 앱을 컴파일하고 실행하면 기기를 회전할 때 주사위의 현재 값이 손실됩니다. onSaveInstanceState()를 구현하여 번들에 값을 유지하고 onCreate()에서 값을 복원합니다.
질문에 답하세요
질문 1
앱에 물리 시뮬레이션이 포함되어 있는데 이를 표시하기 위해서는 과도한 컴퓨팅이 필요합니다. 그런데 사용자가 전화를 받습니다. 다음 중 올바른 설명은 무엇인가요?
- 전화 통화 중에도 물리 시뮬레이션에서는 계속해서 객체의 위치를 계산해야 합니다.
- 전화 통화 중에는 물리 시뮬레이션에서 객체 위치 계산을 중지해야 합니다.
질문 2
앱이 화면에 없을 때 시뮬레이션을 일시중지하려면 어떤 수명 주기 메서드를 재정의해야 하나요?
onDestroy()onStop()onPause()onSaveInstanceState()
질문 3
Android 수명 주기 라이브러리를 통해 클래스가 수명 주기를 인식하도록 하려면 클래스에서 어떤 인터페이스를 구현해야 하나요?
LifecycleLifecycleOwnerLifecycle.EventLifecycleObserver
질문 4
활동의 onCreate() 메서드는 어떤 상황에서 데이터가 있는 Bundle (즉, null이 아닌 Bundle)을 수신하나요? 답이 두 개 이상일 수 있습니다.
- 활동은 기기가 회전된 후에 다시 시작됩니다.
- 활동이 처음부터 시작됩니다.
- 활동은 백그라운드에서 반환된 후에 재개됩니다.
- 기기가 재부팅되었습니다.
다음 강의인
이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.