이 Codelab은 Android Kotlin 기초 과정의 일부입니다. Codelab을 순서대로 진행하면 이 과정의 학습 효과를 극대화할 수 있습니다. 모든 과정 Codelab은 Android Kotlin 기본사항 Codelab 방문 페이지에 나열되어 있습니다.
소개
앱의 완벽한 사용자 환경을 만드는 데 있어 최우선 순위 중 하나는 UI가 항상 응답성이 있고 원활하게 실행되도록 하는 것입니다. UI 성능을 개선하는 한 가지 방법은 데이터베이스 작업과 같은 장기 실행 작업을 백그라운드로 이동하는 것입니다.
이 Codelab에서는 Kotlin 코루틴을 사용하여 기본 스레드에서 데이터베이스 작업을 실행하여 TrackMySleepQuality 앱의 사용자 대상 부분을 구현합니다.
기본 요건
다음을 잘 알고 있어야 합니다.
- 활동, 프래그먼트, 뷰, 클릭 핸들러를 사용하여 기본 사용자 인터페이스 (UI)를 빌드합니다.
- 프래그먼트 간 탐색 및
safeArgs
를 사용하여 프래그먼트 간에 간단한 데이터 전달 - 모델, 모델 팩토리, 변환,
LiveData
를 확인합니다. Room
데이터베이스를 만들고, DAO를 만들고, 항목을 정의하는 방법- 스레딩 및 멀티프로세싱 개념에 익숙하면 도움이 됩니다.
학습할 내용
- Android에서 스레드가 작동하는 방식
- Kotlin 코루틴을 사용하여 데이터베이스 작업을 기본 스레드에서 떨어뜨려 놓는 방법
TextView
에 형식이 지정된 데이터를 표시하는 방법
실습할 내용
- TrackMySleepQuality 앱을 확장하여 데이터베이스에서 데이터를 수집, 저장, 표시합니다.
- 코루틴을 사용하여 백그라운드에서 장기 실행 데이터베이스 작업을 실행합니다.
LiveData
를 사용하여 탐색과 스낵바 표시를 트리거합니다.LiveData
를 사용하여 버튼을 사용 설정 및 중지합니다.
이 Codelab에서는 TrackMySleepQuality 앱의 뷰 모델, 코루틴, 데이터 표시 부분을 빌드합니다.
앱에는 프래그먼트로 표현되는 두 개의 화면이 있습니다(아래 그림 참고).
왼쪽에 표시된 첫 번째 화면에는 추적을 시작하고 중지하는 버튼이 있습니다. 화면에는 사용자의 모든 수면 데이터가 표시됩니다. 지우기 버튼은 앱이 사용자를 위해 수집한 모든 데이터를 완전히 삭제합니다.
오른쪽에 표시된 두 번째 화면은 수면의 질 평가를 선택하는 화면입니다. 앱에서 평점은 숫자로 표시됩니다. 개발 목적으로 앱은 얼굴 아이콘과 숫자 값을 모두 표시합니다.
사용자의 흐름은 다음과 같습니다.
- 사용자가 앱을 열면 수면 추적 화면이 표시됩니다.
- 사용자가 시작 버튼을 탭합니다. 시작 시간을 기록하고 표시합니다. 시작 버튼은 사용 중지되고 중지 버튼은 사용 설정됩니다.
- 사용자가 중지 버튼을 탭합니다. 이렇게 하면 종료 시간이 기록되고 수면의 질 화면이 열립니다.
- 사용자가 수면의 질 아이콘을 선택합니다. 화면이 닫히고 추적 화면에 수면 종료 시간과 수면의 질이 표시됩니다. 중지 버튼은 사용 중지되고 시작 버튼은 사용 설정됩니다. 앱이 또 다른 밤을 맞이할 준비가 되었습니다.
- 데이터베이스에 데이터가 있으면 언제든지 지우기 버튼이 사용 설정됩니다. 사용자가 지우기 버튼을 탭하면 모든 데이터가 복구할 수 없이 삭제됩니다. '정말로 지우시겠습니까?' 메시지가 표시되지 않습니다.
이 앱은 전체 아키텍처의 컨텍스트에서 아래와 같이 간소화된 아키텍처를 사용합니다. 앱은 다음 구성요소만 사용합니다.
- UI 컨트롤러
- 모델 및
LiveData
보기 - Room 데이터베이스
이 작업에서는 TextView
를 사용하여 서식이 지정된 수면 추적 데이터를 표시합니다. (이것은 최종 인터페이스가 아닙니다. 다른 Codelab에서 더 나은 방법을 알아봅니다.)
이전 Codelab에서 빌드한 TrackMySleepQuality 앱을 계속 사용하거나 이 Codelab의 시작 앱을 다운로드하면 됩니다.
1단계: 시작 앱 다운로드 및 실행
- GitHub에서 TrackMySleepQuality-Coroutines-Starter 앱을 다운로드합니다.
- 앱을 빌드하고 실행합니다. 앱에
SleepTrackerFragment
프래그먼트의 UI가 표시되지만 데이터는 표시되지 않습니다. 버튼을 눌러도 반응하지 않습니다.
2단계: 코드 검사
이 Codelab의 시작 코드는 6.1. Room 데이터베이스 만들기 Codelab의 솔루션 코드와 같습니다.
- res/layout/activity_main.xml 이 레이아웃에는
nav_host_fragment
프래그먼트가 포함되어 있습니다.<merge>
태그도 확인하세요.merge
태그를 사용하면 레이아웃을 포함할 때 중복 레이아웃을 제거할 수 있으므로 사용하는 것이 좋습니다. 중복 레이아웃의 예로는 ConstraintLayout > LinearLayout > TextView가 있습니다. 여기서 시스템은 LinearLayout을 삭제할 수 있습니다. 이러한 최적화를 통해 뷰 계층 구조를 간소화하고 앱 성능을 개선할 수 있습니다. - navigation 폴더에서 navigation.xml을 엽니다. 두 프래그먼트와 이를 연결하는 탐색 작업이 표시됩니다.
- layout 폴더에서 수면 추적기 프래그먼트를 더블클릭하여 XML 레이아웃을 확인합니다. 다음 사항에 유의하세요.
- 데이터 결합을 사용 설정하기 위해 레이아웃 데이터가
<layout>
요소로 래핑됩니다. ConstraintLayout
및 기타 뷰는<layout>
요소 내에 배치됩니다.- 파일에 자리표시자
<data>
태그가 있습니다.
스타터 앱은 UI의 크기, 색상, 스타일도 제공합니다. 앱에는 Room
데이터베이스, DAO, SleepNight
항목이 포함되어 있습니다. 이전 Codelab을 완료하지 않은 경우 코드의 이러한 측면을 직접 살펴보세요.
이제 데이터베이스와 UI가 있으므로 데이터를 수집하고, 데이터베이스에 데이터를 추가하고, 데이터를 표시해야 합니다. 이 모든 작업은 뷰 모델에서 실행됩니다. 수면 추적기 뷰 모델은 버튼 클릭을 처리하고, DAO를 통해 데이터베이스와 상호작용하며, LiveData
을 통해 UI에 데이터를 제공합니다. 모든 데이터베이스 작업은 기본 UI 스레드에서 벗어나 실행되어야 하고 코루틴을 사용하면 됩니다.
1단계: SleepTrackerViewModel 추가
- sleeptracker 패키지에서 SleepTrackerViewModel.kt를 엽니다.
- 시작 앱에 제공되고 아래에도 표시된
SleepTrackerViewModel
클래스를 검사합니다. 클래스가AndroidViewModel()
를 확장합니다. 이 클래스는ViewModel
와 동일하지만 애플리케이션 컨텍스트를 매개변수로 가져와 속성으로 사용할 수 있도록 합니다. 이 값은 나중에 필요합니다.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
2단계: SleepTrackerViewModelFactory 추가
- sleeptracker 패키지에서 SleepTrackerViewModelFactory.kt를 엽니다.
- 아래에 표시된 팩토리에 제공된 코드를 살펴봅니다.
class SleepTrackerViewModelFactory(
private val dataSource: SleepDatabaseDao,
private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
return SleepTrackerViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
다음 사항에 유의합니다.
- 제공된
SleepTrackerViewModelFactory
은ViewModel
과 동일한 인수를 사용하고ViewModelProvider.Factory
을 확장합니다. - 팩토리 내에서 코드는 클래스 유형을 인수로 사용하고
ViewModel
를 반환하는create()
를 재정의합니다. create()
본문에서 코드는 사용 가능한SleepTrackerViewModel
클래스가 있는지 확인하고, 있는 경우 인스턴스를 반환합니다. 그렇지 않으면 코드에서 예외가 발생합니다.
3단계: SleepTrackerFragment 업데이트
SleepTrackerFragment
에서 애플리케이션 컨텍스트에 대한 참조를 가져옵니다. 참조를binding
아래의onCreateView()
에 넣습니다. 이 프래그먼트가 연결된 앱을 참조하여 뷰 모델 팩토리 제공자에 전달해야 합니다.requireNotNull
Kotlin 함수는 value가null
인 경우IllegalArgumentException
를 발생시킵니다.
val application = requireNotNull(this.activity).application
- DAO 참조를 통해 데이터 소스를 참조해야 합니다.
onCreateView()
에서return
앞에dataSource
를 정의합니다. 데이터베이스의 DAO에 대한 참조를 가져오려면SleepDatabase.getInstance(application).sleepDatabaseDao
을 사용합니다.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
onCreateView()
에서return
앞에viewModelFactory
의 인스턴스를 만듭니다.dataSource
및application
을 전달해야 합니다.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- 이제 팩토리가 있으므로
SleepTrackerViewModel
참조를 가져옵니다.SleepTrackerViewModel::class.java
매개변수는 이 객체의 런타임 Java 클래스를 참조합니다.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- 완성된 코드는 다음과 같이 표시됩니다.
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
지금까지의 onCreateView()
메서드는 다음과 같습니다.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_sleep_tracker, container, false)
val application = requireNotNull(this.activity).application
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
return binding.root
}
4단계: 뷰 모델의 데이터 결합 추가
기본 ViewModel
가 준비되었으므로 SleepTrackerFragment
에서 데이터 결합 설정을 완료하여 ViewModel
를 UI와 연결해야 합니다.
fragment_sleep_tracker.xml
레이아웃 파일에서 다음을 실행합니다.
<data>
블록 내에서SleepTrackerViewModel
클래스를 참조하는<variable>
을 만듭니다.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
SleepTrackerFragment
에서:
- 현재 활동을 바인딩의 수명 주기 소유자로 설정합니다.
onCreateView()
메서드 내에서return
문 앞에 다음 코드를 추가합니다.
binding.setLifecycleOwner(this)
sleepTrackerViewModel
결합 변수를sleepTrackerViewModel
에 할당합니다.SleepTrackerViewModel
을 만드는 코드 아래의onCreateView()
에 다음 코드를 넣습니다.
binding.sleepTrackerViewModel = sleepTrackerViewModel
- 바인딩 객체를 다시 만들어야 하므로 오류가 표시될 수 있습니다. 프로젝트를 정리하고 다시 빌드하여 오류를 없앱니다.
- 마지막으로 언제나처럼 코드가 오류 없이 빌드되고 실행되는지 확인합니다.
Kotlin에서 코루틴은 장기 실행 작업을 원활하고 효율적으로 처리하는 방법입니다. Kotlin 코루틴을 사용하면 콜백 기반 코드를 순차 코드로 변환할 수 있습니다. 순차적으로 작성된 코드는 일반적으로 읽기가 더 쉬우며 예외와 같은 언어 기능을 사용할 수도 있습니다. 결국 코루틴과 콜백은 동일한 작업을 합니다. 즉, 장기 실행 작업에서 결과를 사용할 수 있을 때까지 기다렸다가 실행을 계속합니다.
코루틴에는 다음과 같은 속성이 있습니다.
- 코루틴은 비동기이며 비차단 형태이며
- 코루틴은 suspend 함수를 사용하여 비동기 코드를 순차 코드로 만듭니다.
코루틴은 비동기입니다.
코루틴은 프로그램의 기본 실행 단계와 독립적으로 실행됩니다. 이는 병렬로 또는 별도의 프로세서에서 실행될 수 있습니다. 앱의 나머지 부분이 입력을 기다리는 동안 약간의 처리를 몰래 수행할 수도 있습니다. 비동기의 중요한 측면 중 하나는 결과를 명시적으로 기다릴 때까지 결과를 사용할 수 있다고 예상할 수 없다는 것입니다.
예를 들어 조사가 필요한 질문이 있고 동료에게 답을 찾아 달라고 요청한다고 가정해 보겠습니다. 이후에 가서 작업을 수행하는 것은 '비동기식'으로 '별도의 스레드에서' 작업을 수행하는 것과 같습니다. 동료가 돌아와서 답을 알려줄 때까지 답과 관련이 없는 다른 작업을 계속할 수 있습니다.
코루틴은 비차단입니다.
차단되지 않음 은 코루틴이 기본 스레드나 UI 스레드를 차단하지 않음을 의미합니다. 따라서 코루틴을 사용하면 UI 상호작용이 항상 우선순위를 가지므로 사용자는 항상 가장 원활한 환경을 경험할 수 있습니다.
코루틴은 정지 함수를 사용하여 비동기 코드를 순차 코드로 만듭니다.
suspend
키워드는 함수 또는 함수 유형을 코루틴에서 사용할 수 있는 것으로 표시하는 Kotlin의 방법입니다. 코루틴이 suspend
로 표시된 함수를 호출하면 일반 함수 호출처럼 함수가 반환될 때까지 차단되는 대신 결과가 준비될 때까지 코루틴 실행이 정지됩니다. 그러면 코루틴이 결과와 함께 중단된 지점에서 다시 시작됩니다.
코루틴이 정지되어 결과를 기다리는 동안 코루틴이 실행되는 스레드의 차단이 해제됩니다. 이렇게 하면 다른 함수나 코루틴이 실행될 수 있습니다.
suspend
키워드는 코드가 실행되는 스레드를 지정하지 않습니다. 정지 함수는 백그라운드 스레드 또는 기본 스레드에서 실행될 수 있습니다.
Kotlin에서 코루틴을 사용하려면 다음 세 가지가 필요합니다.
- 작업
- 디스패처
- 범위
작업: 기본적으로 작업은 취소할 수 있는 모든 것입니다. 모든 코루틴에는 작업이 있으며 작업을 사용하여 코루틴을 취소할 수 있습니다. 작업은 상위-하위 계층 구조로 정렬할 수 있습니다. 상위 작업을 취소하면 작업의 모든 하위 항목이 즉시 취소되므로 각 코루틴을 수동으로 취소하는 것보다 훨씬 편리합니다.
디스패처: 디스패처는 다양한 스레드에서 실행되도록 코루틴을 전송합니다. 예를 들어 Dispatcher.Main
는 기본 스레드에서 작업을 실행하고 Dispatcher.IO
는 차단 I/O 작업을 공유 스레드 풀로 오프로드합니다.
범위: 코루틴의 범위는 코루틴이 실행되는 컨텍스트를 정의합니다. 스코프는 코루틴의 작업 및 디스패처에 관한 정보를 결합합니다. 범위는 코루틴을 추적합니다. 코루틴을 실행하면 '범위 내'에 있게 됩니다. 즉, 코루틴을 추적할 범위를 지정한 것입니다.
사용자가 다음과 같은 방식으로 수면 데이터와 상호작용할 수 있도록 하려고 합니다.
- 사용자가 시작 버튼을 탭하면 앱에서 새로운 수면 밤을 만들고 수면 밤을 데이터베이스에 저장합니다.
- 사용자가 중지 버튼을 탭하면 앱이 종료 시간으로 밤을 업데이트합니다.
- 사용자가 지우기 버튼을 탭하면 앱이 데이터베이스의 데이터를 삭제합니다.
이러한 데이터베이스 작업은 시간이 오래 걸릴 수 있으므로 별도의 스레드에서 실행해야 합니다.
1단계: 데이터베이스 작업을 위한 코루틴 설정
Sleep Tracker 앱의 Start 버튼을 탭하면 SleepTrackerViewModel
에서 함수를 호출하여 SleepNight
의 새 인스턴스를 만들고 데이터베이스에 인스턴스를 저장해야 합니다.
버튼을 탭하면 SleepNight
생성 또는 업데이트와 같은 데이터베이스 작업이 트리거됩니다. 이러한 이유로 코루틴을 사용하여 앱 버튼의 클릭 핸들러를 구현합니다.
- 앱 수준
build.gradle
파일을 열고 코루틴의 종속 항목을 찾습니다. 코루틴을 사용하려면 다음 종속 항목이 필요하며, 이는 자동으로 추가되었습니다.$coroutine_version
은 프로젝트build.gradle
파일에coroutine_version =
'1.0.0'
로 정의되어 있습니다.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
SleepTrackerViewModel
파일을 엽니다.- 클래스의 본문에서
viewModelJob
을 정의하고Job
의 인스턴스를 할당합니다. 이viewModelJob
을 사용하면 뷰 모델이 더 이상 사용되지 않고 소멸될 때 이 뷰 모델에서 시작된 모든 코루틴을 취소할 수 있습니다. 이렇게 하면 반환할 곳이 없는 코루틴이 발생하지 않습니다.
private var viewModelJob = Job()
- 클래스 본문 끝에서
onCleared()
를 재정의하고 모든 코루틴을 취소합니다.ViewModel
가 소멸되면onCleared()
가 호출됩니다.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
viewModelJob
정의 바로 아래에 코루틴의uiScope
을 정의합니다. 스코프는 코루틴이 실행될 스레드를 결정하며 스코프는 작업에 대해서도 알아야 합니다. 범위를 가져오려면CoroutineScope
인스턴스를 요청하고 디스패처와 작업을 전달합니다.
Dispatchers.Main
를 사용하면 uiScope
에서 실행된 코루틴이 기본 스레드에서 실행됩니다. 이는 ViewModel
로 시작된 많은 코루틴에 적합합니다. 이러한 코루틴은 일부 처리를 실행한 후 UI를 업데이트하기 때문입니다.
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
uiScope
정의 아래에 현재 밤을 저장할tonight
이라는 변수를 정의합니다. 데이터를 관찰하고 변경할 수 있어야 하므로 변수를MutableLiveData
로 만듭니다.
private var tonight = MutableLiveData<SleepNight?>()
tonight
변수를 최대한 빨리 초기화하려면tonight
정의 아래에init
블록을 만들고initializeTonight()
를 호출합니다. 다음 단계에서initializeTonight()
를 정의합니다.
init {
initializeTonight()
}
init
블록 아래에initializeTonight()
를 구현합니다.uiScope
에서 코루틴을 실행합니다. 내부에서getTonightFromDatabase()
를 호출하여 데이터베이스에서tonight
값을 가져오고tonight.value
에 값을 할당합니다. 다음 단계에서getTonightFromDatabase()
를 정의합니다.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
getTonightFromDatabase()
를 구현합니다. 현재 시작된SleepNight
이 없는 경우 null 허용SleepNight
을 반환하는private suspend
함수로 정의합니다. 함수는 무언가를 반환해야 하므로 오류가 발생합니다.
private suspend fun getTonightFromDatabase(): SleepNight? { }
-
getTonightFromDatabase()
의 함수 본문 내에서Dispatchers.IO
컨텍스트에서 실행되는 코루틴의 결과를 반환합니다. 데이터베이스에서 데이터를 가져오는 것은 I/O 작업이며 UI와는 관련이 없으므로 I/O 디스패처를 사용합니다.
return withContext(Dispatchers.IO) {}
- return 블록 내에서 코루틴이 데이터베이스에서 tonight (가장 최근 밤)을 가져오도록 합니다. 시작 시간과 종료 시간이 동일하지 않으면 밤이 이미 완료되었음을 의미하므로
null
를 반환합니다. 그렇지 않으면 밤을 반환합니다.
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
완성된 getTonightFromDatabase()
중단 함수는 다음과 같습니다. 더 이상 오류가 표시되지 않습니다.
private suspend fun getTonightFromDatabase(): SleepNight? {
return withContext(Dispatchers.IO) {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
}
}
2단계: 시작 버튼의 클릭 핸들러 추가
이제 Start 버튼의 클릭 핸들러인 onStartTracking()
를 구현할 수 있습니다. 새 SleepNight
를 만들어 데이터베이스에 삽입하고 tonight
에 할당해야 합니다. onStartTracking()
의 구조는 initializeTonight()
와 매우 유사합니다.
onStartTracking()
의 함수 정의로 시작합니다.SleepTrackerViewModel
파일의onCleared()
위에 클릭 핸들러를 배치할 수 있습니다.
fun onStartTracking() {}
onStartTracking()
내에서uiScope
의 코루틴을 실행합니다. 이 결과가 있어야 계속 진행하고 UI를 업데이트할 수 있기 때문입니다.
uiScope.launch {}
- 코루틴 실행 내에서 현재 시간을 시작 시간으로 캡처하는 새
SleepNight
를 만듭니다.
val newNight = SleepNight()
- 코루틴 실행 내에서
insert()
을 호출하여newNight
을 데이터베이스에 삽입합니다. 이insert()
정지 함수를 아직 정의하지 않았기 때문에 오류가 표시됩니다. (이름이 같은 DAO 함수가 아닙니다.)
insert(newNight)
- 코루틴 실행 내부에서도
tonight
를 업데이트합니다.
tonight.value = getTonightFromDatabase()
onStartTracking()
아래에서SleepNight
을 인수로 사용하는private suspend
함수로insert()
을 정의합니다.
private suspend fun insert(night: SleepNight) {}
insert()
의 본문에서 I/O 컨텍스트에서 코루틴을 실행하고 DAO에서insert()
을 호출하여 밤을 데이터베이스에 삽입합니다.
withContext(Dispatchers.IO) {
database.insert(night)
}
fragment_sleep_tracker.xml
레이아웃 파일에서 이전에 설정한 데이터 결합의 마법을 사용하여onStartTracking()
의 클릭 핸들러를start_button
에 추가합니다.@{() ->
함수 표기법은 인수를 사용하지 않고sleepTrackerViewModel
에서 클릭 핸들러를 호출하는 람다 함수를 만듭니다.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- 앱을 빌드하고 실행합니다. Start 버튼을 탭합니다. 이 작업으로 데이터가 생성되지만 아직 아무것도 표시되지 않습니다. 다음 단계에서 이 문제를 해결합니다.
fun someWorkNeedsToBeDone { uiScope.launch { suspendFunction() } } suspend fun suspendFunction() { withContext(Dispatchers.IO) { longrunningWork() } }
3단계: 데이터 표시
SleepTrackerViewModel
에서 nights
변수는 LiveData
를 참조합니다. DAO의 getAllNights()
가 LiveData
를 반환하기 때문입니다.
데이터베이스의 데이터가 변경될 때마다 LiveData
nights
가 업데이트되어 최신 데이터를 표시하는 Room
기능입니다. LiveData
를 명시적으로 설정하거나 업데이트할 필요가 없습니다. Room
는 데이터베이스와 일치하도록 데이터를 업데이트합니다.
하지만 텍스트 뷰에 nights
를 표시하면 객체 참조가 표시됩니다. 객체의 콘텐츠를 보려면 데이터를 형식이 지정된 문자열로 변환하세요. nights
가 데이터베이스에서 새 데이터를 수신할 때마다 실행되는 Transformation
맵을 사용합니다.
Util.kt
파일을 열고formatNights()
정의와 연결된import
문에 대한 코드의 주석 처리를 삭제합니다. Android 스튜디오에서 코드의 주석을 해제하려면//
로 표시된 코드를 모두 선택하고Cmd+/
또는Control+/
를 누릅니다.formatNights()
는 HTML 형식의 문자열인Spanned
유형을 반환합니다.- strings.xml을 엽니다. 수면 데이터를 표시하기 위해 문자열 리소스의 형식을 지정하는 데
CDATA
가 사용됩니다. - SleepTrackerViewModel을 엽니다.
SleepTrackerViewModel
클래스에서uiScope
정의 아래에nights
이라는 변수를 정의합니다. 데이터베이스에서 모든 밤을 가져와nights
변수에 할당합니다.
private val nights = database.getAllNights()
nights
정의 바로 아래에nights
를nightsString
로 변환하는 코드를 추가합니다.Util.kt
.
에서formatNights()
함수를 사용합니다.Transformations
클래스에서map()
함수에nights
를 전달합니다.
문자열 리소스에 액세스하려면 매핑 함수를formatNights()
를 호출하는 것으로 정의하세요.nights
및Resources
객체를 제공합니다.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
fragment_sleep_tracker.xml
레이아웃 파일을 엽니다. 이제TextView
의android:text
속성에서 리소스 문자열을nightsString
참조로 바꿀 수 있습니다.
"@{sleepTrackerViewModel.nightsString}"
- 코드를 다시 빌드하고 앱을 실행합니다. 이제 시작 시간이 포함된 모든 수면 데이터가 표시됩니다.
- 시작 버튼을 몇 번 더 탭하면 더 많은 데이터가 표시됩니다.
다음 단계에서는 Stop 버튼의 기능을 사용 설정합니다.
4단계: 중지 버튼의 클릭 핸들러 추가
이전 단계와 동일한 패턴을 사용하여 SleepTrackerViewModel.
에서 중지 버튼의 클릭 핸들러를 구현합니다.
ViewModel
에onStopTracking()
를 추가합니다.uiScope
에서 코루틴을 실행합니다. 종료 시간이 아직 설정되지 않은 경우endTimeMilli
를 현재 시스템 시간으로 설정하고 야간 데이터를 사용하여update()
를 호출합니다.
Kotlin에서return@
label
문법은 여러 중첩 함수 중에서 이 문이 반환되는 함수를 지정합니다.
fun onStopTracking() {
uiScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
insert()
를 구현하는 데 사용한 것과 동일한 패턴을 사용하여update()
를 구현합니다.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- 클릭 핸들러를 UI에 연결하려면
fragment_sleep_tracker.xml
레이아웃 파일을 열고stop_button
에 클릭 핸들러를 추가합니다.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- 앱을 빌드하고 실행합니다.
- 시작을 탭한 다음 중지를 탭합니다. 시작 시간, 종료 시간, 값이 없는 수면의 질, 수면 시간이 표시됩니다.
5단계: Clear 버튼의 클릭 핸들러 추가
- 마찬가지로
onClear()
및clear()
을 구현합니다.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- 클릭 핸들러를 UI에 연결하려면
fragment_sleep_tracker.xml
를 열고clear_button
에 클릭 핸들러를 추가합니다.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- 앱을 빌드하고 실행합니다.
- 지우기를 탭하여 모든 데이터를 삭제합니다. 그런 다음 시작 및 중지를 탭하여 새 데이터를 만듭니다.
Android 스튜디오 프로젝트: TrackMySleepQualityCoroutines
ViewModel
,ViewModelFactory
, 데이터 결합을 사용하여 앱의 UI 아키텍처를 설정합니다.- 계속 UI를 원활하게 실행하려면 각종 데이터베이스 작업 같은 장기 실행 작업에 코루틴을 사용합니다.
- 코루틴은 비동기이며 비차단 형태이며
suspend
함수를 사용하여 비동기 코드를 순차 코드로 만듭니다. - 코루틴이
suspend
로 표시된 함수를 호출하면 일반 함수 호출처럼 해당 함수가 반환될 때까지 차단되는 대신 결과가 준비될 때까지 실행이 일시 중지됩니다. 그런 다음 결과와 함께 중단된 지점에서 다시 시작합니다. - 차단과 일시중지의 차이점은 스레드가 차단되면 다른 작업이 발생하지 않는다는 것입니다. 스레드가 정지되면 결과가 제공될 때까지 다른 작업이 실행됩니다.
코루틴을 실행하려면 작업, 디스패처, 범위가 필요합니다.
- 기본적으로 작업은 취소할 수 있는 모든 것입니다. 모든 코루틴에는 작업이 있으며 작업을 사용하여 코루틴을 취소할 수 있습니다.
- 디스패처는 다양한 스레드에서 실행되도록 코루틴을 전송합니다.
Dispatcher.Main
는 기본 스레드에서 작업을 실행하고Dispartcher.IO
는 차단 I/O 작업을 공유 스레드 풀로 오프로드하는 데 사용됩니다. - 스코프는 작업 및 디스패처를 비롯한 정보를 결합하여 코루틴이 실행되는 컨텍스트를 정의합니다. 범위는 코루틴을 추적합니다.
데이터베이스 작업을 트리거하는 클릭 핸들러를 구현하려면 다음 패턴을 따르세요.
- 결과가 UI에 영향을 미치므로 기본 스레드나 UI 스레드에서 실행되는 코루틴을 실행합니다.
- 결과를 기다리는 동안 UI 스레드가 차단되지 않도록 정지 함수를 호출하여 장기 실행 작업을 실행합니다.
- 장기 실행 작업은 UI와 관련이 없으므로 I/O 컨텍스트로 전환합니다. 이렇게 하면 이러한 종류의 작업을 위해 최적화되고 예약된 스레드 풀에서 작업을 실행할 수 있습니다.
- 그런 다음 데이터베이스 함수를 호출하여 작업을 실행합니다.
객체가 변경될 때마다 Transformations
맵을 사용하여 LiveData
객체에서 문자열을 만듭니다.
Udacity 과정:
Android 개발자 문서:
기타 문서 및 도움말:
- 팩토리 패턴
- 코루틴 Codelab
- 코루틴, 공식 문서
- 코루틴 컨텍스트 및 디스패처
Dispatchers
- Android 속도 제한 초과
Job
launch
- Kotlin의 반환 및 점프
- CDATA는 문자 데이터를 의미합니다. CDATA는 이러한 문자열 사이의 데이터에 XML 마크업으로 해석될 수 있지만 그렇게 해서는 안 되는 데이터가 포함되어 있음을 의미합니다.
이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 진행하는 학생에게 출제할 수 있는 과제가 나열되어 있습니다. 다음 작업은 강사가 결정합니다.
- 필요한 경우 과제를 할당합니다.
- 과제 제출 방법을 학생에게 알립니다.
- 과제를 채점합니다.
강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 적절하다고 생각되는 다른 과제를 출제해도 됩니다.
이 Codelab을 직접 진행하는 경우 이러한 과제를 자유롭게 사용하여 배운 내용을 테스트해 보세요.
다음 질문에 답하세요
질문 1
다음 중 코루틴의 장점은 무엇인가요?
- 비차단
- 비동기식으로 실행됩니다.
- 기본 스레드가 아닌 다른 스레드에서 실행될 수 있습니다.
- 항상 앱이 더 빨리 실행되도록 해 줍니다.
- 예외를 사용할 수 있습니다.
- 선형 코드로 작성되거나 읽을 수 있습니다.
질문 2
정지 함수란 무엇인가요?
suspend
키워드로 주석 처리된 일반 함수입니다.- 코루틴 내에서 호출할 수 있는 함수입니다.
- 정지 함수가 실행되는 동안 호출 스레드가 정지됩니다.
- 정지 함수는 항상 백그라운드에서 실행해야 합니다.
질문 3
스레드 차단과 스레드 정지의 차이점은 무엇인가요? 참인 것을 모두 선택하세요.
- 실행이 차단되면 차단된 스레드에서는 다른 작업을 실행할 수 없습니다.
- 실행이 정지된 경우 스레드는 오프로드된 작업이 완료되기를 기다리면서 다른 작업을 할 수 있습니다.
- 스레드가 아무것도 하지 않으면서 기다리지 않을 수 있으므로 정지하는 것이 더 효율적입니다.
- 차단되든 정지되든 실행은 코루틴 결과를 계속 대기하다가 진행합니다.
다음 강의 시작:
이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.