깨알 개념/Android

[Kotlin] Coroutines - LifecycleScope

interfacer_han 2024. 2. 21. 12:34

본 게시글의 Coroutine 개념은 Android 내에서 사용되는 것을 전제로 작성되었다.

 

#1 이전 게시글

 

[Kotlin] Coroutines - ViewModelScope

본 게시글의 Coroutine 개념은 Android 내에서 사용되는 것을 전제로 작성되었다. #1 ViewModel 속 전통적인 방식의 Coroutinesimport androidx.lifecycle.ViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.D

kenel.tistory.com

이전 게시글에서 ViewModel의 수명주기를 참조해, ViewModel이 소멸될 때 알아서 종료되는 CoroutineScope에 대해 다뤘다. 이번엔 그 참조의 대상이 LifecycleOwner인 버전을 살펴본다. 이전 글과 거의 비슷한 개념이므로, 본 게시글에선 이전 게시글과 중복되는 설명은 최소한으로 줄였다.

 

#2 lifecycle-run-ktx 라이브러리

#2-1 개요

 

수명 주기 인식 구성요소로 Kotlin 코루틴 사용  |  Android 개발자  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 수명 주기 인식 구성요소로 Kotlin 코루틴 사용 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Kotlin 코

developer.android.com

이 라이브러리의 LifecycleScope를 이용해서 상용구 코드를 제거한다.

 

#2-2 androidx.lifecycle.LifecycleOwner.kt 살펴보기 (LifecycleScope와 lifecycleScope)

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.lifecycle

import kotlinx.coroutines.CoroutineScope

/**
 * A class that has an Android lifecycle. These events can be used by custom components to
 * handle lifecycle changes without implementing any code inside the Activity or the Fragment.
 *
 * @see Lifecycle
 * @see ViewTreeLifecycleOwner
 */
public interface LifecycleOwner {
    /**
     * Returns the Lifecycle of the provider.
     *
     * @return The lifecycle of the provider.
     */
    public val lifecycle: Lifecycle
}

/**
 * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
 */
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

LifecycleScope는 LifecycleOwner 인터페이스의 구현 객체에 종속되어, 해당 객체들이 소멸할 때 알아서 종료되는 CoroutineScope다. #3-1에서처럼 라이브러리를 불러온 순간부터 LifecycleScope는 LifecycleOwner의 프로퍼티로서 자동 생성된다. 해당 프로퍼티의 이름은 첫 글자가 소문자인, lifecycleScope다. lifecycleScope는 coroutineScope와 비슷하게 사용하면 된다.

 

#3 LifecycleScope 사용하기

#3-1 build.gradle.kts (Module)에서 라이브러리 다운로드

plugins {
    ...
}

android {
    ...
}

dependencies {
    ...

    // LifecycleScope
    val lifecycle_version = "2.6.2"
    implementation ("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version")
}

여기의 구문을 복사해 모듈 수준 그래들 파일에 붙여넣는다.

 

#3-2 MainActivity.kt

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

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

        lifecycleScope.launch {
            while (true) {
                Log.i("interfacer_han", "I'm Coroutines")
                delay(1000)
            }
        }
    }
}

위 코드의 코루틴은 해당 Activity가 onDestory()되면 알아서 종료된다.

 

Fragment도 Activity처럼 LifecycleOwner의 구현체이므로, lifecycleScope를 사용할 수 있다.

 

ViewModel도 뷰모델 만의 생명주기가 있지만 LifecycleOwner 인터페이스의 구현체는 아니기 때문에 ViewModel은 lifecycleScope 프로퍼티를 가지지 않고, 따라서 사용할 수도 없다. 대신 ViewModel을 위한 ViewModelScope가 있다.

 

#3-3 repeatOnLifecycle()

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        Log.i("interfacer_han", "MainActivity.onCreate()")
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                Log.i("interfacer_han", "(lifecycleScope) repeat when onStart()")
            }
        }
    }

    override fun onStart() {
        Log.i("interfacer_han", "MainActivity.onStart()")
        super.onStart()
    }

    override fun onResume() {
        Log.i("interfacer_han", "MainActivity.onResume()")
        super.onResume()
    }

    override fun onPause() {
        Log.i("interfacer_han", "MainActivity.onPause()")
        super.onPause()
    }

    override fun onStop() {
        Log.i("interfacer_han", "MainActivity.onStop()")
        super.onStop()
    }

    override fun onRestart() {
        Log.i("interfacer_han", "MainActivity.onRestart()")
        super.onRestart()
    }

    override fun onDestroy() {
        Log.i("interfacer_han", "MainActivity.onDestroy()")
        super.onDestroy()
    }
}

/* Log 출력 결과 - 앱 최초 실행 시
MainActivity.onCreate()
MainActivity.onStart()
(lifecycleScope) repeat when onStart()
MainActivity.onResume()
*/

/* Log 출력 결과 - 앱을 벗어났을 때
MainActivity.onPause()
MainActivity.onStop()
*/

/* Log 출력 결과 - 앱에 재진입했을 때
MainActivity.onRestart()
MainActivity.onStart()
(lifecycleScope) repeat when onStart()
MainActivity.onResume()
*/

위 코드는 이 게시글의 #3-1을 참조해 짰다. LifecycleOwner에서 쓰이는 코루틴이 생명주기에 좀 더 디테일하게 반응하게 만들고 싶을 수 있다. 이럴 땐, repeatOnLifecycle()을 사용한다. 어쩌면 이 repeatOnLifecycle()이야말로 LifecycleScope의 진가라고 볼 수 있다. 왜냐하면 LifecycleOwner 생명주기의 각 단계에서 시작될 코루틴을 쉽게 구현할 수 있기 때문이다. (종료는 마찬가지로 LifecycleOwner가 파괴될 때 수행된다). 위의 코드에선 Lifecycle.State를 Started로 설정했으므로, onStart()일 때 수행된다. 이처럼 repeateOnLifecycle()가 쓰이면 좀 더 좁은 범위, 국소적인 버전의 lifecycleScope가 된다.

 

#3-4 repeatOnLifecycle()의 존재 이유 추측

하지만, #3-3에선 의문점이 하나 생긴다. 굳이 repeatOnLifecycle()를 쓸 게 아니라, 그냥 아래와 같은 코드를 쓰면 그만 아닌가?

...

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        Log.i("interfacer_han", "MainActivity.onCreate()")
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
    
    ...

    override fun onStart() {
        Log.i("interfacer_han", "MainActivity.onStart()")
        super.onStart()

        lifecycleScope.launch {
            Log.i("interfacer_han", "(lifecycleScope) repeat when onStart()")
        }
    }

    ...
}

/* Log 출력 결과 - 앱 최초 실행 시
MainActivity.onCreate()
MainActivity.onStart()
(lifecycleScope) repeat when onStart()
MainActivity.onResume()
*/

/* Log 출력 결과 - 앱을 벗어났을 때
MainActivity.onPause()
MainActivity.onStop()
*/

/* Log 출력 결과 - 앱에 재진입했을 때
MainActivity.onRestart()
MainActivity.onStart()
(lifecycleScope) repeat when onStart()
MainActivity.onResume()
*/

출력 결과가 #3-3과 똑같다. 같은 코드라는 말이다. 그렇다면 repeatOnLifecycle()가 굳이 존재하는 이유는 무엇인가? 사실 나도 잘 모르겠다. 아마도, 각 Lifecycle.State에 어떤 코루틴이 수행될 지 (가독성 좋게) 모아서 볼 수 있다는 점으로 추측된다.

 

#4 요약

LifecycleOwner에서의 CoroutineScope 생성 및 Cancellation을 암시화한다.

 

#5 완성된 앱

 

android-practice/coroutines/LifecycleScope at master · Kanmanemone/android-practice

Contribute to Kanmanemone/android-practice development by creating an account on GitHub.

github.com