안드로이드에서의 코루틴 사용

안드로이드에서는 뷰를 다루는 스레드가 단 하나만 존재하며, 블로킹되면 안 된다.

따라서 아래와 같이 시간이 오래 걸리는 작업을 메인 스레드에서 실행하면 안 된다.

fun onCreate() {
    val news = getNewsFromApi()
    val sortedNews = news.sortedByDescending { it.publishedAt }
    view.showNews(sortedNews)
}

스레드 전환

이전 문제를 해결하는 가장 직관적인 방법은 스레드 전환이다. 블로킹이 가능한 스레드를 먼저 사용하고, 이후에 메인 스레드로 전환한다.

import kotlin.concurrent.thread

fun onCreate() {
    thread {
        val news = getNewsFromApi()
        val sortedNews = news.sortedByDescending { it.publishedAt }
        runOnUiThread {
            view.showNews(sortedNews)
        }
    }
}

위 코드는 다음과 같은 문제가 있다.

다음과 같이 생각하면 위의 문제를 이해하는 데 도움이 된다. 뷰를 열었다가 재빨리 닫았다고 생각해보자. 뷰가 열려 있는 동안, 데이터를 가져와 처리하는 스레드가 다수 생성된다. 생성된 스레드들을 제거하지 않으면, 스레드는 주어진 작업을 계속 수행한 후 더 이상 존재하지 않는 뷰를 수정하려고 시도할 것이다. 이것은 디바이스에 불필요한 작업이며, 백그라운드에서 예외(exception)를 유발하거나 예상하기 어려운 결과가 발생할 수 있다.

콜백

콜백(callback)은 앞에서 주어진 문제를 해결하는 또 다른 패턴이다. 콜백의 기본적인 방법은 함수를 논블로킹(non-blocking)으로 만들고, 함수의 작업이 끝났을 때 호출될 콜백 함수를 넘겨주는 것이다. 콜백 패턴을 쓰는 함수는 다음과 같다.

fun onCreate() {
    getNewsFromApi { news ->
        val sortedNews = news.sortedByDescending { it.publishedAt }
        view.showNews(sortedNews)
    }
}

위와 같이 콜백을 이용해 구현한 방식 또한 중간에 작업을 취소할 수 없다. 취소할 수 있는 콜백 함수를 만들 수도 있지만 쉬운 일은 아니다. 콜백 함수를 취소하는 예시는 다음과 같다.

class CallbackHandler {
    private var isCancelled = false

    fun setCallback(callback: () -> Unit) {
        if (!isCancelled) {
            callback()
        }
    }

    fun cancelCallback() {
        isCancelled = true
    }
}

fun main() {
    val handler = CallbackHandler()

    handler.setCallback {
        println("Callback executed")
    }

    handler.cancelCallback()

    handler.setCallback {
        println("This will not be printed")
    }
}

콜백 함수 각각에 대해 취소할 수 있도록 구현해야 할 뿐 아니라, 취소하기 위해선 모든 객체를 분리해서 모아야 한다.