메인 스레드에서 네트워크 요청은 차단으로 인해 ANR을 발생할 수 있습니다. 그러기 때문에, 백그라운드에서 스레드를 실행합니다.
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
// 현재 스레드를 차단하는 네트워크 요청을 생성하는 함수
fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
LoginRepository의 makeLoginRequest가 네트워크 동기식 호출로 호출 스레드를 차단합니다.
네트워크 응답 요청을 위해 Result 클래스가 사용됩니다.
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
LoginViewModel 내에서 사용자 버튼 클릭 후 네트워크 요청을 수행합니다.
네트워크 요청 시 UI 스레드를 차단합니다.
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// UI thread의 실행을 옮기기 위한 새로운 Coroutine 생성
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
메인 스레드에서 실행을 이동하는 코드는 새 Coroutine을 생성하여 I/O 스레드에서 네트워크 요청을 실행하는 것입니다.
viewModelScope.launch : Coroutine을 생성하고, 함수 본문의 실행을 디스패처로 전달합니다
Dispatchers.IO : I/O 작업을 위해 예약된 스레드에서 실행되어야 한다는 의미입니다.
login 함수 상세
View 계층으로부터 login 함수는 메인 스레드에서 실행됩니다.
launch는 새로운 coroutine을 생성하고, 네트워크 요청이 I/O 명령을 위한 예약된 스레드위에서 독립적으로 이루어집니다.
Coroutine이 실행되는 동안 login 함수는 실행을 계속하고, 네트워크 요청이 완료되기 전에 리턴합니다. (지금은 네트워크 응답이 무시되는 코드)
viewModelScope에서 시작되므로, ViewModel 안에서 실행됩니다.
ViewModel이 Destroy 된다면, viewModelScope는 자동으로 취소되고, 모든 coroutine도 취소됩니다.
현재 코드에서는 makeLoginRequest 함수를 메인스레드 실행을 옮겨야 합니다.
변경 후 : Main-Safety를 위한 Coroutine 사용
main-safe : 메인 스레드에서 UI 업데이트를 차단하지 않게 하는 것
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// 코루틴 실행을 Dispatchers로 이동
return withContext(Dispatchers.IO) {
// 네트워크 요청 코드 차단
}
}
}
다른 스레드로 Coroutine의 실행을 이동하려면 Coroutine 라이브러리의 withContext() 을 사용합니다.
withContext(Dispatchers.IO) 는 I/O 스레드로 Coroutine 실행을 이동합니다. 필요에 따라 UI 업데이트도 가능합니다.
makeLoginRequest의 suspend 사용은 Coroutine 내에서 호출되는 함수를 강제하기 위한 Kotlin의 특징입니다.
makeLoginRequest를 메인스레드 실행에서 옮기면서, login 함수의 Coroutine을 메인스레드에서 실행할 수 있습니다.
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// UI thread에서 새로운 coroutine 생성
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// 작업이 끝날때 까지, suspend 실행과 네트워크 호출을 생성합니다.
val result = loginRepository.makeLoginRequest(jsonBody)
// 사용자에 네트워크 요청의 결과를 출력합니다.
when (result) {
is Result.Success<LoginResponse> ->
else ->
}
}
}
}
makeLoginRequest는 suspend 함수이고, Coroutine에서 실행되어야 하므로 Coroutine은 여전히 필요합니다.
이전 코드와 비교
launch는 Dispatchers.IO 파라미터를 사용하지 않습니다. (launch가 Dispatcher를 전달하지 않을 때, viewModelScope에서 모든 Coroutine은 메인 스레드에서 실행됩니다.
네트워크 요청 성공, 실패 결과를 UI에서 처리하게 됩니다.
login 함수는
메인스레드 View 계층에서 실행됩니다.
launch는 새로운 Coroutine을 생성하고, 실행을 시작합니다.
Coroutine 내에서 loginRepository.makeLoginRequest()는 makeLoginRequest()의 withContext 블록 실행이 완료 될때 까지 Coroutine의 추가 실행을 일시 중단 합니다.
[TIL-230625] Android Coroutine 알아보기
안녕하세요
오늘은 Kotlin의 Coroutine에 대해서 간략하게 포스팅 해보겠습니다.
비동기로 실행되는 코드를 단순화하기 위해 Android에서 사용할 수 있는 동시성 디자인 패턴
특징
* 구조화된 동시성
종속성
변경 전 : 백그라운드 스레드에서 실행
메인 스레드에서 네트워크 요청은 차단으로 인해 ANR을 발생할 수 있습니다. 그러기 때문에, 백그라운드에서 스레드를 실행합니다.
(지금은 네트워크 응답이 무시되는 코드)
변경 후 : Main-Safety를 위한 Coroutine 사용
main-safe : 메인 스레드에서 UI 업데이트를 차단하지 않게 하는 것
makeLoginRequest를 메인스레드 실행에서 옮기면서, login 함수의 Coroutine을 메인스레드에서 실행할 수 있습니다.
(launch가 Dispatcher를 전달하지 않을 때, viewModelScope에서 모든 Coroutine은 메인 스레드에서 실행됩니다.
이번에는 Kotlin의 Coroutine 및 사용법에 대해 알아보았습니다
잘못된 내용이 있다면 댓글 부탁드리고, 내용이 좋았다면 공감, 구독 부탁드려요!
'DEV > Android' 카테고리의 다른 글