본 게시글의 Coroutine 개념은 Android 내에서 사용되는 것을 전제로 작성되었다.
#1 Background Thread의 한계 - UI 조작 불가능
...
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
...
btnDwCoroutine.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
downloadData()
}
}
}
private fun downloadData() {
for (i in 1..100000) {
tvProgress.text = "진행도: $i" // 에러 발생
}
}
}
/* 에러 메시지
android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
Expected: main
Calling: DefaultDispatcher-worker-1
*/
위 코드는 "뷰 계층 구조를 만든 원래 스레드만 해당 뷰를 조작할 수 있습니다."라는 에러 메시지를 뱉는 에러를 발생시킨다. UI를 다루는 객체인 Activity는 main 스레드에서 만들어진다. 그러나 downloadData()는 IO 스레드에서 수행된다. main 스레드와 달리, UI에 대한 접근 권한이 없는 IO 스레드에서 TextView를 조작할 수 없는 것이다. 안드로이드에서는 메인 스레드 이외의 스레드를 일반적으로 '백그라운드 스레드'라고 부른다. 정리하면, 백그라운드 스레드에선 UI를 조작할 수 없다는 결론이 나온다.
즉 위 코드가 작동되게끔 수정하려면, UI를 조작할 때만 잠시 main 스레드로 전환하게 만들어야 한다.
#2 스레드 전환 구현
#2-1 수정할 샘플 앱
위 게시글의 '완성된 앱'을 수정해서, 스레드를 전환하는 동작을 구현해본다.
#2-2 activity_main.xml에 TextView 추가
'Download Data' 및 'Download With Coroutine' 버튼 아래에, android:id="@+id/tvProgress"인 TextView 위젯을 추가한다. 그리고 'Download Data' 버튼은 이제 사용하지 않을 것이므로 삭제한다.
#2-3 MainActivity.kt에서 위젯 인플레이트
...
class MainActivity : AppCompatActivity() {
...
private lateinit var tvProgress: TextView
override fun onCreate(savedInstanceState: Bundle?) {
...
tvProgress = findViewById(R.id.tvProgress)
...
}
...
}
#2-2에서 추가한 텍스트뷰인 'tvProgress'를 inflate한다. 'Download Data' 버튼과 관련된 코드는 삭제한다.
#2-4 MainActivity.kt에서 withContext() 사용하기
private suspend fun downloadData() {
for (i in 1..100000) {
withContext(Dispatchers.Main) {
tvProgress.text = "진행도: $i"
}
}
}
downloadData() 함수에 #1에서 에러가 났었던 코드 tvProgress.text = "진행도: $i"를 넣고 withContext(Dispatchers.Main) { ... }로 감싼다. withContext()는 코루틴의 실행 스레드를 변경하는 함수다. withContext()는 Suspending Function이고, Suspending Function은 Suspend Function에서만 호출할 수 있으므로, downloadData()에 suspend 키워드를 붙여준다.
참고로 withContext { ... } 블록은 별도의 임시 CoroutineScope를 생성한다. 블록 내에서 실행되는 코드는 이 CoroutineScope에서 실행되는데, 이 말은 즉 withContext는 Structured Concurrency가 보장된다는 말이 된다.
#2-5 작동 확인
#3 요약
withContext()로 필요할 때만 잠시 스레드를 전환할 수 있다.
#4 완성된 앱
https://github.com/Kanmanemone/android-practice/tree/master/coroutines/ThreadSwitch
'깨알 개념 > Kotlin' 카테고리의 다른 글
[Kotlin] 위임 프로퍼티 (Delegated properties) (0) | 2024.07.22 |
---|---|
[Kotlin] Coroutines - ViewModelScope (0) | 2024.02.20 |
[Kotlin] Coroutines - Parallel Decomposition (0) | 2024.02.17 |
[Kotlin] Coroutines - Structured Concurrency (0) | 2024.02.16 |
[Kotlin] Couroutine - 스레드(Thread)와 스레드 풀(Thread Pool) (0) | 2024.02.15 |