Coroutines
은 비동기적으로 실행되는 코드를 간소화하기 위해 안드로이드에서 사용할 수 있는 동시 실행 설계 패턴입니다.
비동기(Asynchronous) 처리
비동기 처리에서는 병렬적으로 태스크를 수행합니다. 만약 태스크가 종료되지 않은 상태라 하더라도 결과가 나올 때까지 대기하지 않고, 다음 태스크를 실행하게 됩니다.
Coroutines
은 서브 루틴을 일시 정지하고 재개할 수 있는 구성 요소를 말합니다. 실행 중인 스레드를 차단하지 않고, 일시 정지를 지원하므로 단일 스레드에서 많은 코루틴을 실행할 수 있습니다. 또, 개발자가 직접 루틴을 언제 실행할지, 언제 종료할지를 지정할 수 있습니다. 이렇게 생성한 루틴은 작업 전환 시에 시스템의 영향을 받지 않기 때문에 그에 따른 비용이 발생하지 않습니다.
Coroutines 기본적인 사용법
0. 종속성 추가
Coroutines
을 사용하기 위해서는 먼저 app 모듈의 build.gradle에 종속성을 추가해야 합니다.
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}
1. Coroutines Scope
Scope
는 그 Scope
에서 생성된 Coroutines
을 계속해서 주시하면서 실행을 취소하거나, 실패 시 예외를 처리할 수 있도록 합니다. Scope
는 커스텀하거나 또는 이미 내장된 범위를 사용할 수 있습니다.
Scope
는 GlobalScope
와 CoroutineScope
가 존재하며, CoroutineScope
의 경우에는 Dispatcher
를 지정할 수 있습니다.
class MainActivity : AppCompatActivity() {
private val tvCoroutineValue : TextView by lazy {
findViewById(R.id.tvCoroutineValue)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
tvCoroutineValue.text = "Coroutine을 통한 값 넣기 (GlobalScope)"
}
CoroutineScope(Dispatchers.Main).async {
tvCoroutineValue.text = "Coroutine을 통해 값 넣기 (CoroutineScope)"
}
}
}
2. Coroutines Dispatcher 지정
Dispatcher
는 Coroutines
을 적당한 스레드에 할당하며, Coroutines
실행 도중 일시 정지나 실행 재개를 담당합니다.
Default
, IO
, Main
, Unconfined
등이 있습니다.
Default
는 안드로이드 기본 스레드풀을 사용하여 CPU를 많이 사용하는 작업에 최적화되어 있습니다. 보통 데이터 정렬이나 복잡한 연산을 할 때 지정합니다.IO
는 이미지 다운로드나 파일 입출력 등 입출력에 최적화되어 있습니다. 보통 네트워크나 디스크, DB 작업을 할 때 지정합니다.Main
은 안드로이드 기본 스레드에서 실행합니다. 보통 UI나 스레드를 막지 않고 빨리 실행되는 작업을 할 때 지정합니다.Unconfined
는 특정 스레드 또는 특정 스레드풀을 지정하지 않습니다. 일반적으로는 사용하지 않으며 특정 목적을 위해서만 사용됩니다.
class MainActivity : AppCompatActivity() {
private val tvCoroutineValue : TextView by lazy {
findViewById(R.id.tvCoroutineValue)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
tvCoroutineValue.text = "Coroutine을 통한 값 넣기 (GlobalScope)"
}
CoroutineScope(Dispatchers.Main).async {
tvCoroutineValue.text = "Coroutine을 통해 값 넣기 (CoroutineScope)"
}
}
}
2. Coroutines 시작
Coroutines
은 launch
와 async
두 가지 방법 중 하나로 시작할 수 있습니다.
launch
는 현재 스레드를 차단하지 않고, 새로운Coroutines
를 실행하고,Coroutines
을 제거할 때 사용할 수 있습니다. ‘실행 후 삭제’로 간주되는 모든 작업은launch
를 사용하여 시작할 수 있습니다.launch
는Job
객체를 반환하며, 이Job
은launch
로 생성된Coroutines
의 상태를 관리하는 용도로 사용하고, 결과를 반환받을 수 없습니다.async
는 새로운Coroutines
를 시작하고,await
라는 정지 함수를 통해 결과를 반환할 수 있게 허용합니다.async
는Deferred<T>
객체를 반환하며, 이Deferred<T>
는async
블록 내 수행된 결과를 원하는 시점에 반환받을 수 있습니다.Deferred
는Job
을 상속받아 구현되었기 때문에Job
의 기능을 사용할 수 있습니다.
class MainActivity : AppCompatActivity() {
private val tvCoroutineValue: TextView by lazy {
findViewById(R.id.tvCoroutineValue)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
tvCoroutineValue.text = "Coroutine을 통한 값 넣기 (GlobalScope)"
}
CoroutineScope(Dispatchers.Main).async {
tvCoroutineValue.text = "Coroutine을 통해 값 넣기 (CoroutineScope)"
}
}
}
Coroutines Method
cancel
Coroutines
의 동작을 멈추는 상태관리 메서드로 하나의 Scope 안에 여러 Coroutines
이 존재하는 경우 하위 Coroutines
또한 모두 멈춥니다.
class MainActivity : AppCompatActivity() {
private val tvCoroutineValue: TextView by lazy {
findViewById(R.id.tvCoroutineValue)
}
private val btnCoroutineCancel: Button by lazy {
findViewById(R.id.btnCoroutineCancel)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val job = CoroutineScope(Dispatchers.Default).launch {
for(i in 0..10) {
delay(1000)
Log.d("Coroutines", "$i")
}
}
btnCoroutineCancel.setOnClickListener {
job.cancel()
}
}
}
join
Coroutines
내부에 여러 launch
블록이 있는 경우, 모두 새로운 Coroutines
으로 분기되어 동시에 실행되기 때문에 순서를 정할 수 없습니다. 하지만 순서를 정해야 한다면 join
메서드를 통해 순차적으로 실행되도록 코드를 짤 수 있습니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.Default).launch {
launch {
for(i in 0..5) {
delay(1000)
Log.d("Coroutines", "i : $i")
}
}.join()
launch {
for(j in 6..10) {
delay(1000)
Log.d("Coroutines", "j : $j")
}
}
}
}
}
async로 결과값 처리
async
로 Coroutines Scope
의 결과를 받아서 쓸 수 있습니다. 특히, 연산 시간이 오래 걸리는 2개의 네트워크 작업의 경우에는 2개의 작업이 모두 완료되고 나서 이를 처리하기 위해 await
을 사용할 수 있습니다. 이때는 async
작업이 모두 완료되고 나서야 await
코드가 실행됩니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.Default).async {
val deferred1 = async {
delay(1000)
600
}
val deferred2 = async {
delay(1500)
200
}
Log.d("Coroutines", "${deferred1.await()}, ${deferred2.await()}")
}
}
}
suspend
Coroutines
안에서 suspend
함수가 호출될 경우 이전까지의 코드 실행이 멈추며, suspend
함수의 처리가 완료된 후 멈춰 있던 원래 Scope
의 다음 코드가 실행됩니다.
withContext
호출 쪽 Coroutines
은 Dispatchers.Main
으로 UI를 제어하고, suspend
함수에서 네트워크나 DB 작업 등을 하는 경우에는 withContext
를 사용하여 suspend
함수의 Dispatcher
를 IO
로 변경하여 사용할 수 있습니다.
class MainActivity : AppCompatActivity() {
private val tvCoroutineValue: TextView by lazy {
findViewById(R.id.tvCoroutineValue)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(**Dispatchers.Main**).launch {
tvCoroutineValue.text = "Coroutines를 통한 값 넣기(CoroutineScope)"
withContext(Dispatchers.IO) {
// 네트워크, DB 작업 등
}
}
}
}
Job
launch
, async
와 같은 빌더를 호출하면 Job
객체가 반환됩니다. Job
은 Coroutines
의 생명주기를 관리할 수 있고, 내부에서 다시 빌더를 호출하면 자식 Job
이 생성됩니다. 부모 Job
취소 시에는 자식 Job
도 취소되며, 반대의 경우에는 취소되지 않습니다.
하지만 launch
빌더로 생성한 자식 Job
은 예외 발생 시에 부모 Job
을 취소시킵니다. async
빌더로 생성한 자식 Job
은 예외가 발생해도 부모 Job
이 취소되지 않는데 이는 반환 결과에 예외도 포함시키기 때문입니다.
그 외
전체 코드
https://github.com/na-ram/AOS_Coroutines.git
자료 참조
'Programando > Android' 카테고리의 다른 글
[Android/Kotlin] DI(Dependency Injection) (0) | 2022.03.16 |
---|---|
[Android/Kotlin] Stetho (0) | 2022.03.11 |
[Android/Kotlin] ViewBinding (0) | 2022.01.27 |
[Android/Kotlin] Retrofit2를 통해 RestAPI와 통신하기 (2) | 2022.01.13 |
[Android/Kotlin] RecyclerView 사용하기 (0) | 2022.01.12 |