본문 바로가기

기타/안드로이드

32. Dessert Clicker App : Activity Life Cycle

Dessert Clicker App : Activity Life Cycle

활동 수명 주기 단계

 

활동 수명 주기 단계  |  Android Developers

이 Codelab에서는 활동 수명 주기와 로깅을 알아봅니다.

developer.android.com

위의 페이지를 참고하여 안드로이드의 활동이 가지는 생명주기를 알아보자

 

 

개요

안드로이드에서의 activity는 대충 프로그램 그 자체라고 볼 수 있는 거 같다

그러한 activity는 위와 같은 생명주기를 가진다

우리가 그동안 main 함수처럼 사용했던 onCreate가 어디있는지를 확인해볼 수 있다

 

생명주기가 각 단계로 전환될 때마다 이름 앞에 on이 붙어있는 콜백함수가 실행되는데

우리는 이 콜백함수를 override할 수 있다

우리가 그동안 onCreate에 해왔던 것처럼 말이다.

 

 

 

콜백함수 호출시 로그출력하기

각각의 콜백함수가 언제 호출되는지 다음과 같은 예제 프로그램에 로그를 출력시켜서 알아보자

예제 프로그램의 이름은 Dessert Clicker다.

 

Dessert Clicker는 디저트를 클릭하면 숫자와 이미지가 조금씩 바뀌는 프로그램이다

 

url에서 clone을 해오고 starter branch로 checkout 해주자

https://github.com/google-developer-training/basic-android-kotlin-compose-training-dessert-clicker

 

 

 

다양한 콜백함수 중에서 이미 코드에 있고 우리에게 익숙한 onCreate를 가지고 시작해보자

먼저 MainActivity.kt 파일에서 import문 아래에 다음과 같이 상수 선언 코드를 추가하자

private const val TAG = "MainActivity"

 

그리고 onCreate에서 super.onCreate(savedInstanceState) 다음 줄에 다음과 같이 로그를 출력하는 코드를 추가하자

Log.d(TAG, "onCreate Called")

Log는 우리가 에뮬레이터 녹화할 때 쓰던 Logcat에 로그를 출력하게 해준다

첫번째 인자는 로그의 title이고 두번째 인자는 로그의 message라고 생각하면 되겠다.

 

Log.v, Log.d, Log.w, Log.i, Log.e처럼 다양한 메소드가 있는데 각각

상세메시지, 디버그메시지, 경고메시지, 정보메시지, 오류메시지를 출력한다

 

Logcat에 뜨는 로그를 보고 있으면 바로 이해가 될 것이다

 

 

우리는 이제 onCreate가 호출될 때 Logcat에 onCreate Called라는 메시지를 출력하도록 만들었다

그럼 이제 나머지 콜백함수들에도 같은 짓을 해줄 차례이다

 

MainActivity 클래스 안에 onCreate가 있는 것처럼 다른 콜백함수들도 MainActivity 안에 들어가야 한다

 

위의 사진처럼 MainActivity 클래스 안에 onCreate와 같은 depth에 마우스 커서를 두고

ctrl+O를 눌러보거나 위의 메뉴바에서 Code->Override Methods를 눌러주자

 

그 상태에서 ctrl+F가 눌려있다고 생각하고 그냥 검색해서

onStart, onResume, onPause, onRestart, onStop, onDestory를 각각 찾은 뒤 OK를 눌러주자

그러면 이제 알아서 콜백함수의 override 코드가 입력되어있다

 

나머지 콜백함수들의 override 코드를 모두 추가해줬다면

이제 onCreate처럼 super.콜백함수 호출 코드의 아래에 로그출력 코드를 추가해줄 것이다

결과적으로 MainActivity 클래스는 다음과 같이 작성된다

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate Called")
        setContent {
            DessertClickerTheme {
                DessertClickerApp(desserts = dessertList)
            }
        }
    }


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

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

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

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

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

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

 

 

콜백함수의 호출시점 확인하기

이제 Dessert Clicker를 실행시켜주자

그리고 Logcat에서 검색기능에 MainActivity를 넣어 TAG를 MainActivity로 해놓은 친구들만 확인하자

 

아래 파란색 친구 3명을 볼 수 있다

우리는 이를 통해 프로그램이 시작되면 우선 onCreate->onStart->onResume이 호출되는 것을 알 수 있다

 

 

이 다이어그램을 보고 Activity가 focus는 없지만 보이긴한다면 onPause만 호출될 것이라고 짐작할 수 있다

그러면 그러한 활동에는 뭐가 있을까?

바로 공유버튼이다

 

Dessert Clicker의 우상단의 공유버튼을 누르면 onPause만 호출된다

다른 행동을 하고 있으니 터치가 먹히지 않는 즉 focus가 없는 상태이지만 화면은 나오고 있다는 게 보일 것이다

이 상태에서 뒤로가기 버튼을 눌러 공유 화면을 닫는다면 onResume이 호출된다

 

그러면 onStop까지 호출하려면 어떻게 해야할까?

 

화면에서 안보이게하면 되니까 '뒤로가기 버튼'을 누르거나

'홈으로 이동 버튼'을 누르면 될 것이다

다시 어플로 이동한다면 onStart->onResume이 호출된다

 


Configuration Change

configuration change란 기기상태가 급격히 바뀌어서

앱을 그에 맞춰 변경하는 가장 쉬운 방법이 앱을 재시작하는 경우인 상황을 말한다

 

예를 들면 화면의 회전이 있다

에뮬레이터에 있는 화면 회전 버튼을 클릭해보자

 

보다시피 onDestroy까지 갔다가 다시 시작한다

(참고로 에뮬레이터를 회전시키면 화면이 완전 멈춰버리는 경우가 있는데 필자는 이 경우에 device manager에서 wipe all data를 해도 해결이 안되서 그냥 다른 가상기기를 만들었더니 바로 해결되었다.)

 

 

여기서 문제는 컴포저블의 상태가 onDestory까지 갔다가 오면 다 날아간다는 점이다

그래서 Dessert sold, Total Revenue 값을 올려놓고 화면을 회전시키면 값이 날아간다

 

이걸 어떻게 해결해야할까

예전에도 이런 비슷한 고민을 했었다

컴포지블이 recomposition때 상태를 잃어버리는 상황이었는데

우리는 remember라는 API를 이용해서 이를 해결했었다

 

그리고 이 상황에서도 비슷한 방식으로 해결할 수 있다

그냥 remember 대신 rememberSaveable을 쓰면 된다!

 

그러니까 이 코드를

var revenue by remember { mutableStateOf(0) }
var dessertsSold by remember { mutableStateOf(0) }

val currentDessertIndex by remember { mutableStateOf(0) }

var currentDessertPrice by remember {
    mutableStateOf(desserts[currentDessertIndex].price)
}
var currentDessertImageId by remember {
    mutableStateOf(desserts[currentDessertIndex].imageId)
}

 

이렇게 바꾸면 된다

var revenue by rememberSaveable { mutableStateOf(0) }
var dessertsSold by rememberSaveable { mutableStateOf(0) }

val currentDessertIndex by rememberSaveable { mutableStateOf(0) }

var currentDessertPrice by rememberSaveable {
    mutableStateOf(desserts[currentDessertIndex].price)
}
var currentDessertImageId by rememberSaveable {
    mutableStateOf(desserts[currentDessertIndex].imageId)
}

 

바꾼 코드를 가지고 다시 에뮬레이터를 회전시켜보자

이제는 Dessert sold와 Total Revenue의 값이 유지되는 것을 볼 수 있을 것이다