본 게시글의 Coroutine 개념은 Android 내에서 사용되는 것을 전제로 작성되었다.
#1 다중 스레드 구현
#1-1 코루틴
컴퓨터 과학에서의 멀티태스킹(Multitasking)에는 크게 2가지 종류이 있는데, 첫 번째 종류는 운영체제가 알아서 프로세스들을 전환하는 방법이고, 두 번째 종류는 프로세스 자체에서 스스로의 동작을 제어하는 방법이다. 전자는 운영체제의 스케쥴러가 수행한다. 개발자가 관여하지 않는다. 반면, 후자는 프로세스의 성능 향상을 위해 개발자가 필연적으로 관여해야 한다.
프로세스를 쪼개면, 프로세스의 최소 단위인 스레드가 된다. 두 번째 종류는 어떤 작업이 여러 스레드를 오가며 수행(멀티스레딩)될 수 있게 만든다. 이를 구현하기 위해 개발자는 그 흐름을 명시적인 코드로 짜주어야 한다.
두 번째 종류의 구현을 위한 RxJava, AsyncTask, Executors, HandlerThreads, IntentServices 등 많은 라이브러리들이 있었다. 하지만, 이 라이브러리들은 꽤 오래되었다고 한다. Kotlin의 Coroutines(코루틴)은 또한 두 번째 종류를 구현하기 위한 라이브러리인데, 예전에 쓰였던 라이브러리들에 비해 쉽고 효율적이다.
#1-2 다중 스레드 구현의 필요성
대부분의 스마트폰은 1초(1000밀리초)에 적어도 60번 화면을 갱신(Refresh)한다. 즉 (1000 / 60) = 16.666...밀리초에 적어도 한 번 이상 화면을 Refresh한다. 그러나, 안드로이드 운영체제의 Main Thread는 XML 파일을 인스턴스화하거나 사용자와의 상호작용을 처리하는 등 이미 할 게 너무나도 많다. 이런 상황에서 화면 갱신마저 Main Thread에서 실행된다면, 스마트폰은 굉장히 느려질 것이다. 느린 스마트폰을 누가 살까? 다중 스레딩 구현(멀티스레딩)은 사실 상의 의무라는 이야기다.
#2 다중 스레드의 필요성 체감하기
샘플 앱을 통해 다중 스레드의 필요성을 체감해본다.
#2-1 외관
Count++ 버튼을 누르면 중앙의 숫자가 1씩 커진다. 상단의 Download Data 버튼을 누르면 데이터 다운로드를 흉내낸 작업을 수행한다.
#2-2 MainActivity.kt
// package com.example.coroutinesbasics
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private var count = 0
private lateinit var tvCount: TextView
private lateinit var btnCount: Button
private lateinit var btnDw: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvCount = findViewById(R.id.tvCount)
btnCount = findViewById(R.id.btnCount)
btnDw = findViewById(R.id.btnDw)
btnCount.setOnClickListener {
tvCount.text = count++.toString()
}
btnDw.setOnClickListener {
downloadData()
}
}
private fun downloadData() {
for (i in 1..100000) {
Log.i("interfacer_han", "Downloading data $i in ${Thread.currentThread().name}")
}
}
}
Downlad Button 클릭 리스너는 데이터를 다운로드하는 동작을 '그럴 듯 하게 흉내내어' 구현한다. 데이터를 정말 다운로드 하는 대신, 10만개의 Log를 발생시킨다. 어느 쪽이든 오랜 시간동안 실행되는 작업이라는 점에서 유사하다.
#2-3 동작 확인
원래라면 Count++ 버튼을 누르면 그 위에 있는 숫자가 즉시 증가해야 한다. 하지만, Download Data 버튼을 누른 즉시 바로 Count++ 버튼을 누르면, 몇 초 있다가 Count가 증가된다. 심지어 중간에 안드로이드 시스템이 앱의 버벅임을 감지하고, 앱을 종료시킬 지 대기할 지를 고르는 메시지까지 뜬다.
이유는 이렇다. Download Data 버튼을 누르면 실행되는 10만개의 Log는 "main" 스레드에서 발생된다. 그래서 같은 메인 스레드인 Count++ 버튼의 동작이 우선순위에서 밀려난 것이다.
이런 앱을 PlayStore에 올린다면 좋은 평가를 받을 수 없을 것이다.
#3 Coroutines 사용하기
#2를 수정해 Coroutines으로 멀티스레딩을 구현하고, 결과적으로 앱의 버벅임을 해소해본다.
#3-1 build.gradle.kts (Module)에서 Coroutines 라이브러리 다운로드
plugins {
...
}
android {
...
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC2") // 코루틴
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC2") // 코루틴
...
}
여기에 있는 implementation 구문을 참조하여, 코틀린 라이브러리를 모듈 수준 build.gradle.kts에서 다운로드한다. 이제부터 코루틴 API를 사용할 수 있게 되었다.
#3-2 MainActivity.kt
...
class MainActivity : AppCompatActivity() {
...
private lateinit var btnDwCoroutine: Button
override fun onCreate(savedInstanceState: Bundle?) {
...
btnDwCoroutine = findViewById(R.id.btnDwCoroutine)
...
btnDwCoroutine.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
downloadData()
}
}
}
...
}
Download Data 버튼 외에 Download With Coroutine 버튼을 추가했다. CoroutineScope는 코루틴의 코드가 담길 영역(Scope)를 정하기 위한 인터페이스다. 즉, 코루틴의 포장지다. 해당 코드는 매우 기초적인 Coroutines 구문이다. 더 자세한 설명 혹은 문법은 여기와 여기서 확인한다. 본 게시글에선 문법보단 코루틴이 있고 없고의 차이를 확인하는 데 집중한다.
#3-3 동작 확인
#2-3와 동일한 상황에서 이번엔 Download Data 버튼 대신 Download With Coroutine 버튼을 눌러보았다. 버벅임이 전혀 없이 바로 숫자가 증가한다.
#4 요약
안드로이드에서 멀티스레딩 활용은 필수다. 코루틴은 멀티스레딩을 쉽게 사용하는 방법이다.
#5 완성된 앱
'깨알 개념 > Kotlin' 카테고리의 다른 글
[Kotlin] Coroutines - Suspend vs Block (0) | 2024.02.09 |
---|---|
[Kotlin] Coroutines - 동기 코드, 비동기 코드 (0) | 2024.02.08 |
[Kotlin] 람다(Lambda) 표현식 (0) | 2024.02.01 |
[Kotlin] 함수 타입(Fuction types) 표현식 (0) | 2024.01.31 |
[Kotlin] 함수형 인터페이스 (Single Abstract Method Interface) (0) | 2024.01.30 |