Android

Android Coroutine 사용하기

BBiRaRiRo 2021. 9. 25. 17:49

1. Coroutine


Coroutine은
Co(협력) + Routine(규칙적인 작업)의 합성어로
하나의 작업이 끝날 때까지 계속 진행되는 것이 아니라
실행 중간에 다른 작업을 하러 갔다가 다시 돌아와서 작업을 이어서 진행할 수 있다.

1.1. Coroutine 은 Thread 가 아니다.

같은 백그라운드 작업을 하는 점에서 비슷하지만
Coroutine은 하나의 작업이라면
Thread는 그 작업을 수행하는 공간이다.

즉 하나의 Thread에서 여러 Coroutine 을 동시에 실행할 수 있다.

1.2. 의존성 추가

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5"

1.3. Scope

코루틴은 실행 범위, 제어 범위를 지정할 수 있으며 이 범위를 Coroutine Scpoe 라고 한다.

GlobalScpoe : 프로그램 어디서나 제어, 동작이 가능한 범위
CoroutineScpoe :특정한 목적을 지정하여 제어 및 동작이 가능한 범위

CoroutineScope(Dispatchers.IO)
CoroutineScope(Dispatchers.Main)
CoroutineScope(Dispatchers.Default)

IO : 네트워크 작업이나 DB에 접근하는 등 백그라운드에서 필요한 작업을 수행
Main: 메인스레드 작업으로 UI 갱신이나, Toast 등 View 관련 작업 수행
Default : 무거운 연산 작업 수행

2. launch 와 async


코루틴 반환의 여부에 따라 사용된다

2.1. launch

launch는 반환이 없는 job 객체를 반환한다.

launch {
	for (i in 1..5){
		println(i)
		delay(10)
	}
}

해당 코루틴의 결과를 대기하려면 join 을사용한다.

val a = launch {
	for (i in 1..5){
		println(i)
		delay(10)
	}
}
a.join()

 

2.2. async

async는 반환값이 있는 Deffered 객체를 반환한다.

val b = async {
	for (i in 1..5){
		println(i)
		delay(10)
	}
	"async 종료"
}

해당 코루틴의 결과를 대기하려면 await 을사용한다.

val b = async {
	for (i in 1..5){
		println(i)
		delay(10)
	}
	"async 종료"
}
b.await()

 

2.3. runBlocking

fun main(){
    val scope = GlobalScope
    scope.launch {
        for (i in 1..5){
            println(i)
        }
    }
}

위와 같이 코드를 작성 한 경우 화면에는 출력이 되지 않는다.
그 이유는 코루틴은 스코프 혹은 프로그램이 종료가 되면 같이 종료가 되기 때문에
main 이 종료가 되면서 같이 종료가 된 것이다.

코루틴이 종료될 때까지 메인루틴을 대기하면 화면에 출력된다.

fun main(){
    runBlocking {
        launch {
            for (i in 1..5){
                println(i)
            }
        }
    }
}

이경우 안드로이드에서 메인스레드 를 오랫동안 대기하면 ANR이 발생하니 주의를 해야 한다.

3. 여러 Dispatchers 의 처리


3.1. 비동기 메서드 생성

suspend fun callApi(): String {
    return "OK"
}

함수 앞에 붙은 suspend는 이 함수가 비동기, 즉 코루틴 안에서 실행하도록 하는 것이다.
만약 코루틴이 아닌 곳에서 사용하려 하면 경고를 출력한다.

3.2. 여러 Dispatchers

CoroutineScope(Dispatchers.IO).launch {
            val result = callApi()
            textView.text  = result
}

 

위와 같이 백그라운드 작업과 UI 작업이 동시에 일어날 때
두 종류의 작업을 처리해야 하는 경우가 생긴다.

물론

CoroutineScope(Dispatchers.IO).launch {
		val result = callApi()
		CoroutineScope(Dispatchers.Main).launch {
			textView.text  = result
		}
	}

위와 같이 코루틴 안에 또 코루틴을 생성하여 처리할 수 있지만
이럴 때 사용하는 withContext가 있다.

CoroutineScope(Dispatchers.IO).launch {
	val result = callApi()
	withContext(Dispatchers.Main){
		textView.text  = result
	}
}

4. 네트워크 타임아웃 처리


네트워크 타임아웃 처리는 withTimeoutOrNull로 쉽게 처리할 수 있다.
시간이 경과하면 null 을 반환한다.

CoroutineScope(Dispatchers.IO).launch {
	val result = withTimeoutOrNull(10000) {
		callApi()
	}
	if (result != null) {
		withContext(Dispatchers.Main){
			textView.text  = result
		}
	}
}