코루틴이란?
✔ 코루틴에서 스레드는 단지 코루틴이 실행되는 공간을 제공하는 역할을 한다.
✔ 실행 중인 스레드를 중단시키지 않기 때문에 하나의 스레드에 여러 개의 코루틴이 존재할 수 있다.
◽ 코루틴 1이 작업을 하는 도중에 코루틴 2로 코드를 넘겨도 코루틴 1만 잠시 멈출 뿐, 공간을 제공한 스레드는 계속 움직인다.
✔ 만약 이러한 구조를 스레드에서 처리한다면 다음과 같다.
◽ 1번에 해당하는 스레드가 잠시 멈추고 2번 스레드가 처리하도록 우선순위를 넘겨준다. 이러한 스레드 간 전환을 컨텍스트 스위칭이라고 하며, 스위칭이 자주 일어날 경우 성능 저하가 발생한다.
코루틴은 이런 컨텍스트 스위칭을 하나의 스레드에서 처리하므로 성능 저하가 적고, 동일한 구조에서는 스레드보다 훨씬 적은 자원을 소모한다.
코루틴 사용하기
코루틴 의존성 설정하기
https://developer.android.com/kotlin/coroutines?hl=ko
코루틴 스코프
글로벌 스코프
✔ 앱의 생명 주기와 함께 동작하기 때문에 앱이 실행되는 동안은 별도의 생명 주기 관리가 필요하지 않다. 만약 앱의 시작부터 종료될 때까지 혹은 장시간 실행되어야 하는 코루틴이 있다면 GlobalScope를 사용하면 된다.
GlobalScope.launch {
}
코루틴 스코프
✔ 버튼을 클릭해서 서버의 정보를 가져오거나 파일을 여는 용도라면 필요할 때만 열고 완료되면 닫는 CoroutineScope를 사용한다.
binding.btnDownload.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
}
}
◽ 글로벌 스코프와 다르게 코루틴 스코프는 괄호 안에 Dispatchers.IO라는 상숫값이 입력되는데, 이는 디스패처이며 코루틴이 실행될 스레드를 지정하는 것이다.
◽ 디스패처의 종류
launch와 상태 관리
✔ 코루틴은 launch와 async로 시작할 수 있다.
✔ launch는 상태를 관리할 수 있고, async는 상태를 관리하고 연산 결과까지 반환한다.
✔ launch는 호출하는 것만으로 코루틴을 생성할 수 있고, 반환되는 job을 변수에 저장해두고 상태 관리용으로 사용할 수 있다.
cancel
✔ 코루틴의 동작을 멈추는 상태 관리 메서드
✔ 하나의 스코프 안에 여러 개의 코루틴이 있다면 하위의 코루틴도 모두 동작을 멈춘다.
val job = CoroutineScope(Dispatchers.Default).launch {
val job1 = launch {
for (i in 0..10) {
delay(500)
Log.d("코루틴", "결과 = $i")
}
}
}
binding.btnStop.setOnClickListener {
job.cancel()
}
join
✔ 코루틴 스코프 안에 선언된 여러 개의 launch 블록이 새로운 코루틴으로 분기되면서 동시에 처리되는 것을 순서를 정하여 순차적으로 실행되도록 해준다.
CoroutineScope(Dispatchers.Default).launch {
launch {
for (i in 0..5) {
delay(500)
Log.d("코루틴", "결과1 = $i")
}
}.join()
launch {
for (i in 0..5) {
delay(500)
Log.d("코루틴", "결과2 = $i")
}
}
}
◽ 코루틴 스코프 안에 2개의 코루틴이 launch로 사용되었는데, join() 메서드로 인해 앞의 코루틴 실행이 완료된 후에 두 번째 코루틴이 실행된다.
async와 반환값 처리
✔ async는 코루틴 스코프의 연산 결과를 받아서 사용할 수 있다.
CoroutineScope(Dispatchers.Default).async {
val deferred1 = async {
delay(500)
350
}
val deferred2 = async {
delay(1000)
200
}
Log.d("코루틴", "연산 결과 = ${deferred1.await() + deferred2.await()}")
}
◽ 시간이 오래 걸리는 2개의 코루틴을 async로 선언하고, 결괏값을 처리하는 곳에서 await 함수를 사용하면 결과 처리가 완료된 후에 await를 호출한 줄의 코드가 실행된다.
suspend
✔ 코루틴 안에서 suspend 키워드로 선언된 함수가 호출되면 이전까지의 코드 실행이 멈추고, suspend 함수의 처리가 완료된 후에 멈춰 있던 원래 스코프의 다음 코드가 실행된다.
suspend fun subRoutine() {
for (i in 0..10) {
Log.d("subRoutine", "$i")
}
}
CoroutineScope(Dispatchers.Main).launch {
// (코드 1)
subRoutine()
// (코드 2)
}
◽ suspend 함수를 만나기 전까지 실행되던 코드가 멈출 때, 부모 루틴의 상태 값을 저장(코드 1) -> suspend 함수 실행과 종료 -> 부모 루틴의 상태 값을 복원(코드 2)의 과정을 거치기 때문에 스레드에는 영향을 주지 않는다.
◽ subRoutine()은 suspend 키워드를 붙였기 때문에 CoroutineScope 안에서 자동으로 백그라운드 스레드처럼 동작한다.
withContext로 디스패처 분리
✔ suspend 함수를 코루틴 스코프에서 호출할 때 호출한 스코프와 다른 디스패처를 사용할 때가 있다.
suspend fun readFile(): String {
return "파일 내용"
}
CoroutineScope(Dispatchers.Main).launch {
// 화면 처리
val result = withContext(Dispatchers.IO) {
readFile()
}
Log.d("코루틴", "파일 결과 = $result")
}
◽ 호출 측 코루틴은 Main 디스패처에서 UI를 제어하는데, 호출되는 suspend 함수는 디스크에서 파일을 읽어와야 하는 경우
◽ 호출되는 suspend 함수에 반환 값이 있다면 변수에 저장하고 사용할 수도 있다.
이미지 다운로드 앱 만들기
MainActivity.kt
class MainActivity : AppCompatActivity() {
val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.run {
buttonDownload.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
progress.visibility = View.VISIBLE
val url = editUrl.text.toString()
val bitmap = withContext(Dispatchers.IO) {
loadImage(url)
}
imagePreview.setImageBitmap(bitmap)
progress.visibility = View.GONE
}
}
}
}
}
suspend fun loadImage(imageUrl:String) : Bitmap {
val url = URL(imageUrl)
val stream = url.openStream()
return BitmapFactory.decodeStream(stream)
}
activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/buttonDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:text="다운로드"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<EditText
android:id="@+id/editUrl"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="16dp"
android:ems="10"
android:hint="여기에 URL을 입력하세요"
android:inputType="textPersonName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/buttonDownload"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/imagePreview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/editUrl"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
|
cs |
'Android > Concept' 카테고리의 다른 글
[Android] [Kotlin] 콘텐트 리졸버 (0) | 2021.09.23 |
---|---|
[Android] [Kotlin] 서비스 (0) | 2021.09.22 |
[Android] [Kotlin] 스레드와 루퍼 (0) | 2021.09.20 |
[Android] [Kotlin] Room 데이터베이스 (0) | 2021.09.19 |
[Android] [Kotlin] SharedPreferences (0) | 2021.09.19 |