생명주기 기반 컴포넌트는 액티비티와 프래그먼트와 같은 다른 컴포넌트의 생명주기 상태 변화에 대응하여 수행됩니다. 생명주기 기반 컴포넌트를 사용하면 코드를 더 가볍게 만들 수 있고 유지보수하기가 쉬워집니다. 즉 액티비티나 프래그먼트의 생명주기와 관련된 코드를 이 컴포넌트안에 작성하여 코드를 더 깔끔하게 작성할 수 있습니다.
예를 들어 어떤 액티비티가 생성되면서 위치 추적이 시작되고 소멸되면서 위치 추적이 멈추어야 한다면 이를 액티비티 내에서 작성할 수 있지만, 액티비티에는 위치 추척 이외에도 다른 작업을 하는 코드가 많습니다. 특정업무(위치 추적)가 생명주기와 밀접한 작업이라면 이 업무만을 추상화 하자는 개념입니다.
우선 build.gradle 파일에 아래와 같이 의존성을 설정합니다.
// 작성일 기준 안정화 버전
def lifecycle_version = "2.4.0"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
오너
생명주기 처리가 필요한 액티비티 또는 프래그먼트를 의미
옵저버
액티비티 또는 프래그먼트의 생명주기 처리를 담당하는 곳
기본적인 구조는 LifeCycle 객체에 Observer를 등록해 놓으면 액티비티나 프래그먼트 등의 생명주기 변경 시 LifeCycle 객체가 등록된 Observer를 실행하는 구조입니다.
먼저 Observer를 준비하는데 LifeCycleObserver 인터페이스를 사용하면 됩니다. 그런데 이 인터페이스를 직접 사용하지 않고 이를 구현하여 작성된 인터페이스인 DefaultLifeCycleObserver 또는 LifecycleEventObserver를 사용합니다.
DefaultLifeCycleObserver
LifeCycleOwner 상태 변경를 관찰하는 콜백 인터페이스입니다. 만약 LifecycleEventObserver와 DefaultLifeCycleObserver를 모두 구현하였다면 DefaultLifecycleObserver의 메서드들이 먼저 호출되고 난 후, LifecycleEventObserver.onStateChanged(LifecycleOwner, Lifecycle.Event)의 호출이 따라옵니다. DefaultLifeCycleObserver의 메서드는 생명주기가 변할 시 각각의 메서드를 사용하여 호출하는 구조입니다.
LifecycleEventObserver
생명주기의 변화를 수신하여 receiver에게 보낼 수 있는 클래스입니다. LifecycleEventObserver의 메서드는 한 개 존재하고 생명주기가 변할 시 Event가 넘어오고 그를 분기하여 필요한 코드를 호출하는 구조입니다.
아래의 코드는 DefaultLifeCycleObserver를 사용해 구현한 클래스입니다.
// DefaultLifecycleObserver 인터페이스 구현
class MyLifecycleObserver: DefaultLifecycleObserver {
// 필요한 메서드를 구현
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 onStart(owner: LifecycleOwner) {
super.onStart(owner)
Log.d(owner.toString(), "onStart")
}
}
DefaultLifecycleObserver를 구현하고 onCreate, onStart 등과 같은 메서드를 오버라이드하여 컴포넌트의 생명주기 상태를 모니터링할 수 있습니다. 만약 이 Observer를 액티비티에 연결하면 액티비티의 생명주기를 MyLifecycleObserver 클래스에서 받을 수 있는 것입니다.
Obsesrver가 준비되었다면 Lifecycle 객체가 필요합니다. 이 객체가 액티비티나 프래그먼트의 생명주기 변화를 감지하는 역할을 수행합니다. 그리고 생명주기의 변화가 발생하면 등록된 Observer를 실행해주는 역할을 합니다.
Lifecycle은 관련된 컴포넌트의 생명주기를 추적하기 위해서 두 개의 주요 Enum을 사용합니다.
Event
Lifecycle event는 프레임워크 및 Lifecycle 클래스에게 보내지는 생명주기 이벤트입니다. 이러한 event는 액티비티나 프래그먼트의 콜백 이벤트로 매핑됩니다.
State
Lifecycle 객체에 의해 추적되어지는 컴포넌트의 현재 상태입니다.
위의 그림에서 States를 그래프의 노드라 생각하고 Event를 그러한 노드를 잇는 간선이라고 생각하면 됩니다. 만약 States가 CREATED에서 STARTED가 된다면 ON_START라는 event가 발생하는 것이고 STARTED에서 RESUMED가 되면 ON_RESUME라는 event가 발생하는 구조입니다.
이제 Lifecycle 객체를 획득하고 addObserver() 메서드를 사용하여 Observer를 Owner에 추가합니다. Lifecycle은 개발자가 작성한 클래스가 아니므로 getLifecycle() 메서드를 통해 획득합니다. 이렇게 등록하면 Owner(액티비티나 프래그먼트)의 생명주기 변화 시 Observer에 정의한 메서드가 실행되는 구조입니다.
// Lifecycle 객체 획득
// addObserver를 사용하여 Owner에 Observer 추가
lifecycle.addObserver(MyLifecycleObserver())
프래그먼트 생명주기도 감지하지만 onCreateView, onAttach와 같은 프래그먼트만을 위한 생명주기는 감지하지 못합니다.
LifecycleOwner는 생명주기를 가지고 있음을 나타내는 단일 메서드 인터페이스입니다. getLifecycle()라는 메서드를 가지고 있는데 이는 반드시 클래스에 의해서 구현되어야만 합니다.
위의 그림에서 볼 수 있듯이 AppCompatActivity와 Fragment가 LifecycleOwner를 구현하고 있기에 액티비티나 프래그먼트 코드 안에서 간단하게 getLifeCycle() 메서드를 호출하여 Lifecycle를 획득할 수 있습니다. 만약 Activity를 상속받아 작성하였다면 직접 LifecycleOwner를 구현해야 생명주기 기반 컴포넌트를 이용할 수 있습니다.
class ExampeActivity: Activity(), LifecycleOwner {
private lateinit var lifecycleRegistry: LifecycleRegistry
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleRegistry = LifecycleRegistry(this)
lifecycleRegistry.addObserver(MyLifecycleObserver())
}
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
}
만약 전체적인 앱 프로세스의 생명주기를 관리하고 싶다면 ProcessLifecycleOwner를 사용해야 합니다. ProcessLifecycleOwner의 경우 ON_CREATE는 최초 한 번만 호출되며 ON_DESTROY는 호출되지 않습니다. 앱의 모든 액티비티가 화면을 점유하지 못하면 ON_PAUSE, ON_STOP이 호출되며, 다시 화면에 나오면 ON_START, ON_RESUME가 호출됩니다.
// DefaultLifecycleObserver 인터페이스 구현
class MyLifecycleObserver: DefaultLifecycleObserver {
// 필요한 메서드를 구현
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
Log.d(owner.toString(), "In LifecycleObserver - onCreate")
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
Log.d(owner.toString(), "In LifecycleObserver - onResume")
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
Log.d(owner.toString(), "In LifecycleObserver - onStart")
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
Log.d(owner.toString(), "In LifecycleObserver - onDestroy")
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
Log.d(owner.toString(), "In LifecycleObserver - onPause")
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
Log.d(owner.toString(), "In LifecycleObserver - onStop")
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Lifecycle 객체 획득
// addObserver를 사용하여 Owner에 Observer 추가
lifecycle.addObserver(MyLifecycleObserver())
}
}
예제는 단지 LifecycleObserver에서 Owner(액티비티)의 생명주기를 로그로 출력하고 있습니다.
위와 같이 실제 액티비티에서 일어나는 생명주기를 획득할 수 있습니다. 이를 통해 생명주기와 관련된 코드를 액티비티나 프래그먼트 내에서 작성하지 않아 유지보수에 도움이 되고 생명주기와 밀접한 관련을 가진 코드를 작성하기가 더 쉬워집니다.(위에서 언급한 지도와 같은 예제)
Android KTX는 코틀린의 확장 기능을 안드로이드 Jetpack과 다른 라이브러리에 적용하는 것입니다. 즉, 코틀린의 확장 함수, 확장 프로퍼티, 람다 등을 안드로이드에서 사용하는 것입니다. 그 중 Lifecycle KTX는 Lifecycle과 관련된 모듈입니다.
Lifecycle KTX는 각 Lifecycle 객체에 대한 LifecycleScope
를 정의합니다. 이 scope(범위)에서 실행된 코루틴은 Lifecycle
이 destroy될 때 취소되어집니다. Lifecycle
의 CorountineScope
는 lifecycle.coroutineScope
또는 lifecycleOwner.lifecycleScope
프로퍼티를 사용하여 접근할 수 있습니다. 위에서 언급하였듯이 AppCompatActivity와 Fragment등 많은 클래스들이 lifecycleOwner 인터페이스를 구현하였기에 액티비티나 프래그먼트에서 프로퍼티를 사용해 간단히 접근할 수 있습니다.
Lifecycle KTX 모듈을 사용하기 위해서는 build.gradle 파일에 의존성을 설정해야 합니다.
dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
}
아래의 코드는 lifecycleOwner.lifecycleScope를 사용하여 이미 처리된 텍스트를 비동기적으로 만드는 방법을 보여줍니다.
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Lifecycle의 CoroutineScope에 접근
// launch 메소드를 사용하여 새로운 Coroutine 실행
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
참조
깡쌤의 안드로이드 프로그래밍
안드로이드 developer - Handle lifecycles
안드로이드 developer - Android KTX
틀린 부분 댓글로 남겨주시면 수정하겠습니다..!!