안드로이드/네트워크콜

Android Kotlin Coroutine을 통한 비동기 처리의 개념과 사용 예시

Coroutine

Android Developer에선 Android의 Kotlin Coroutine을 다음과 같이 설명하고 있습니다.

비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴

Coroutine은 Android의 비동기 프로그래밍에 권장되는 Solution입니다.

 

주목할 만한 4가지 기능

  • 경량 - Coroutine을 실행 중인 스레드를 차단하지 않는 정지를 지원해 단일 스레드에서 많은 Coroutine 실행이 가능(성능적으로 매우 우수!)
  • 메모리 누수 감소 - 구조화된 동시 실행을 사용해 범위 내 작업 실행 (AsyncTask가 deprecated된 이유 중 하나…)
  • 기본으로 제공되는 취소 지원 - 실행 중인 Coroutine 계층 구조를 통해 자동으로 취소가 전달
  • Jetpack 통합 - 많은 Jetpack 라이브러리에 Coroutine을 완전히 지원하는 방법이 포함되어 있음(Jetpack 최고!)

Kotlin의 Coroutine은 비동기 처리를 할 수 있는 좋은 방법입니다.

Coroutine을 사용하면 작업을
foreground → background 또는 background → foreground로 쉽게 전환하며 처리할 수 있습니다.

 

Android에서 Coroutine을 사용하기 위해 기본적으로 알아야 할 주요 키워드

  • CoroutineScope
  • CoroutineContext
  • Dispatcher
  • launch

CoroutineScope

Coroutine의 범위, Coroutine Block을 묶음으로 제어할 수 있는 단위입니다.

CoroutineScope를 상속받아 CoroutineScope와 GlobalScope 등에서 이를 활용합니다.

이 중 GlobalScope는 프로그램 전반에 걸쳐 Background에서 동작합니다.

Android 앱이 처음 시작부터 종료할 때까지 하나의 CoroutineContext 안에서 동작할 수 있도록 할 수 있습니다.

CoroutineContext

CoroutineContext는 Coroutine을 어떻게 처리할 것인지에 대한 여러가지 정보의 집합입니다.

주된 요소는 Coroutine의 Job과 Dispatcher 입니다.

Dispatcher

Dispatcher는 CoroutineContext의 요소 입니다.

CoroutineContext를 상속 받아서 어떤 Thread를 이용해 어떻게 동작할 것인지 정의되어 있습니다.

Dispatch는 1. 보내다, 2. 파견,발송 의 의미를 지니고 있습니다.

Coroutine의 Dispatcher도 의미를 벗어나지 않습니다.

Coroutine을 어떤 Thread에게 보낼지 정하는 것이 Dispatcher 입니다.

Understand Kotlin Coroutines on Android(Google I/O’19) 에서 설명된 Dispatcher 입니다.

  1. Dispatchers.Default
    • CPU 사용량이 많은 작업에 사용하며 주 스레드에서 작업하기에는 너무 긴 작업에 적합
  2. Dispatchers.IO
    • 네트워크, DB 작업에 사용하며 파일, 소켓을 읽거나 쓰거나 작업을 멈추는 것(Thread Block)에 적합
  3. Dispatchers.Main
    • UI 작업을 할 때 적합
    • Android의 Main Thread는 UI Thread이며, Android에서 Dispatchers.Main은 UI Thread를 사용해 동작합니다.

즉 Dispatcher는 Coroutine을 어떤 Thread에게 보낼지 정하며 Dispatchers에 정의된 내용을 바탕으로 보내게 됩니다.

launch

launch는 CoroutineScope의 확장함수입니다.

넘겨 받는 Code Block으로 Coroutine을 만들어 실행해주는 Coroutine Builder 입니다.

launch는 Job 객체를 반환하며 이를 통해 작업을 제어할 수 있습니다.

Android에서 Coroutine 사용 요약 !

  1. Coroutine을 Dispatcher를 이용하여 결정
  2. Dispatchers를 이용해 CoroutineScope를 생성
  3. CoroutineScope에서 launch를 통해 수행할 Code Block을 넘김
CoroutineScope(Dispatchers.Main).launch {
    // foreground ~
}

CoroutineScope(Dispatchers.Main).launch(CoroutineScope(Dispathcers.Default) {
    // background ~
}

위와 같이 포그라운드 작업과 백그라운드 작업을 쉽게 전환하며 작업할 수 있습니다.

백그라운드 작업을 보면 같은 CoroutineScope에서 작업이 처리되는 Thread만 변경하여 background를 처리할 수 있다는 것을 알 수 있습니다.

launch를 통해 작업한 Coroutine Block은 Job 객체를 반환하며 이 Job 객체를 이용하여 Coroutine Block을 제어할 수 있습니다.

네트워크 작업을 주로 사용하는 저희는 Dispatcher를 IO로 정하고 CoroutineScope를 생성합니다.

CoroutineScope에서 launch하며 수행될 Code를 작성합니다.

CoroutineScope(Dispatchers.IO).launch {
    try {
        val response = client() // client() is API Call Response
        if (response.isSuccessful) {
                성공 작업 ~
        } else {
                실패 작업 ~
        }

    } catch (e: Exception) {
        실패 작업 ~
    }
}

API를 실행시킬 Code Block을 lambda 형태의 client 인자로 받아와 처리하고
그에 따른 결과를 다시 Android MainThread인 UI Thread로 전환하여 처리하는 구조입니다.

Coroutine을 사용하는 큰 이유 중 하나가 바로 이런 경량화인데

멀티 스레딩 작업의 경우, Thread가 OS를 통해 관리(context switching)되어 CPU에서 비용이 많이 들 수 있습니다.

Coroutine의 경우는 작업이 동시에 진행되는 것처럼 보이지만 사실 1개의 Thread에서 동기적으로 작업이 진행됩니다.

이것이 Cocurrency(동시성)입니다.

그래서 Thread가 아무것도 하지 않는 유휴 상태를 방지해 Thread를 최대한 활용할 수 있습니다.

멀티 스레딩 기법에서 CPU를 많이 소모하는 Context Switching이 필요하지 않아 속도가 훨씬 빠른 것입니다.

 

만약 이것이 이루어지지 않는다면 어떤 일이 벌어질까요?

안드로이드의 Main Thread는 UI Thread입니다.

즉 Main Thread에서 UI 작업 이외에 복잡하고 무거운 작업을 수행하는 것은 적절하지 않습니다.

복잡한 작업이 끝날 때까지 사용자는 UI를 볼 수 없고 이는 사용자의 좋지 않은 경험으로 이어질 수 있습니다.

그래서 안드로이드는 무겁고 복잡한 작업을 Main Thread에서 진행하지 못하도록 막습니다.

 

AsyncTask가 AOS에서 2019년 말에 deprecated되고 그에 대한 대응으로 RxJAVA, Coroutine이 있었는데 Coroutine을 추천한다는 말에 Coroutine 사용법을 보며 사용하고 있었습니다.

간략하게 알고 있던 Coroutine의 개념을 자료를 정리하고 문서를 작성하며 보다 깊이 있게 알 수 있는 계기가 된 것 같습니다.

이만 마치도록 하겠습니다.

감사합니다.