Android/Theory

[안드로이드] 수명 주기 인식요소가 뭔데? - Lifecycle, LifecycleOwner

sh1mj1 2023. 10. 18. 16:16

수명 주기 인식 구성요소.

안드로이드 개발자 공식 문서에서 Lifecycle-Aware Components 을 이렇게 번역하고 있습니다.

 

그런데 Lifecycle-Aware 컴포넌트가 정확히 뭘까요?

 

출처 https://nambuivan.medium.com/architecture-components-lifecycle-aware-and-use-cases-102021d3972d

 

Lifecycle-Aware 컴포넌트란?

먼저 정의부터 바로 하겠습니다.

Activity 나 Fragment 같은 다른 컴포넌트의 `Lifecycle` 이 변경될 때 이에 대응하는 컴포넌트.
이 컴포넌트는 `lifecycleOwner` 의 상태 변화를 `observe` 해서 필요한 작업을 스스로 할 수 있다.
(이때 컴포넌트는 안드로이드 component 만을 의미하는 게 아닌, 자신의 역할을 할 수 있는 컴포넌트를 의미함)

 

일반적인 패턴에서는 액티비티나 프래그먼트의 lifecycle 메서드 내에서 데이터를 불러오거나 리소스를 정리합니다. 즉, 액티비티나 프래그먼트의 create, start, pause, stop, destroy 등의 특정 이벤트 메서드 내에서 코드가 작성되죠.

이런 패턴은 프로그램 규모가 커지면, 코드가 너무 복잡해지며, 오류가 발생하기 쉬워집니다.

 

그래서 lifecycle 에 의존적이었던 코드를 걷어내고, `Lifecycle-Aware Component` 에 이에 대한 처리를 위임해서 코드의 유지,보수성을 높일 수 있습니다. lifecycle 에 따른 작업을 소유하는 컴포넌트를 Lifecycle-Aware 하다고 합니다.

 

아래 예시 코드를 보면 Lifecycle-Aware 컴포넌트가 왜 필요한지 이해할 수 있을 것입니다.

아래 화면에 디바이스 위치를 표시하는 액티비티가 있다고 합시다. 일반적인 패턴으로 구현되어 있습니다. (코드 출처)

 

`MyLocationListener` & `MyActivity` - 일반적인 패턴

internal class MyLocationListener(
        private val context: Context,
        private val callback: (Location) -> Unit
) {

    fun start() {
        // 시스템 위치 서비스와 연결
    }

    fun stop() {
        // 시스템 위치 서비스와 연결 끊기
    }
}

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // UI 업데이트
        }
    }

    public override fun onStart() {
        super.onStart()
        Util.checkUserStatus { result ->
            // 만약 이 콜백이 액티비티가 멈추고 나서 실행된다면?
            if(result) {
                myLocationListener.start()
            }
        }
        // 액티비티 lifecycle 에 응답해야 하는 다른 컴포넌트들 관리
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
        // 액티비티 lifecycle 에 응답해야 하는 다른 컴포넌트 관리
    }
}

 

위 코드에서는 액티비티의 lifecycle 메서드 내에서 `myLocationListener` 의 동작들이 정의되어 있습니다. 그리 이상해보이지는 않네요.

그런데 만약 액티비티 lifecycle 에 따라 동작하는 다른 컴포넌트들이 많아지면 어떨까요? `onStart` 와 `onStop` 메서드 내에 코드가 매우 많아질 것입니다.

 

게다가 다른 문제도 있습니다. 

`onStart` 메서드 내부에 `Util.checkUserStatus` 를 보면, 콜백으로 `myLocationListener`  를 실행시키고 있습니다. 그런데 사용자가 앱을 빠르게 켰다가 디바이스를 꺼버리면 `onStart` 가 실행되자마자 종료되기 전에 `onStop` 이 실행됩니다.

그래서 컴포넌트가 필요 이상으로 오래 유지되어 버리고, race condition 을 만들 수도 있습니다.

만약, `onStart` 에서 시간이 오래 걸리는 동작을 수행하도록 한다면, 이런 상황이 발생할 가능성이 더 커지겠죠.

 

Lifecycle

액티비티나 프래그먼트와 같은 컴포넌트의 lifecycle 상태에 대한 정보를 가지고, 다른 객체가 이를 관찰할 수 있도록 돕는 클래스.

 

`Lifecycle` 은 `androidx.lifecycle` 패키지에 있는 클래스입니다. 액티비티나 프래그먼트같은 컴포넌트의 Lifecycle 상태와 관련된 정보를 가지고 있죠. 그리고 다른 객체가 이 Lifecycle 상태를 관찰할 수 있게 합니다.[중요(아래에서 또 나옴)

 

Lifecycle 은 `Event(이벤트)` 와 `State(상태)` 라는 두가지 주요 열거형을 사용해서 컴포넌트의 Lifecycle 상태를 추적합니다.

  • 이벤트: 프레임워크나 `Lifecycle` 클래스로부터 전달되는 Lifecycle 이벤트. (액티비티와 프래그먼트의 콜백 이벤트)
  • 상태: `Lifecycle` 객체가 추적한 컴포넌트의 현재 상태

 

 

어려울 것이 하나도 없습니다. 말 그대로 상태는 `CREATED` 와 같이 컴포넌트의 현재 상태이고, 이벤트는 `ON_CREATE` 와 같이 상태에서 다른 상태로 이동할 수 있는 이벤트입니다.

 

클래스는 `DefaultLifecycleObserver` 를 구현하고 `onCreate`, `onDestroy` 등의 메서드를 오버라이드해서  컴포넌트의 lifecycle 상태를 모니터링할 수 있습니다.  그리고 나서, 아래 코드처럼 `Lifecycle` 클래스의 `addObserver()` 메서드를 호출해서 옵저버의 인스턴스를 전달함으로써 옵저버를 추가할 수 있습니다.

 

`MyObserver`

class MyObserver : DefaultLifecycleObserver {
    override fun onResume(owner: LifecycleOwner) {
        connect()
    }

    override fun onPause(owner: LifecycleOwner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(MyObserver())

사진 출처: https://velog.io/@h-swon/Android-Jetpack-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-Lifecycles

 

위 코드와 그림을 보면 `LifeCycleOwner` 가 `Lifecycle` 을 얻어서 옵저버를 추가하고 있습니다. 옵저버는 `Lifecycle` 이 `onResume` 되는지, `onPause` 되는지 관찰하고 있습니다.

 

위 코드에서 `myLifecycleOwner` 객체는 `LifecycleOwner` 인터페이스를 구현하고 있습니다. 그렇다면 이제 `LifecycleOwner` 에 대해 더 알아봅시다.

 

LifecycleOwner

클래스에 `Lifecycle` 이 있음을 나타내는 함수인 `getLifecycle()` 메서드 하나만 가진 함수형 인터페이스(SAM)
abstract @NonNull Lifecycle getLifecycle()

 

`getLifecycle()` 의 리턴 타입은 위 코드 블록에서 나온 것처럼 `Lifecycle` 입니다. `Lifecycle` 은 Lifecycle 옵저버를 추가, 제거하는 메서드와 현재 lifecycle 상태를 확인할 수 있는 인터페이스를 제공합니다.

`AppCompatActivity` 나 `Fragment` 는 `LifecycleOwner` 가 이미 구현되어 있습니다. 그래서 `getLifeCycle()` 메서드로를 바로 사용해서 쉽게 `Lifecycle` 의 소유권을 뽑아낼 수 있습니다. 당연히, 다른 커스텀 App. 클래스도 직접 `LifecycleOwner` 인터페이스를 구현할 수 있습니다.

 

위에서 `MyObserver` 클래스 코드를 다시 보면, `onResume` 과 `onPause` 메서드의 패러미터가 `owner: LifecycleOwner` 였습니다. 이 `LifecycleOwner` 가 옵저버(`MyObserver`)에게 관찰을 할 수 있는 lifecycle 을 제공할 수 있습니다.  그래서 `DefaultLifecycleObserver` 를 구현하는 컴포넌트가 `LifecycleOwner` 를 구현하는 컴포넌트와 원활하게 작동할 수 있는 것입니다.

 

맨 위에서 설명했던 위치 추적의 예를 다시 코드로 작성해보겠습니다. 이번에는 `MyLocationListener` 가 `DefaultLifecycleObserver` 를 구현하는 것으로 만들어봅시다.

 

`MyLocationListener`

internal class MyLocationListener(
        private val context: Context,
        private val lifecycle: Lifecycle,
        private val callback: (Location) -> Unit
): DefaultLifecycleObserver {

    private var enabled = false

    override fun onStart(owner: LifecycleOwner) {
        if (enabled) {
            // 연결하기
        }
    }

    fun enable() {
        enabled = true
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            // 연결되어 있지 않다면 연결하기
        }
    }

    override fun onStop(owner: LifecycleOwner) {
        // 연결되어 있다면 연결 끊기
    }
}

 

 

 

 

`MyLocationListener` 클래스에서 `DefaultLifecycleObserver` 를 구현하도록 한 후에 `onStart()`, `onStop()`  메서드에서 연결, 연결 끊기 동작을 수행하고 있습니다. 

추가로 `enable()` 이라는 메서드도 새로 만들었습니다. 일반적으로 `Lifecycle` 이 현재 정상 상태가 아니라면 특정 콜백 호출을 피하는 동작도 이렇게 넣어줍니다. 이게 가능한 이유는 `Lifecycle` 는 다른 객체가 현재 상태를 관찰할 수 있도록 하기 때문입니다. 이렇게 `MyLocationListener` 는 lifecycle 을 완전히 인식합니다. 

 

`MyActivity`

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this, lifecycle) { location ->
            // UI 업데이트
        }
        Util.checkUserStatus { result ->
            if (result) {
                myLocationListener.enable()
            }
        }
    }
}

 

그리고 `MyActivity` 에서는 따로 `onStart()`, `onStop()` 메서드 내부에서 따로 연결, 연결 끊기 동작을 하지 않아도 됩니다. 단지 `onCreate()` 메서드에서 액티비티의 `Lifecycle` 로 클래스를 초기화할 수 있습니다.

 

이렇게 lifecycle 상태의 변경에 반응하는 로직이 액티비티 대신 `MyLocationListener` 에서 선언되도록 수정했습니다!!

개별 컴포넌트가 자체 로직을 저장하도록 해서 액티비티나 프래그먼트의 로직을 더 쉽게 관리할 수 있게 되었습니다. 만약 다른 액티비티나 프래그먼트가 `MyLocationListener` 를 사용해야 한다면, 그 곳에서 `MyLocationListener` 를 초기화하기만 하면 됩니다!

 

이제 위에서 사용했던 `DefaultLifecycleobserver` , 그리고 그와 비슷한 `LifecycleEventObserver` 를 알아봅시다.

 

DefaultLifecycleObserver & LifecycleEventObserver

 출처: https://developer.android.com/reference/androidx/lifecycle/DefaultLifecycleObserver

`LifecycleOwner` 의 상태 변경을 관찰하는 콜백 인터페이스.
만약 `DefaultLifeCycleObserver` 와 `LifecycleEventObserver` 를 모두 구현했다면 `DefaultLifecycleObserver` 의 메서드들이 먼저 호출되고 나서 `LifecycleObserver.onStateChanged()`  호출이 따라온다.

 

`onCreate(@NonNull LifecycleOwner owner)` 의 형태로 `onDestroy`, `onPause` 등 의 메서드를 가지고 있습니다.

 

LifecycleEventObserver

출처: https://developer.android.com/reference/androidx/lifecycle/LifecycleEventObserver#onStateChanged(androidx.lifecycle.LifecycleOwner,androidx.lifecycle.Lifecycle.Event)

lifecycle 의 변화를 수신해서 receiver 에게 보낼 수 있는 클래스.
`LifecycleEventObserver` 의 메서드는 `onStateChanged` 1개 존재한다.
lifecycle 이 변할 때 `Event`가 넘어오게 되고 해당 `Event` 를 분기해서 필요한 코드를 호출하는 구조이다.

 

이 옵저버를 테스트하는 코드를 작성해봅시다. (코드 출처)

 

`MyLifeCycleObserver`

class MyLifeCycleObserver : DefaultLifecycleObserver, LifecycleEventObserver {

    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
        Log.d(owner.toString(), "onCreate")
    }

    override fun onResume(owner: LifecycleOwner) {
        super.onResume(owner)
        Log.d(owner.toString(), "onResume")
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        when (event) {
            Lifecycle.Event.ON_CREATE -> {
                Log.d(source.toString(), "onCreateEvent")
            }
            Lifecycle.Event.ON_START -> {
                Log.d(source.toString(), "onStartEvent")
            }
            else -> {}
        }
    }
}

 

`MainActivity`

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        lifecycle.addObserver(MyLifeCycleObserver())
    }
}

 

 

위처럼 옵저버를 액티비티에 연결하면, `MainActivity` 의 lifecycle 을 `MyLifeCycleObserver` 클래스에서 받을 수 있습니다. 

`MainActivity` 는 `ComponentActivity` 를 확장하고 있으므로 바로 lifecycle 을 사용할 수 있습니다. (`AppCompatActivity` 가 `FragmentActivity` 를 확장하고 `FragmentActivity` 는 `ComponentActivity` 를 확장함.)

 

 

이 코드를 테스트 하면 아래와 같이 로그가 출력됩니다.

 

예상대로 `DefaultLifeCycleObserver` 메서드를 오버라이딩한 메서드인 onCreate 가 먼저 로그에 찍히고, 이후에 onCreateEvent 가 로그에 찍히는 것을 확인할 수 있습니다. 같은 상태가 아니라면 당연히 기존 액티비티의 생명 주기대로 로그가 찍히네요.

 

이 이외에도 lifecycle-aware component 는 다양한 사례에서 사용되어 수명 주기를 훨씬 쉽게 관리할 수 있습니다. 안드로이드 개발자 공식 문서에는 아래와 같이 사례를 안내하고 있습니다.

정리

  • Lifecycle-Aware component(수명 주기 인식 구성요소) 는 lifecycle 이 변경될 때 이에 대응하는 컴포넌트이다.
  • `lifecycleOwner` 의 상태 변화를 observe 해서 필요한 작업을 스스로 할 수 있다.
  • lifecycle 에 의존적이었던 코드를 걷어내고, `Lifecycle-Aware Component` 에 이에 대한 처리를 위임해서 코드의 유지,보수성을 높일 수 있습니다
  • 이 때 `LifecycleOwner` 와 `Lifecycle`, `Observer` 를 사용한다.
  • `Lifecycle` 은 액비티티나 프래그먼트와 같은 컴포넌트이 lifecycle 에 대한 정보를 가지고 있고, 다른 객체가 이를 관찰할 수 있도록 만든다.
  • `LifecycleOwner` 는 클래스에 `Lifecycle` 이 있음을 나타내는 함수인 `getLifecycle()` 메서드 하나만 가진 함수형 인터페이스이다.
  • `LifecycleOwner`가 `Lifecycle` 을 얻도록 하여 `Observer` 를 만들 수 있다. 옵저버는 lifecycle 을 관찰한다.
  • `DefaultLifecycleObserver` 나 `LifecycleEventObserver` 를 구현하는 컴포넌트는 `LifecycleOwner` 를 구현하는 컴포넌트와 동작할 수 있다.

 

 

Reference

https://nambuivan.medium.com/architecture-components-lifecycle-aware-and-use-cases-102021d3972d

https://developer.android.com/topic/libraries/architecture/lifecycle?hl=ko#kotlin

https://velog.io/@h-swon/Android-Jetpack-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-Lifecycles

https://developer.android.com/reference/androidx/lifecycle/DefaultLifecycleObserver

 

'Android > Theory' 카테고리의 다른 글

MVC, MVP, MVVM 봐도 봐도 조금씩 헷갈리면 모르는 거임  (1) 2023.10.02
Android Room, SQLite 기본  (0) 2022.09.06
Android SharedPreference  (0) 2022.09.05
Android Thread, Handler, Runnable  (0) 2022.09.03
Intent는 택배 상자!  (1) 2022.08.28