Android Kotlin 基礎知識 06.2:協同程式和 Room

這個程式碼研究室是 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:下載並執行入門應用程式

  1. 從 GitHub 下載 TrackMySleepQuality-Coroutines-Starter 應用程式。
  2. 建構並執行應用程式。應用程式會顯示 SleepTrackerFragment 片段的 UI,但不會顯示任何資料。按鈕不會對輕觸動作做出反應。

步驟 2:檢查程式碼

本程式碼研究室的範例程式碼與「6.1 建立 Room 資料庫」程式碼研究室的解決方案程式碼相同。

  1. 開啟「res/layout/activity_main.xml」。這個版面配置包含 nav_host_fragment 片段。此外,請注意 <merge> 標記。

    加入版面配置時,merge 標記可用來減少多餘的版面配置,建議您使用這個標記。舉例來說,如果版面配置為 ConstraintLayout > LinearLayout > TextView,系統可能會消除 LinearLayout。這類最佳化作業可簡化檢視區塊階層,並提升應用程式效能。
  2. 在「navigation」資料夾中,開啟「navigation.xml」。您會看到兩個片段,以及連結這兩個片段的導覽動作。
  3. 在「layout」資料夾中,按兩下睡眠追蹤器片段,即可查看其 XML 版面配置。請注意下列事項:
  • 版面配置資料會包裝在 <layout> 元素中,以啟用資料繫結。
  • ConstraintLayout,其他檢視畫面則排列在 <layout> 元素內。
  • 檔案含有預留位置 <data> 標記。

入門應用程式也會提供 UI 的尺寸、顏色和樣式。這個應用程式包含 Room 資料庫、DAO 和 SleepNight 實體。如果您未完成先前的程式碼研究室,請務必自行探索程式碼的這些層面。

現在您已擁有資料庫和 UI,接下來需要收集資料、將資料新增至資料庫,並顯示資料。所有這些工作都會在檢視畫面模型中完成。睡眠追蹤器檢視模型會處理按鈕點擊事件、透過 DAO 與資料庫互動,並透過 LiveData 將資料提供給 UI。所有資料庫作業都必須透過主使用者介面執行緒執行,為此,使用協同程式即可。

步驟 1:新增 SleepTrackerViewModel

  1. sleeptracker 套件中,開啟 SleepTrackerViewModel.kt
  2. 檢查入門應用程式中提供的 SleepTrackerViewModel 類別,如下所示。請注意,這個類別會擴充 AndroidViewModel()。這個類別與 ViewModel 相同,但會將應用程式內容做為參數,並以屬性形式提供。稍後會用到這個網址。
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

步驟 2:新增 SleepTrackerViewModelFactory

  1. sleeptracker 套件中,開啟 SleepTrackerViewModelFactory.kt
  2. 檢查為工廠提供的程式碼,如下所示:
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

  1. SleepTrackerFragment 中,取得應用程式內容的參照。將參照放在 onCreateView() 中,binding 下方。您需要參照這個片段所附加的應用程式,才能傳遞至檢視模型工廠供應器。

    如果 valuenullrequireNotNull Kotlin 函式會擲回 IllegalArgumentException
val application = requireNotNull(this.activity).application
  1. 您需要透過 DAO 的參照,參照資料來源。在 onCreateView() 中,於 return 之前定義 dataSource。如要取得資料庫的 DAO 參照,請使用 SleepDatabase.getInstance(application).sleepDatabaseDao
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. onCreateView() 中,於 return 之前建立 viewModelFactory 的例項。您需要傳遞 dataSourceapplication
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. 現在您已擁有工廠,請取得 SleepTrackerViewModel 的參照。SleepTrackerViewModel::class.java 參數是指這個物件的執行階段 Java 類別。
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. 完成的程式碼應如下所示:
// 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 版面配置檔案中:

  1. <data> 區塊中,建立參照 SleepTrackerViewModel 類別的 <variable>
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

SleepTrackerFragment 中:

  1. 將目前的活動設為繫結的生命週期擁有者。在 return 陳述式之前的 onCreateView() 方法中新增這段程式碼:
binding.setLifecycleOwner(this)
  1. sleepTrackerViewModel 繫結變數指派給 sleepTrackerViewModel。將這段程式碼放在 onCreateView() 內,建立 SleepTrackerViewModel 的程式碼下方:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. 您可能會看到錯誤,因為您必須重新建立繫結物件。清除專案再重新建構即可解決錯誤。
  2. 最後,請務必確認程式碼建構及執行時沒有錯誤。

在 Kotlin 中,協同程式是處理長時間執行工作的流暢有效方式。運用 Kotlin 協同程式,您可將以回呼為基礎的程式碼轉換為循序程式碼。依序編寫的程式碼通常較容易閱讀,甚至能使用例外狀況等語言功能。說到底,協同程式和回呼所做的工作其實並無二致,亦即等到長時間執行的工作產生結果,再繼續執行。

協同程式具有下列屬性:

  • 協同程式具有非同步和非阻塞的特點。
  • 協同程式會使用「暫停」函式,讓非同步程式碼依序執行。

協同程式為非同步。

協同程式的執行作業獨立於程式的主要執行步驟。這項作業可以平行處理,也可以在獨立的處理器上執行。此外,您也可以在應用程式的其餘部分等待輸入時,偷偷進行一些處理作業。非同步的重要層面之一是,您必須明確等待結果,才能預期結果可用。

舉例來說,假設您有一個需要研究的問題,並請同事找出答案。他們會離開並處理這項工作,這就像他們「非同步」和「在另一個執行緒上」執行工作。在同事回來告訴你答案之前,你可以繼續處理其他不需答案的工作。

協同程式不會封鎖執行緒。

非封鎖 是指協同程式不會封鎖主執行緒或 UI 執行緒。因此,使用協同程式時,使用者一律能享有最流暢的體驗,因為 UI 互動一律優先處理。

協同程式會使用暫停函式,讓非同步程式碼依序執行。

Kotlin 會使用 suspend 關鍵字標記函式或函式類型,表示這些函式或函式類型可供協同程式使用。協同程式呼叫標記有 suspend 的函式時,不會像一般函式呼叫一樣阻斷,直到函式傳回為止,而是會暫停執行,直到結果準備就緒。接著,協同程式會從中斷的地方繼續執行,並取得結果。

協同程式暫停並等待結果時,會解除封鎖執行的執行緒。這樣一來,其他函式或協同程式就能執行。

suspend 關鍵字不會指定程式碼執行的執行緒。暫停函式可在背景執行緒或主執行緒上執行。

如要在 Kotlin 中使用協同程式,您需要三項要素:

  • 工作
  • 調度員
  • 範圍

工作:基本上,工作是指任何可取消的項目。每個協同程式都有工作,您可以使用工作取消協同程式。工作可以安排成父項/子項階層。取消父項工作會立即取消所有子項工作,比手動取消每個協同程式方便許多。

調度器: 調度器會將協同程式傳送至各種執行緒執行。舉例來說,Dispatcher.Main 會在主執行緒上執行工作,而 Dispatcher.IO 則會將封鎖的 I/O 工作卸載至共用執行緒集區。

範圍: 協同程式的範圍會定義協同程式的執行環境。範圍會合併協同程式工作和調度工具的相關資訊。範圍會追蹤協同程式。啟動協同程式時,該程式會「位於範圍內」,也就是說,您已指出哪個範圍會追蹤協同程式。

您希望使用者能透過下列方式與睡眠資料互動:

  • 使用者輕觸「開始」按鈕後,應用程式會建立新的睡眠夜間記錄,並將其儲存在資料庫中。
  • 使用者輕觸「停止」按鈕後,應用程式會更新夜間睡眠記錄,並加入結束時間。
  • 使用者輕觸「清除」按鈕後,應用程式就會清除資料庫中的資料。

這些資料庫作業可能需要很長時間,因此應在另一個執行緒中執行。

步驟 1:為資料庫作業設定協同程式

當使用者輕觸「睡眠追蹤器」應用程式中的「開始」按鈕時,您想在 SleepTrackerViewModel 中呼叫函式,建立 SleepNight 的新例項,並將該例項儲存在資料庫中。

輕觸任一按鈕都會觸發資料庫作業,例如建立或更新 SleepNight。基於這個原因和其他原因,您會使用協同程式實作應用程式按鈕的點擊處理常式。

  1. 開啟應用程式層級的 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"
  1. 開啟 SleepTrackerViewModel 檔案,
  2. 在類別主體中,定義 viewModelJob 並指派 Job 的執行個體。當檢視區塊模型不再使用並遭到刪除時,您可以使用這個 viewModelJob 取消這個檢視區塊模型啟動的所有協同程式。這樣一來,您就不會產生無法返回的協同程式。
private var viewModelJob = Job()
  1. 在類別主體結尾,覆寫 onCleared() 並取消所有協同程式。ViewModel 遭到刪除時,系統會呼叫 onCleared()
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. viewModelJob 的定義正下方,定義協同程式的 uiScope。範圍會決定協同程式執行的執行緒,範圍也需要瞭解工作。如要取得範圍,請要求 CoroutineScope 的例項,並傳入調度器和工作。

使用 Dispatchers.Main 表示在 uiScope 中啟動的協同程式會在主執行緒上執行。對於 ViewModel 啟動的許多協同程式來說,這是合理的做法,因為這些協同程式執行一些處理作業後,會導致 UI 更新。

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. uiScope 的定義下方,定義名為 tonight 的變數,用來保存當晚的資料。將變數設為 MutableLiveData,因為您需要能夠觀察資料並加以變更。
private var tonight = MutableLiveData<SleepNight?>()
  1. 如要盡快初始化 tonight 變數,請在 tonight 的定義下方建立 init 區塊,然後呼叫 initializeTonight()。您將在下一個步驟中定義 initializeTonight()
init {
   initializeTonight()
}
  1. init 區塊下方實作 initializeTonight()。在 uiScope 中啟動協同程式。在內部,透過呼叫 getTonightFromDatabase() 從資料庫取得 tonight 的值,並將該值指派給 tonight.value。您將在下一個步驟中定義 getTonightFromDatabase()
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. 實作 getTonightFromDatabase()。將其定義為 private suspend 函式,如果沒有目前啟動的 SleepNight,則會傳回可為空值的 SleepNight。這樣會發生錯誤,因為函式必須傳回某些內容。
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. getTonightFromDatabase() 的函式主體中,從在 Dispatchers.IO 內容中執行的協同程式傳回結果。請使用 I/O 調度器,因為從資料庫取得資料是 I/O 作業,與 UI 無關。
  return withContext(Dispatchers.IO) {}
  1. 在回傳區塊中,讓協同程式從資料庫取得今晚 (最新一晚) 的資料。如果開始和結束時間不同,表示夜間睡眠已結束,請傳回 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、將其插入資料庫,然後指派給 tonightonStartTracking() 的結構與 initializeTonight() 非常相似。

  1. onStartTracking() 的函式定義開始。您可以在 SleepTrackerViewModel 檔案中,將點擊事件處理常式放在 onCleared() 上方。
fun onStartTracking() {}
  1. onStartTracking() 中,於 uiScope 啟動協同程式,因為您需要這個結果才能繼續並更新 UI。
uiScope.launch {}
  1. 在協同程式啟動期間,建立新的 SleepNight,擷取目前時間做為開始時間。
        val newNight = SleepNight()
  1. 在協同程式啟動程序中,呼叫 insert()newNight 插入資料庫。由於您尚未定義這個 insert() 暫停函式,因此系統會顯示錯誤訊息。(這不是同名的 DAO 函式)。
       insert(newNight)
  1. 同樣在協同程式啟動期間,更新 tonight
       tonight.value = getTonightFromDatabase()
  1. onStartTracking() 下方,將 insert() 定義為 private suspend 函式,並以 SleepNight 做為引數。
private suspend fun insert(night: SleepNight) {}
  1. insert() 的主體中,於 I/O 內容中啟動協同程式,並透過呼叫 DAO 中的 insert(),將夜晚插入資料庫。
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. fragment_sleep_tracker.xml 版面配置檔案中,使用您先前設定的資料繫結魔法,將 onStartTracking() 的點擊處理常式新增至 start_button@{() -> 函式標記會建立不使用任何引數的 lambda 函式,並呼叫 sleepTrackerViewModel 中的點按處理常式。
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. 建構並執行應用程式,然後輕觸「Start」按鈕。這項操作會建立資料,但您還不會看到任何資料。接下來,您將修正此問題。
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

步驟 3:顯示資料

SleepTrackerViewModel 中,nights 變數會參照 LiveData,因為 DAO 中的 getAllNights() 會傳回 LiveData

這是 Room 的功能,每當資料庫中的資料變更時,LiveData nights 就會更新,顯示最新資料。您不需要明確設定或更新 LiveDataRoom 會更新資料,使其與資料庫相符。

不過,如果您在文字檢視畫面中顯示 nights,系統會顯示物件參照。如要查看物件內容,請將資料轉換為格式化字串。使用 Transformation 對映,每當 nights 從資料庫收到新資料時,就會執行對映。

  1. 開啟 Util.kt 檔案,並取消註解 formatNights() 的定義程式碼和相關的 import 陳述式。如要在 Android Studio 中取消註解程式碼,請選取所有標示 // 的程式碼,然後按下 Cmd+/Control+/
  2. 請注意,formatNights() 會傳回 Spanned 型別,也就是 HTML 格式的字串。
  3. 開啟 strings.xml。請注意,我們使用 CDATA 格式化字串資源,以便顯示睡眠資料。
  4. 開啟 SleepTrackerViewModel。在 SleepTrackerViewModel 類別中,於 uiScope 的定義下方,定義名為 nights 的變數。從資料庫取得所有夜晚,並指派給 nights 變數。
private val nights = database.getAllNights()
  1. nights 的定義下方,新增程式碼將 nights 轉換為 nightsString。使用 Util.kt 中的 formatNights() 函式。

    nights 傳遞至 Transformations 類別中的 map() 函式。如要存取字串資源,請將對應函式定義為呼叫 formatNights()。提供 nightsResources 物件。
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. 開啟 fragment_sleep_tracker.xml 版面配置檔案。在 TextViewandroid:text 屬性中,您現在可以將資源字串替換為 nightsString 的參照。
"@{sleepTrackerViewModel.nightsString}"
  1. 重新建構程式碼並執行應用程式,現在應該會顯示所有睡眠資料和開始時間。
  2. 多輕觸幾次「開始」按鈕,即可查看更多資料。

在下一個步驟中,您將啟用「停止」按鈕的功能。

步驟 4:為「停止」按鈕新增點擊處理常式

使用與上一個步驟相同的模式,在 SleepTrackerViewModel. 中實作「停止」按鈕的點擊處理常式。

  1. onStopTracking() 新增至 ViewModel。在 uiScope 中啟動協同程式。如果尚未設定結束時間,請將 endTimeMilli 設為目前的系統時間,並使用夜間資料呼叫 update()

    在 Kotlin 中,return@label 語法會指定這個陳述式傳回的函式 (位於多個巢狀函式中)。
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. 請使用與實作 insert() 時相同的模式,實作 update()
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. 如要將點擊處理常式連結至 UI,請開啟 fragment_sleep_tracker.xml 版面配置檔案,並將點擊處理常式新增至 stop_button
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. 建構並執行應用程式。
  2. 依序輕觸「開始」和「停止」。你看到開始時間、結束時間、沒有值的睡眠品質,以及睡眠時間。

步驟 5:為「清除」按鈕新增點擊處理常式

  1. 同樣地,請實作 onClear()clear()
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. 如要將點擊處理常式連結至 UI,請開啟 fragment_sleep_tracker.xml 並將點擊處理常式新增至 clear_button
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. 建構並執行應用程式。
  2. 輕觸「清除」即可刪除所有資料。然後輕觸「開始」和「停止」,即可建立新資料。

Android Studio 專案:TrackMySleepQualityCoroutines

  • 使用 ViewModelViewModelFactory 和資料繫結,為應用程式設定 UI 架構。
  • 為了讓 UI 順利運作,請為長時間執行的工作 (例如所有資料庫作業) 使用協同程式。
  • 協同程式具備非同步和非阻塞的特點,並會使用 suspend 函式,讓非同步程式碼依序執行。
  • 協同程式呼叫標示為 suspend 的函式時,不會像一般函式呼叫一樣阻斷,直到該函式傳回為止,而是會暫停執行,直到結果準備就緒。然後從中斷處繼續執行,並傳回結果。
  • 封鎖暫停的差異在於,如果執行緒遭到封鎖,就不會執行其他作業。如果執行緒暫停,系統會執行其他工作,直到結果可用為止。

如要啟動協同程式,您需要工作、調度工具和範圍:

  • 基本上,工作是指任何可取消的項目。每個協同程式都有工作,您可以使用工作取消協同程式。
  • 調度器會將協同程式傳送至不同執行緒執行。Dispatcher.Main 會在主執行緒上執行工作,而 Dispartcher.IO 則用於將封鎖的 I/O 工作卸載至共用執行緒集區。
  • 範圍會結合工作和調度工具等資訊,定義協同程式的執行環境。範圍會追蹤協同程式。

如要實作會觸發資料庫作業的點擊處理常式,請按照下列模式操作:

  1. 啟動在主執行緒或 UI 執行緒上執行的協同程式,因為結果會影響 UI。
  2. 呼叫暫停函式執行長時間的工作,這樣在等待結果時就不會封鎖 UI 執行緒。
  3. 耗時偏長的工作與 UI 無關,因此請切換至 I/O 內容。這樣一來,工作就能在專為這類作業最佳化及預留的執行緒集區中執行。
  4. 然後呼叫資料庫函式來執行作業。

每當 LiveData 物件變更時,請使用 Transformations 對應項從該物件建立字串。

Udacity 課程:

Android 開發人員說明文件:

其他說明文件和文章:

本節列出的作業可由課程講師指派給學習本程式碼研究室的學員。講師可自由採取以下行動:

  • 視需要指派作業。
  • 告知學員如何繳交作業。
  • 為作業評分。

講師可以視需求使用全部或部分建議內容,也可以自由指派任何其他合適的作業。

如果您是自行學習本程式碼研究室,不妨利用這些作業驗收學習成果。

回答這些問題

第 1 題

下列何者是協同程式的優點:

  • 協同程式屬於非阻斷式
  • 協同程式會以非同步方式執行。
  • 可在除了主執行緒以外的其他執行緒上執行。
  • 協同程式總是會提升應用程式執行速度。
  • 協同程式可以使用例外狀況。
  • 可以當做線性程式碼來編寫及讀取。

第 2 題

什麼是暫停函式?

  • 加上 suspend 關鍵字註解的一般函式。
  • 可在協同程式內呼叫的函式。
  • 暫停函式執行時,呼叫執行緒會暫停。
  • 暫停函式一律必須在背景執行。

第 3 題

封鎖和暫停執行緒有何不同?請選取所有正確敘述。

  • 當執行遭到封鎖後,則無法在已封鎖的執行緒上執行其他作業。
  • 執行作業暫停後,執行緒可在等待卸載工作完成期間,執行其他作業。
  • 暫停更有效率,因為執行緒可能並未在等候,也並未執行任何作業。
  • 不論是處於封鎖或暫停的狀態,執行作業會先等待協同程式的結果,再繼續執行作業。

前往下一堂課:6.3 使用 LiveData 控制按鈕狀態

如要查看本課程其他程式碼研究室的連結,請參閱 Android Kotlin 基礎知識程式碼研究室登陸頁面