안드로이드에서는 뷰를 다루는 스레드가 단 하나만 존재하며, 블로킹되면 안 된다.
따라서 아래와 같이 시간이 오래 걸리는 작업을 메인 스레드에서 실행하면 안 된다.
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")
}
}
콜백 함수 각각에 대해 취소할 수 있도록 구현해야 할 뿐 아니라, 취소하기 위해선 모든 객체를 분리해서 모아야 한다.