안드로이드

[Android/Kotlin] Kotlin Coroutine (1) 안드로이드 코루틴

란서 2022. 3. 7. 10:51

안드로이드 코루틴

메인 스레드를 차단하여 앱이 응답하지 않게 만들 수도 있는 장기 실행 작업을 관리하는데 Coroutine 코루틴을 사용한다.


  • 경량 : 코루틴은 실행 중인 스레드를 '차단(Block)' 하지 않는 '정지(Suspend)' 을 지원한다. 따라서 단일 스레드에서 많은 코루틴을 실행할 수 있다.

    정지는 동시에 많은 작업을 진행하면서도 메모리를 절약한다.

  • 메모리 누수 감소 : '구조화된 동시 실행'을 사용하여 범위 내에서 작업을 실행한다.

    launch{}

  • 취소 지원 : 현재 코루틴이 실행되고 있는 Scope를 통해 자동으로 '취소'가 전달된다.

    .cancel()

  • Jetpack 통합 : 많은 Jetpack 라이브러리에 코루틴을 완전히 지원하는 확장프로그램이 포함되어 있다.
    일부 라이브러리는 '구조화된 동시 실행'에 적용 가능한 자체 코루틴 범위 (Scope)를 제공한다.

    viewModelScope, lifecycleScope 등등

Overview

네트워크 요청을 보내고 , 해당 결과를 메인 스레드로 반환하여 결과를 사용자에게 표시.

 

특히 ViewModel 아키텍쳐 구성요소는 기본 스레드의 repositery layer를 호출하여 네트워크 요청을 trigger 한다. 

 

다양한 솔루션을 반복하여 메인 스레드가 차단되지 않도록 유지.

 

viewModel에는 코루틴과 직접 연동되는 KTX확장 프로그램이 포함. -> lifecycle-viewmodle-ktx

 

Gradle

Android 프로젝트에서 코루틴을 사용하기 위해서

 

implementation "org.jetbraings.kotlinx:kotlinx-coroutines-android:1.3.9"

 

백그라운드에서 실행. (코루틴 x)

메인 스레드에서 네트워크 요청을 보내면, 응답을 받을 때 까지 메인 스레드가 대기하거나 차단된다. 스레드가 차단될 경우 OS가 onDraw()를 호출할 수 없엇서 앱이 멈추고, 어플리케이션 응답 없음(ANR) 대화 상자가 표시.

따랏서 사용자 환경을 개선하기 위해선 네트워크 요청과 같은 장기 실행 작업은 백그라운드 스레드에서 실행하여야 한다.

 

//Viewmodel.kt

class LoginViewModel(
	private val loginRepostiery: LoginRepositery
    ) : ViewModel() {
    	
        fun login(name:String, token:String) {
        	viewModelScope.launch(Dispatchers.IO) {
            	...
            }
        }
    	
    }

 

  1. viewModelScope : ViewModel KTX확장 프로그램에 포함되어 사전 정의된 CoroutineScope. (모든 코루틴은 코루틴 범위 내에서 실행된다.)

  2. lacunh : 코루틴을 생성(시작)하고, 함수 본문의 실행을 인자로 넘긴 Dispatcher에 전달하는 함수.
  3. Dispatcher.IO : 코루틴을, I/O 작업용으로 예약된 스레드(백그라운드 스레드)에서 실행햐아 한다.

 

이처럼 viewModelScope에서 시작되는 코루틴은 ViewModel의 범위에서 실행된다. 사용자가 viewModel의 수명주기 밖에서 활동한다면 viewModel이 소멸될 수도 있다. 이에 따라 viewModelScope는 자동으로 취소되고, 실행 중인 모든 코루틴도 취소된다.

 

위예에도 문제점이 있다.

viewModelScoepe 내에서 실행되는 항목들이 여러가지가 있을 수  있다.
예를 들어 Login을 위해서 loginRepositery.makeLoginRequest() 를 호출할 수 있고 해당 함수는 I/O 작업용 백그라운드 스레드에서 실행되는 것이 좋을 것이다.

그러나 로그인 정보를 받아와 이를 UI에 display하는 것은 응당 메인 스레드에서 해야할 작업이다. 
따라서 viewModelScope의 dispatcher를 I/O Dispatcher로 명시적으로 지정해놓는다면 유연하게 실행을 할 수 없을 것이다.


Main-safe 한 코루틴 함수 만들기.

메인 스레드에서 UI업데이트와 같은 사용자 환경을 차단하지 않는 함수를 main-safe 하다고 간주.

메인 스레드에서 네트워크를 요청하는 함수를 호출하는 것은 UI를 차단 시키기 때문에 main-safe하지 않다. 따라서 해당 함수를 main-safe하게 만드는 방법을 알아보자.

 

//Repositery.kt

...

suspend fun makeLoginRequest() : LoginDataClass{
	return withContext(Dispatcher.IO) {...}
}

해당 함수는 네트워크 요청을 통해 로그인을 실행하는 함수이다. 메인스레드에서 실행될 경우 UI업데이트를 차단할 수 있다. 따라서 withContext() 함수 호출Dispatcher를 통해 해당 코루틴의 실행을 I/O 작업용 백그라운드 스레드로 이동시킬 수 있다. -> 해당 함수를 main-safe하게 만든다.

main-safe한 함수처리를 위해선 추가적으로 suspend 키워드를 사용한다. suspend 키워드는 해당 함수가 main-safe 해야 함을 말하며 반드시 코루틴 내에서 호출 되도록 강제하는 kotlin 문법이다. 

 

이와 같은 main-safe함수는 

//Viewmodel.kt

class LoginViewModel(
	private val loginRepostiery: LoginRepositery
    ) : ViewModel() {
    	
        fun login(name:String, token:String) {
        	viewModelScope.launch {
            	val jsonBody = ...
            	val login = loginRepositery.makeLoginRequest(jsonBody)
                
                when(result) {
                	is Result.Success<LoginResponse> -> {}
                    else -> {}
                }
            }
        }
    	
    }

 

이런식으로 메인 스레드에서도 스스로 코루틴 실행을 외부로 이동하기 때문에 메인 스레드에서도 안전하게 실행이 가능하다.

 

  1. launch가 더 이상 Dispatcher를 매개변수로 사용하지 않는다. Dispatcher를 launch에 전달하지 않으면 기본적으로 Scope에서 실행되는 코루틴은 메인 스레드에서 실행된다.
  2. 실행 과정.

    -앱이 메인 스레드의 view layer에서 login()함수를 호출. 

    -launch가 메인 스레드에서 네트워크 요청을 보낼 새로운 코루틴을 생성하고, 코루틴이 실행을 시작.

    -코루틴 내에서 login함수를 호출하고 login함수의 withContext{} 실행이 끝날 때 까지 해당 CoroutineScope의 코루틴추가 실행을 정지

    -wtihContext {} 이 완료되면 login()의 코루틴이 네트워크 요청의 결과와 함께 main thread에서 실행 재개.



코루틴 실행 예외 처리

try() catch() 구문 이용.

val result = try {
	loginRepositery.makeLoginRequest(jsonBody)
} catch(e:Exception) {
	Result.Error(Exception("Network request Failed"))
}