這個程式碼研究室是 Android Kotlin 基礎知識課程的一部分。如果您按部就班完成程式碼研究室,就能充分體驗到本課程的價值。所有課程程式碼研究室都列在 Android Kotlin 基礎知識程式碼研究室到達網頁。
簡介
確保 UI 一律能靈敏回應並順暢執行,是為應用程式打造完美使用者體驗的首要任務之一。如要提升 UI 效能,其中一個方法是將資料庫作業等耗時較長的工作移至背景執行。
在本程式碼研究室中,您將實作 TrackMySleepQuality 應用程式的使用者介面部分,並使用 Kotlin 協同程式在主執行緒以外執行資料庫作業。
必備知識
您必須已經熟悉下列項目:
- 使用活動、片段、檢視區塊和點擊處理常式建構基本使用者介面 (UI)。
- 在片段之間導覽,並使用
safeArgs
在片段之間傳遞簡單資料。 - 查看模型、模型工廠、轉換和
LiveData
。 - 如何建立
Room
資料庫、建立 DAO,以及定義實體。 - 如果您熟悉執行緒和多重處理的概念,這會很有幫助。
課程內容
- Android 中的執行緒運作方式。
- 如何使用 Kotlin 協同程式,將資料庫作業移出主執行緒。
- 如何在
TextView
中顯示格式化資料。
學習內容
- 擴充 TrackMySleepQuality 應用程式,以便在資料庫中收集、儲存及顯示資料。
- 使用協同程式在背景執行長時間執行的資料庫作業。
- 使用
LiveData
觸發導覽和顯示 Snackbar。 - 使用
LiveData
啟用及停用按鈕。
在本程式碼研究室中,您會建構 TrackMySleepQuality 應用程式的檢視區塊模型、協同程式和資料顯示部分。
如圖所示,這個應用程式有兩個畫面,分別以片段表示。
如左側所示,第一個畫面會顯示開始和停止追蹤的按鈕。畫面上會顯示使用者的所有睡眠資料。「清除」按鈕會永久刪除應用程式為使用者收集的所有資料。
右側的第二個畫面用於選取睡眠品質評分。應用程式會以數字表示評分。為了方便開發,應用程式會同時顯示臉部圖示和對應的數字。
使用者流程如下:
- 使用者開啟應用程式,並看到睡眠追蹤畫面。
- 使用者輕觸「開始」按鈕。系統會記錄開始時間並顯示。「開始」按鈕已停用,「停止」按鈕已啟用。
- 使用者輕觸「停止」按鈕。系統會記錄結束時間,並開啟睡眠品質畫面。
- 使用者選取睡眠品質圖示。螢幕會關閉,追蹤畫面則會顯示睡眠結束時間和睡眠品質。「停止」按鈕已停用,「開始」按鈕已啟用。應用程式已準備好迎接下一個夜晚。
- 只要資料庫中有資料,「清除」按鈕就會啟用。使用者輕觸「清除」按鈕後,所有資料都會遭到清除,且無法復原,系統也不會顯示「確定要清除嗎?」訊息。
這個應用程式採用簡化架構,如下圖所示 (完整架構的背景資訊)。應用程式只會使用下列元件:
- UI 控制器
- 查看模型和
LiveData
- Room 資料庫
在這項工作中,您會使用 TextView
顯示格式化的睡眠追蹤資料。(這不是最終介面,您會在另一個程式碼研究室中瞭解更有效率的方法。)
您可以繼續使用先前程式碼研究室中建構的 TrackMySleepQuality 應用程式,或下載本程式碼研究室的範例應用程式。
步驟 1:下載並執行入門應用程式
- 從 GitHub 下載 TrackMySleepQuality-Coroutines-Starter 應用程式。
- 建構並執行應用程式。應用程式會顯示
SleepTrackerFragment
片段的 UI,但不會顯示任何資料。按鈕不會對輕觸動作做出反應。
步驟 2:檢查程式碼
本程式碼研究室的範例程式碼與「6.1 建立 Room 資料庫」程式碼研究室的解決方案程式碼相同。
- 開啟「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
實體。如果您未完成先前的程式碼研究室,請務必自行探索程式碼的這些層面。
現在您已擁有資料庫和 UI,接下來需要收集資料、將資料新增至資料庫,並顯示資料。所有這些工作都會在檢視畫面模型中完成。睡眠追蹤器檢視模型會處理按鈕點擊事件、透過 DAO 與資料庫互動,並透過 LiveData
將資料提供給 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
。 - 在工廠中,程式碼會覆寫
create()
,該程式碼會將任何類別型別做為引數,並傳回ViewModel
。 - 在
create()
的主體中,程式碼會檢查是否有可用的SleepTrackerViewModel
類別,如有,則傳回該類別的執行個體。否則程式碼會擲回例外狀況。
步驟 3:更新 SleepTrackerFragment
- 在
SleepTrackerFragment
中,取得應用程式內容的參照。將參照放在onCreateView()
中,binding
下方。您需要參照這個片段所附加的應用程式,才能傳遞至檢視模型工廠供應器。
如果 value 為null
,requireNotNull
Kotlin 函式會擲回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
中:
- 將目前的活動設為繫結的生命週期擁有者。在
return
陳述式之前的onCreateView()
方法中新增這段程式碼:
binding.setLifecycleOwner(this)
- 將
sleepTrackerViewModel
繫結變數指派給sleepTrackerViewModel
。將這段程式碼放在onCreateView()
內,建立SleepTrackerViewModel
的程式碼下方:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- 您可能會看到錯誤,因為您必須重新建立繫結物件。清除專案再重新建構即可解決錯誤。
- 最後,請務必確認程式碼建構及執行時沒有錯誤。
在 Kotlin 中,協同程式是處理長時間執行工作的流暢有效方式。運用 Kotlin 協同程式,您可將以回呼為基礎的程式碼轉換為循序程式碼。依序編寫的程式碼通常較容易閱讀,甚至能使用例外狀況等語言功能。說到底,協同程式和回呼所做的工作其實並無二致,亦即等到長時間執行的工作產生結果,再繼續執行。
協同程式具有下列屬性:
- 協同程式具有非同步和非阻塞的特點。
- 協同程式會使用「暫停」函式,讓非同步程式碼依序執行。
協同程式為非同步。
協同程式的執行作業獨立於程式的主要執行步驟。這項作業可以平行處理,也可以在獨立的處理器上執行。此外,您也可以在應用程式的其餘部分等待輸入時,偷偷進行一些處理作業。非同步的重要層面之一是,您必須明確等待結果,才能預期結果可用。
舉例來說,假設您有一個需要研究的問題,並請同事找出答案。他們會離開並處理這項工作,這就像他們「非同步」和「在另一個執行緒上」執行工作。在同事回來告訴你答案之前,你可以繼續處理其他不需答案的工作。
協同程式不會封鎖執行緒。
非封鎖 是指協同程式不會封鎖主執行緒或 UI 執行緒。因此,使用協同程式時,使用者一律能享有最流暢的體驗,因為 UI 互動一律優先處理。
協同程式會使用暫停函式,讓非同步程式碼依序執行。
Kotlin 會使用 suspend
關鍵字標記函式或函式類型,表示這些函式或函式類型可供協同程式使用。協同程式呼叫標記有 suspend
的函式時,不會像一般函式呼叫一樣阻斷,直到函式傳回為止,而是會暫停執行,直到結果準備就緒。接著,協同程式會從中斷的地方繼續執行,並取得結果。
協同程式暫停並等待結果時,會解除封鎖執行的執行緒。這樣一來,其他函式或協同程式就能執行。
suspend
關鍵字不會指定程式碼執行的執行緒。暫停函式可在背景執行緒或主執行緒上執行。
如要在 Kotlin 中使用協同程式,您需要三項要素:
- 工作
- 調度員
- 範圍
工作:基本上,工作是指任何可取消的項目。每個協同程式都有工作,您可以使用工作取消協同程式。工作可以安排成父項/子項階層。取消父項工作會立即取消所有子項工作,比手動取消每個協同程式方便許多。
調度器: 調度器會將協同程式傳送至各種執行緒執行。舉例來說,Dispatcher.Main
會在主執行緒上執行工作,而 Dispatcher.IO
則會將封鎖的 I/O 工作卸載至共用執行緒集區。
範圍: 協同程式的範圍會定義協同程式的執行環境。範圍會合併協同程式工作和調度工具的相關資訊。範圍會追蹤協同程式。啟動協同程式時,該程式會「位於範圍內」,也就是說,您已指出哪個範圍會追蹤協同程式。
您希望使用者能透過下列方式與睡眠資料互動:
- 使用者輕觸「開始」按鈕後,應用程式會建立新的睡眠夜間記錄,並將其儲存在資料庫中。
- 使用者輕觸「停止」按鈕後,應用程式會更新夜間睡眠記錄,並加入結束時間。
- 使用者輕觸「清除」按鈕後,應用程式就會清除資料庫中的資料。
這些資料庫作業可能需要很長時間,因此應在另一個執行緒中執行。
步驟 1:為資料庫作業設定協同程式
當使用者輕觸「睡眠追蹤器」應用程式中的「開始」按鈕時,您想在 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()
。將其定義為private suspend
函式,如果沒有目前啟動的SleepNight
,則會傳回可為空值的SleepNight
。這樣會發生錯誤,因為函式必須傳回某些內容。
private suspend fun getTonightFromDatabase(): SleepNight? { }
- 在
getTonightFromDatabase()
的函式主體中,從在Dispatchers.IO
內容中執行的協同程式傳回結果。請使用 I/O 調度器,因為從資料庫取得資料是 I/O 作業,與 UI 無關。
return withContext(Dispatchers.IO) {}
- 在回傳區塊中,讓協同程式從資料庫取得今晚 (最新一晚) 的資料。如果開始和結束時間不同,表示夜間睡眠已結束,請傳回
null
。否則傳回 night。
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:為「開始」按鈕新增點擊處理常式
現在您可以實作 onStartTracking()
,也就是「Start」按鈕的點擊處理常式。您需要建立新的 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()
下方,將insert()
定義為private suspend
函式,並以SleepNight
做為引數。
private suspend fun insert(night: SleepNight) {}
- 在
insert()
的主體中,於 I/O 內容中啟動協同程式,並透過呼叫 DAO 中的insert()
,將夜晚插入資料庫。
withContext(Dispatchers.IO) {
database.insert(night)
}
- 在
fragment_sleep_tracker.xml
版面配置檔案中,使用您先前設定的資料繫結魔法,將onStartTracking()
的點擊處理常式新增至start_button
。@{() ->
函式標記會建立不使用任何引數的 lambda 函式,並呼叫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
。
這是 Room
的功能,每當資料庫中的資料變更時,LiveData
nights
就會更新,顯示最新資料。您不需要明確設定或更新 LiveData
。Room
會更新資料,使其與資料庫相符。
不過,如果您在文字檢視畫面中顯示 nights
,系統會顯示物件參照。如要查看物件內容,請將資料轉換為格式化字串。使用 Transformation
對映,每當 nights
從資料庫收到新資料時,就會執行對映。
- 開啟
Util.kt
檔案,並取消註解formatNights()
的定義程式碼和相關的import
陳述式。如要在 Android Studio 中取消註解程式碼,請選取所有標示//
的程式碼,然後按下Cmd+/
或Control+/
。 - 請注意,
formatNights()
會傳回Spanned
型別,也就是 HTML 格式的字串。 - 開啟 strings.xml。請注意,我們使用
CDATA
格式化字串資源,以便顯示睡眠資料。 - 開啟 SleepTrackerViewModel。在
SleepTrackerViewModel
類別中,於uiScope
的定義下方,定義名為nights
的變數。從資料庫取得所有夜晚,並指派給nights
變數。
private val nights = database.getAllNights()
- 在
nights
的定義下方,新增程式碼將nights
轉換為nightsString
。使用Util.kt
中的formatNights()
函式。
將nights
傳遞至Transformations
類別中的map()
函式。如要存取字串資源,請將對應函式定義為呼叫formatNights()
。提供nights
和Resources
物件。
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- 開啟
fragment_sleep_tracker.xml
版面配置檔案。在TextView
的android:text
屬性中,您現在可以將資源字串替換為nightsString
的參照。
"@{sleepTrackerViewModel.nightsString}"
- 重新建構程式碼並執行應用程式,現在應該會顯示所有睡眠資料和開始時間。
- 多輕觸幾次「開始」按鈕,即可查看更多資料。
在下一個步驟中,您將啟用「停止」按鈕的功能。
步驟 4:為「停止」按鈕新增點擊處理常式
使用與上一個步驟相同的模式,在 SleepTrackerViewModel.
中實作「停止」按鈕的點擊處理常式。
- 將
onStopTracking()
新增至ViewModel
。在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:為「清除」按鈕新增點擊處理常式
- 同樣地,請實作
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 Studio 專案:TrackMySleepQualityCoroutines
- 使用
ViewModel
、ViewModelFactory
和資料繫結,為應用程式設定 UI 架構。 - 為了讓 UI 順利運作,請為長時間執行的工作 (例如所有資料庫作業) 使用協同程式。
- 協同程式具備非同步和非阻塞的特點,並會使用
suspend
函式,讓非同步程式碼依序執行。 - 協同程式呼叫標示為
suspend
的函式時,不會像一般函式呼叫一樣阻斷,直到該函式傳回為止,而是會暫停執行,直到結果準備就緒。然後從中斷處繼續執行,並傳回結果。 - 封鎖和暫停的差異在於,如果執行緒遭到封鎖,就不會執行其他作業。如果執行緒暫停,系統會執行其他工作,直到結果可用為止。
如要啟動協同程式,您需要工作、調度工具和範圍:
- 基本上,工作是指任何可取消的項目。每個協同程式都有工作,您可以使用工作取消協同程式。
- 調度器會將協同程式傳送至不同執行緒執行。
Dispatcher.Main
會在主執行緒上執行工作,而Dispartcher.IO
則用於將封鎖的 I/O 工作卸載至共用執行緒集區。 - 範圍會結合工作和調度工具等資訊,定義協同程式的執行環境。範圍會追蹤協同程式。
如要實作會觸發資料庫作業的點擊處理常式,請按照下列模式操作:
- 啟動在主執行緒或 UI 執行緒上執行的協同程式,因為結果會影響 UI。
- 呼叫暫停函式執行長時間的工作,這樣在等待結果時就不會封鎖 UI 執行緒。
- 耗時偏長的工作與 UI 無關,因此請切換至 I/O 內容。這樣一來,工作就能在專為這類作業最佳化及預留的執行緒集區中執行。
- 然後呼叫資料庫函式來執行作業。
每當 LiveData
物件變更時,請使用 Transformations
對應項從該物件建立字串。
Udacity 課程:
Android 開發人員說明文件:
其他說明文件和文章:
- 工廠模式
- 協同程式程式碼研究室
- 協同程式官方說明文件
- 協同程式結構定義與調度工具
Dispatchers
- 超過 Android 速度限制
Job
launch
- Kotlin 中的回傳和跳轉
- CDATA 代表字元資料。CDATA 表示這些字串之間的資料包含可能解譯為 XML 標記的資料,但不應如此。
本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:
- 視需要指派作業。
- 告知學員如何繳交作業。
- 為作業評分。
講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。
如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。
回答這些問題
第 1 題
下列何者是協同程式的優點:
- 協同程式屬於非阻斷式
- 協同程式會以非同步方式執行。
- 可在除了主執行緒以外的其他執行緒上執行。
- 協同程式總是會提升應用程式執行速度。
- 協同程式可以使用例外狀況。
- 可以當做線性程式碼來編寫及讀取。
第 2 題
什麼是暫停函式?
- 加上
suspend
關鍵字註解的一般函式。 - 可在協同程式內呼叫的函式。
- 暫停函式執行時,呼叫執行緒會暫停。
- 暫停函式一律必須在背景執行。
第 3 題
封鎖和暫停執行緒有何不同?請選取所有正確敘述。
- 當執行遭到封鎖後,則無法在已封鎖的執行緒上執行其他作業。
- 執行作業暫停後,執行緒可在等待卸載工作完成期間,執行其他作業。
- 暫停更有效率,因為執行緒可能並未在等候,也並未執行任何作業。
- 不論是處於封鎖或暫停的狀態,執行作業會先等待協同程式的結果,再繼續執行作業。
前往下一堂課:
如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面。