안드로이드에서 라이프 사이클은 라이프 사이클을 갖는 컴포넌트(액티비티, 프래그먼트)가 처음 초기화될 때부터 마지막으로 파괴되고 시스템이 메모리를 회수할 때까지 컴포넌트가 거칠 수 있는 다양한 상태를 말합니다.
안드로이드에서는 이러한 다양한 상태에 맞는 콜백 메서드가 정의되어있습니다. 우리는 우리가 만든 컴포넌트들에서 이러한 콜백 메서드를 재정의하여 사용합니다. 다음의 다이어그램은 재정의 가능한 콜백 메서드와 상태를 보여줍니다.
참고: 액티비티가 Stop되면 시스템은 해당 액티비티가 포함된 프로세스를 소멸시킬 수 있습니다(시스템이 메모리를 복구해야 하는 경우). 활동이 Stop된 동안 시스템이 프로세스를 소멸시키더라도 Bundle(키-값 쌍의 blob)에 있는 View 객체(예: EditText 위젯의 텍스트) 상태가 그대로 유지되고, 사용자가 이 액티비티로 돌아오면 이를 복원합니다. 사용자가 돌아올 때의 활동 복원에 관한 자세한 내용은 활동 상태 저장 및 복원을 참조하세요.
onDestroy()는 액티비티가 소멸되기 전에 호출됩니다. 시스템은 다음 중 하나에 해당할 때 이 콜백을 호출합니다.
onDestroy() 콜백은 이전의 콜백(ex. onStop())에서 아직 해제되지 않은 모든 리소스를 해제해야 합니다.
어떤 빌드업 이벤트에서 초기화 작업을 실행하든 빌드업 이벤트에 상응하는 수명 주기 이벤트를 사용하여 리소스를 해제하세요. ON_START 이벤트 이후에 무언가를 초기화하는 경우, ON_STOP 이벤트 이후에 이를 해제하거나 종료하세요. ON_RESUME 이벤트 이후에 초기화하는 경우, ON_PAUSE 이벤트 이후에 해제하세요.
아래의 코드스니펫은 카메라 초기화 코드를 수명 주기 인식 구성요소에 넣습니다. 이 방법 대신 활동 수명 주기 콜백(예: onStart(), onStop())을 직접 넣을 수는 있지만 권장하지는 않습니다. 이 로직을 독립적인 수명 주기 인식 구성요소에 넣으면 코드를 복사하지 않고도 여러 활동에서 구성요소를 다시 사용할 수 있습니다. 수명 주기 인식 구성요소를 생성하는 방법에 대해 알아보려면 수명 주기 인식 구성요소로 수명 주기 처리를 참조하세요.
class CameraComponent : LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun initializeCamera() {
if (camera == null) {
getCamera()
}
}
...
}
Android 프래그먼트 수명 주기는 Activity 수명 주기와 유사하며 여러 프래그먼트별 메서드가 있습니다.
// 처음 앱 시작시
navigation I/MainActiviy: onCreate Called
navigation I/TitleFragment: onAttach called
navigation I/TitleFragment: onCreate called
navigation I/TitleFragment: onCreateView called
navigation I/TitleFragment: onViewCreated called
navigation I/TitleFragment: onStart called
navigation I/MainActivity: onStart Called
navigation I/MainActivity: onResume Called
navigation I/TitleFragment: onResume called
// 다른 프래그먼트로 이동시
navigation I/GameFragment: onAttach called
navigation I/GameFragment: onCreate called
navigation I/GameFragment: onCreateView called
navigation I/GameFragment: onViewCreated called
navigation I/GameFragment: onStart called
navigation I/GameFragment: onResume called
navigation I/TitleFragment: onPause called
navigation I/TitleFragment: onStop called
navigation I/TitleFragment: onDestroyView called
TitleFragment 제외 OnDetach called
// Upbutton으로 타이틀 프래그먼트로 이동시
navigation I/TitleFragment: onCreateView called
navigation I/TitleFragment: onViewCreated called
navigation I/TitleFragment: onStart called
navigation I/TitleFragment: onResume called
navigation I/GameFragment: onPause called
navigation I/GameFragment: onStop called
navigation I/GameFragment: onDestroyView called
navigation I/GameFragment: onDetach called
// BackButton으로 타이틀 프래그먼트로 이동시
Upbutton과 동일.
// BackButtoon으로 앱 종료시
navigation I/TitleFragment: onPause called
navigation I/MainActivity: onPause Called
navigation I/TitleFragment: onStop called
navigation I/MainActivity: onStop Called
navigation I/TitleFragment: onDestroyView called
navigation I/TitleFragment: onDetach called
navigation I/MainActivity: onDestroy Called
// 홈 화면으로 이동시
navigation I/GameFragment: onPause called
navigation I/MainActivity: onPause Called
navigation I/GameFragment: onStop called
navigation I/MainActivity: onStop Called
// 홈 화면에서 종료시
navigation I/GameFragment: onDestroyView called
navigation I/GameFragment: onDetach called
navigation I/TitleFragment: onDetach called
/* TitleFragment는 앱 종료시에만 종료되는듯 */
navigation I/MainActivity: onDestroy Called
이전에 만들었던 DessertClicker에서 디저트를 몇번 탭하고 홈화면에서 멀티윈도우 모드로 켜보면 onDestroy 콜백 메서드가 실행되어 앱이 초기상태로 돌아가는것을 볼 수 있다.
dessertclicker I/MainActivity: onCreate Called
dessertclicker I/MainActivity: onStart Called
dessertclicker I/MainActivity: onResume Called
// 홈화면으로 갔을 때
dessertclicker I/MainActivity: onPause Called
dessertclicker I/MainActivity: onStop Called
// 멀티 윈도우 실행했을 때
dessertclicker I/MainActivity: onDestroy Called
dessertclicker I/MainActivity: onCreate Called
dessertclicker I/MainActivity: onStart Called
dessertclicker I/MainActivity: onResume Called
dessertclicker I/MainActivity: onPause Called
하지만 우리는 멀티 윈도우로 실행 했을 때도 내가 사용하던 앱이 현재 상태를 유지하기를 기대합니다.
onDestroy() 이후에도 앱의 상태를 유지하기 위해서는 onSaveInstance()와 onRestoreInstance()를 사용해야합니다. (다른 방법이 있을수도 있음. 근데 난 모름...)
우선, onSaveInstance()와 onRestoreInstance()를 추가하여 로그를 찍어보면 아래와 같이 나옵니다.
// 앱 시작
dessertclicker I/MainActivity: onCreate Called
dessertclicker I/MainActivity: onStart Called
dessertclicker I/MainActivity: onResume Called
// 홍 버튼
dessertclicker I/MainActivity: onPause Called
dessertclicker I/MainActivity: onStop Called
dessertclicker I/MainActivity: onSaveInstanceState
// 멀티 윈도우 모드
dessertclicker I/MainActivity: onDestroy Called
dessertclicker I/MainActivity: onCreate Called
dessertclicker I/MainActivity: onStart Called
dessertclicker I/MainActivity: onRestoreInstanceState
dessertclicker I/MainActivity: onResume Called
dessertclicker I/MainActivity: onPause Called
이 로그를 통해 앱이 홈버튼을 누를 때, onSaveInstanceState()로 상태를 저장하고 멀티 윈도우 모드를 사용할 때, 새로 액티비티가 만들어지면서 onRestoreInstanceState()로 상태를 복구한다는 사실을 알 수 있습니다.
따라서 우리는 onSaveInstanceState()로 우리가 상태를 유지해야 하는 데이터의 값을 저장하고 onRestoreInstanceState()에서 저장한 값을 받아서 다시 변수에 집어넣어주면 됩니다.
DessertClicker에서의 예시
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState?.run {
putInt(stateRevenue, revenue)
putInt(stateDesserts, dessertsSold)
putInt(stateCurrDessert, currentDessert.imageId)
Timber.i("onSaveInstanceState")
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val prevImageId = savedInstanceState?.getInt(stateCurrDessert)
val prevRevenue = savedInstanceState?.getInt(stateRevenue)
val prevDessert = savedInstanceState?.getInt(stateDesserts)
binding.revenue = prevRevenue
binding.amountSold = prevDessert
binding.dessertButton.setImageResource(prevImageId)
revenue = prevRevenue
dessertsSold = prevDessert
Timber.i("onRestoreInstanceState")
}