아아...코루틴... 많이 들어봤고 중요한 내용인건 알지만 코루틴이 뭐냐고 물어보면 일목요연하게 답변할 자신은 없다. 하지만 놀랍게도 내가 직접 관련 내용에 대해 정리한 글이 존재한다. ...인간의 기억력이란 뭘까?
비록 기억이 증발했다곤 하나 내가 직접 정리한 글을 보니 무슨 개념이었는지 금방 상기할 수 있었다. 역시 정리글은 꾸준히 써야한다. 게다가 그때는 보이지 않았던 부분도 좀 보이는 듯 하다. 그때는 그냥 음 그렇구나 정도였는데, 이제는 아 이때 그랬던게 그래서였어? 하는 감상도 추가된 그런... 어쨌든 좀 더 코루틴에 집중한 정리글을 이 포스트에 추가로 작성해보자.
비동기 처리란 동시에 여러가지 한다는 뜻이다. 때때로 스레드 여러 개를 두고 비동기적으로 처리를 해야 할 때가 있다. 예를 들어 api 호출 같은 경우가 대표적이다. 특히 저 위에 있는 화성 앱같은 경우에는 이미지를 서버로부터 받아온다. 이미지를 다운로드하는데는 시간이 걸린다. 만약 UI 스레드(메인스레드)에서 다운로드를 하게 된다면 다운로드하는 시간만큼 UI가 멈추게 될 것이다. 그래서 UI 스레드가 아닌 다른 스레드로 다운로드를 처리하고 비동기적으로 결과를 처리해야 한다.
이러한 비동기적인 일처리, 동시 실행 코드 작성을 편하게 하기 위해서 사용되는 실행 설계 패턴이 바로 코루틴이다.
사용 예시를 보자.
private fun getMarsPhotos() {
//launch 함수 호출해 코루틴 실행.
viewModelScope.launch {
try {
//MarsApiService에서 정의한 함수를 호출
val listResult = MarsApi.retrofitService.getPhotos()
//서버에서 받은 결과를 변수에 저장
_status.value = "Success: ${listResult.size} Mars photos retrieved"
}
catch (e: Exception) { //인터넷에 연결 안된 사용자가 튕기지 않도록..
_status.value = "Failure: ${e.message}"
}
}
}
이건 내가 안드로이드 강의 들을때 작성했던 코드이다.
fun getJson(path: String, params: Map<String, Any>, completed: (Result<String>) -> Unit) {
GlobalScope.launch(Dispatchers.IO) {
var queryParams = if (params.isNotEmpty()) params
.map { entry -> "${entry.key}=${entry.value}" }
.reduce { s1, s2 -> "${s1}&${s2}" }
else ""
if (queryParams.isNotEmpty()) queryParams = "?${queryParams}"
val url = if (path.startsWith("http")) URL(path + queryParams)
else URL(baseUrl + path + queryParams)
val json = url.readText(Charsets.UTF_8)
completed(Result.success(json))
}
}
이건 프로그래머스 과제관의 케이묵 문제의 초기 스켈레톤 코드에 포함되어 있는 코드이다.
위 코드를 보면 알겠지만, 코루틴은 launch 혹은 aync로 시작이 가능하다. aync으로 시작하는 건 처음 들어보는데..launch는 상태를 관리할 수 있고, async는 상태를 관리+결과 반환까지 된다고 한다.
위에 보면 launch 앞에 붙은 ~Scope
가 있다. 얘네는 상황에 따라 다르게 골라서 사용하는데, Scope의 종류는 아래와 같다.
코루틴 얘기 나올때마다 suspend
가 꼭 등장하는데... 그 이유는 코루틴의 주요 기능 중 하나가 상태를 저장하여 중단했다가 재개할 수 있다는 것이기 때문이다.
이해를 위해 위 그림을 보자. 상황은 다음과 같다.
비동기 작업을 하다보면 이런 일이 많이 생기는데, 이때 코루틴은 자신의 작업을 일시정지한다. 때문에 일시 중단 할 가능성이 있는 함수는 코루틴 블록 내부에서 수행되어야 한다. 코루틴 블록 내부에서 하기 싫으면? 그때는 suspend
키워드를 붙여서 정지함수로 만들어야 한다.
//기본 URL(Retrofit 빌더에서 정의함)에 엔드포인트 photos를 추가해서 가져옴.
interface MarsApiService {
@GET("photos")
suspend fun getPhotos(): List<MarsPhoto> //변경
//참고-suspend 키워드를 붙여서 정지함수로 만들면 코루틴 내에서 이 메서드 호출 가능
}
화성이미지 예제에서는 위 인터페이스를 만들때 정지함수를 선언하였다.
난 졸업 프로젝트로 자바 기반 안드로이드 앱을 만들 때 아두이노 센서로부터 블루투스 통신으로 값 받아오는 것도 해봤고, 파이어베이스에서 이미지 받아서 띄우는 것도 해봤다. 이거 둘다 비동기 처리가 필요해보이는데...어떻게 했는가?
블루투스 통신하는 건 스레드를 따로 구현했다. 이후 이 스레드를 new 어쩌구스레드()
이렇게 선언할때, 소켓 변수 하나를 파라미터로 넣어줬다. 그래서 워커 스레드는 수시로 소켓의 Input Stream을 확인하며 수신한 값이 있으면 가져오는 역할을 할 수 있었다.
파이어베이스에서 이미지 받아오는 건 glide 라이브러리를 사용했다. 사실 냅다 glide 부터 사용한 건 아니고... 얘도 스레드를 직접 구현해서 하는 방식으로 해봤는데, glide가 로딩되는 속도도 빠르고 코드도 간결해서 glide를 선택했다.
어쨌든 둘 다 코루틴은 안썼다. 의문이 들었다. 자바에는 코루틴이 없나...? 찾아봐도 잘 안나오고, 난 학교 선배 등 물어볼 수 있는 사람 1도 없고 오로지 솔플을 하고 있기에... sns에서 가볍게 물어봤는데 감사하게도 답을 얻을 수 있었다. 자바에는 코루틴 같이 편리하게 쓸 수 있는건 없고, 내가 했던것처럼 직접 구현하거나 프레임워크에서 지원하는 스프링의 Async 같은걸 사용한다고 한다. 코틀린과 자바의 차이점을 또 하나 알게 되었다..
드디어 코루틴에 대해 좀 알게 된 느낌이다. 혹시 까먹더라도, 내가 들은 강의, 내가 쓴 코드와 주석, 내 식대로 바꾼 설명으로 구구절절 열심히 설명했으니....다시 읽어보면 기억이 잘 날것이라고 생각한다.
참고: https://android-uni.tistory.com/m/33
https://prgms.tistory.com/87
https://kotlinworld.com/144