-
안드로이드 앱 개발 연습 - 12 | CoroutineArchive/캡스톤디자인 2022. 4. 4. 17:21
Coroutine
Coroutine
안드로이드에서 사용하는 스레드를 경량화한 새로운 도구
하나의 스레드에 여러 개의 코루틴이 존재할 수 있으며, 작업이 각 코루틴으로 넘어가더라도 해당 공간을 제공하는 스레드는 멈추지 않고 계속 움직일 수 있음build.gradle (:app)
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" ... }
Coroutine Scope
GlobalScope.launch { // 여기에 작성된 코드가 코루틴으로 실행됨 } CoroutineScope(Dispatcher).launch { // 여기에 작성된 코드가 코루틴으로 실행됨 }
GlobalScope
앱의 생명 주기와 함께 동작하는 코루틴으로, 앱이 실행되는 동안은 별도의 생명 주기 관리가 필요하지 않음.
앱의 시작부터 종료까지 사용해야 하는 코루틴을 작성할 때 주로 사용.CoroutineScope
특정 행동에 대한 결과를 처리하기 위해 사용하는 코루틴으로, 목적을 완료한 후에는 종료되는 코루틴을 작성할 때 주로 사용. 인수로 Dispatcher라는 상수값을 사용하는데, 이를 이용해 해당 코루틴이 실행될 스레드를 지정함.Dispatcher
Dispatcher Description Dispatchers.Default CPU를 많이 사용하는 작업에 최적화되어 있는 디스패처 Dispatchers.IO 입출력에 최적화되어 있는 디스패처 Dispatchers.Main 기본 스레드에서 코루틴을 실행하고, UI와의 상호작용에 최적화되어 있는 디스패처 Dispatchers.Unconfined 특수한 컨텍스트 (잘 모름)
Coroutine 실행 (launch & async)
코루틴의 실행을 위해서는 launch 또는 async를 이용할 수 있다. 단순히 코루틴의 상태관리만 제어하기 위해서는 launch를 사용하면 되고, 코루틴의 상태관리 뿐 아니라 연산 결과까지 반환받기 위해서는 async를 사용한다.
launch
CoroutineScope(Dispatchers.Default).launch { for (i in 0..5) { delay(500) Log.d("coroutine", "result: $i") }
async
CoroutineScope(Dispatchers.Default).async { val waiting1 = async { delay(500) 100 } val waiting2 = async { delay(1000) 200 } Log.d("coroutine", "result: ${waiting1.await()}, ${waiting2.await()}" }
Coroutine 상태관리
cancel
코루틴의 동작을 멈추는 상태 관리 메서드. 하나의 스코프 안에 여러 개의 코루틴이 있다면, 하위의 코루틴도 모두 동작을 멈춘다.
val job = CoroutineScope(Dispatchers.Default).launch { val job2 = launch { for (i in 0..10) { delay(500) } } } binding.btnStop.setOnClickListener { job.cancel() }
join
코루틴 스코프 안에 선언된 여러 개의 lauch블록은 모두 새로운 코루틴으로 동시에 처리되기 때문에 순서를 정할 수 없다. 이때 join메서드를 사용하면 각각의 코루틴을 순차적으로 실행할 수 있다.
CoroutineScope(Dispatchers.Default).launch { launch { for (i in 0..3) { delay(500) Log.d("coroutine", "first: $i") } }.join() launch { for (i in 0..3) { delay(500) Log.d("coroutine", "second: $i") } } }
suspend 키워드
코루틴 안에서 suspend 키워드로 선언된 함수가 호출되면 이전까지의 코드 실행이 모두 멈추고, suspend 함수의 처리가 완료된 후에 멈춰 있던 원래 다시 스코프가 동작한다. 단, 이때에도 스레드의 중단은 없다.
suspend fun subRoutine() { for (i in 0..10) { delay(200) Log.d("coroutine", "result: $i") } } CoroutineScope(Dispatchers.Main).launch { Log.d("coroutine", "first") subRoutine() Log.d("coroutine", "second") }
withContext
suspend 함수를 코루틴 스코프 내에서 호출할 때, 호출한 스코프와 다른 디스패처를 사용해야 하는 경우 withContext를 이용해 다른 디스패처를 사용할 수 있다.
suspend fun readFile(): String { ... return result } CoroutineScope(Dispatchers.Main).launch { ... val result = withContext(Dispatchers.IO) { readFile() } ... }
Image Download App (예시)
build.gradle (:app)
... android { buildFeatures { viewBinding true } ... dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" ... }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.pokycookie.coroutine"> <uses-permission android:name="android.permission.INTERNET" /> ... </manifest>
MainActivity.kt
class MainActivity : AppCompatActivity() { val binding by lazy { ActivityMainBinding.inflate(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) binding.btnDownload.setOnClickListener { CoroutineScope(Dispatchers.Main).launch { binding.progressBar.visibility = View.VISIBLE val url = binding.inputURL.text.toString() val bitmap = withContext(Dispatchers.IO) { loadImage(url) } binding.imageView.setImageBitmap(bitmap) binding.progressBar.visibility = View.GONE } } } } suspend fun loadImage(imageUrl: String): Bitmap { val url = URL(imageUrl) val stream = url.openStream() return BitmapFactory.decodeStream(stream) }