# WHAT IS A COROUTINE? ## Kotlin 的 Coroutine 介紹 - Coroutine 是一種 Kotlin 中的協程概念,受到 Google 的高度支持。 - 協程的特性使其在 Android 開發中非常受歡迎。 ## 函數與執行緒的背景知識 - 函數:一系列指令的執行,接受輸入並返回輸出。 ![CleanShot 2024-08-24 at 22.50.43](https://hackmd.io/_uploads/rkELmOvo0.png) - 執行緒:指示在哪個上下文中執行這些指令,通常是線性執行。 ![CleanShot 2024-08-24 at 22.50.52](https://hackmd.io/_uploads/B1R8XOPiR.png) ![CleanShot 2024-08-24 at 22.51.03](https://hackmd.io/_uploads/BkKwXuwiA.png) ## 單執行緒的限制 - 在沒有多執行緒的情況下,所有指令會在同一個執行緒中依序執行。 - 這可能導致問題,例如進行網路請求時會凍結 UI,因為主執行緒在等待回應。 ## 多執行緒的重要性 - Android 中的指令通常在主執行緒中執行,主執行緒負責更新 UI。 ![CleanShot 2024-08-24 at 22.51.38](https://hackmd.io/_uploads/S19FXOwoC.png) - 如果進行網路請求等耗時操作而不使用多執行緒,會導致 UI 停止更新。 - 因此,我們會啟動一個新的執行緒來處理這些耗時操作,避免阻塞主執行緒。 ![CleanShot 2024-08-24 at 22.51.46](https://hackmd.io/_uploads/Bk49mOPoA.png) ## Coroutine 的優勢 ![CleanShot 2024-08-24 at 22.52.17](https://hackmd.io/_uploads/ry6sQuPsA.png) - Coroutine 可以做執行緒能做的事情,並且更高效。 - 協程是輕量級的執行緒,允許在單一執行緒中啟動多個協程。 - 協程可以暫停執行並在需要時繼續,這是執行緒無法做到的。 - 協程可以輕鬆切換上下文,即從一個執行緒切換到另一個執行緒。 ## Coroutine 的效能展示 - 啟動 100,000 個協程來打印字元,執行速度非常快且不會出現內存不足錯誤。 - 相同操作用 100,000 個執行緒來執行,會導致內存不足錯誤。 ![CleanShot 2024-08-24 at 22.53.32](https://hackmd.io/_uploads/SysxVdDs0.png) ![CleanShot 2024-08-24 at 22.52.52](https://hackmd.io/_uploads/BkHkN_PiR.png) # Starting our First Coroutine ## 在 Kotlin 中啟動第一個 Coroutine - 要啟動第一個 Coroutine,首先需要在專案中加入 Coroutine 相關的依賴項目。 - 開啟 `build.gradle` 中的 `dependencies` 區塊,加入相關依賴並同步。 ![CleanShot 2024-08-24 at 23.20.13](https://hackmd.io/_uploads/HJY49OvsC.png) ## 在 MainActivity 中啟動 Coroutine - 使用 `GlobalScope.launch` 可以啟動一個 Coroutine,這是最簡單的方式,但並不是最佳方式。 - `GlobalScope` 表示這個 Coroutine 的生命週期將與整個應用程序一樣長。 ## 觀察 Coroutine 的執行緒 ![CleanShot 2024-08-24 at 23.21.41](https://hackmd.io/_uploads/B1Lqq_woC.png) - 可以透過 `Log.d` 來記錄 Coroutine 所在的執行緒名稱。 - 在 `GlobalScope` 中啟動的 Coroutine 將在另一個執行緒中執行,這樣可以確認 Coroutine 是異步執行的。 ![CleanShot 2024-08-24 at 23.21.57](https://hackmd.io/_uploads/H1Gi9dPoR.png) ## 使用 Delay 暫停 Coroutine - Coroutine 可以使用 `delay` 函數暫停指定的時間,類似於執行緒的 `sleep` 函數,但不會阻塞整個執行緒。 - `delay` 只會暫停當前的 Coroutine,不會影響同一執行緒中的其他 Coroutine。 ![CleanShot 2024-08-24 at 23.23.13](https://hackmd.io/_uploads/rk21o_wjC.png) ## 主執行緒結束時的行為 - 當主執行緒結束時,所有的執行緒和 Coroutine 也會被取消,即使它們在其他執行緒中運行。 - 這可以通過延長 `delay` 時間並立即退出應用來觀察,這樣 Coroutine 的輸出就不會顯示出來。 ![CleanShot 2024-08-24 at 23.24.09](https://hackmd.io/_uploads/SkH7s_PiA.png) # Suspend Functions ## Coroutine 中的 Suspend 函數介紹 - `delay` 函數是一個 `suspend` 函數,它只能在另一個 `suspend` 函數或 Coroutine 中調用。 Ctrl + Q ![CleanShot 2024-08-24 at 23.27.49](https://hackmd.io/_uploads/ByQZ2dviR.png) ![CleanShot 2024-08-24 at 23.28.59](https://hackmd.io/_uploads/rJPHh_wjR.png) - 在 `GlobalScope.launch` 中啟動 Coroutine 後,可以調用 `delay` 函數來暫停 Coroutine。 ## Suspend 函數的特點 - Suspend 函數只能在 Coroutine 或其他 Suspend 函數內部調用。 - 這與一般函數不同,無法在普通上下文中直接使用 Suspend 函數。 ## 自定義 Suspend 函數 - 可以自定義 Suspend 函數,像這樣: ![CleanShot 2024-08-24 at 23.29.33](https://hackmd.io/_uploads/rJFwh_PiA.png) ![CleanShot 2024-08-24 at 23.29.56](https://hackmd.io/_uploads/S1lYndvoR.png) - 在 Coroutine 內部可以調用這些自定義的 Suspend 函數,模擬一些需要時間的操作,例如網絡請求。 ## 多個 Suspend 函數的影響 - 當在同一 Coroutine 中依次調用多個 Suspend 函數時,它們的執行時間會累加。 - 例如,兩個各延遲 3 秒的 Suspend 函數在同一 Coroutine 中調用時,總共會延遲 6 秒。 ![CleanShot 2024-08-24 at 23.30.38](https://hackmd.io/_uploads/rk5jhuPsC.png) ![CleanShot 2024-08-24 at 23.30.31](https://hackmd.io/_uploads/ryBsh_woA.png) # Coroutine Contexts ## Coroutine Context 簡介 - Coroutine 是在特定的上下文(Context)中啟動的,這個上下文決定了 Coroutine 會在哪個執行緒中運行。 - 之前我們只使用了 `GlobalScope.launch` 來啟動 Coroutine,這沒有給予我們太多的控制。 ## 使用 Dispatchers 控制 Coroutine 的執行緒 ![CleanShot 2024-08-24 at 23.33.09](https://hackmd.io/_uploads/rybBTuDjR.png) | **Dispatcher** | **Dispatchers.Main** | **Dispatchers.IO** | **Dispatchers.Default** | **Dispatchers.Unconfined** | |--------------------------|----------------------------------------------------------|---------------------------------------------------------|-------------------------------------------------------------|----------------------------------------------------------------| | **特性** | - 主要在 Android 中使用<br>- 使用 UI 線程執行 | - 用於 I/O 密集型任務<br>- 具有比 Default 更高的線程池數量 | - 用於 CPU 密集型任務<br>- 線程池數量與 CPU 核心數相當 | - 不限於特定線程<br>- 根據掛起函數的繼續位置決定在哪個線程運行 | | **適用場景** | - 更新 UI 元素<br>- 處理與用戶交互相關的邏輯 | - 網路請求<br>- 檔案讀寫<br>- 資料庫操作 | - 複雜的計算任務<br>- 資料處理<br>- 大型數據操作 | - 只適合簡單任務<br>- 通常用於測試或一些特殊場景 | | **應用範例 1** | **即時天氣應用**<br>- 從 API 獲取天氣資料後,使用 `Dispatchers.Main` 將資料顯示在 UI 上,並更新天氣狀態。 | **即時天氣應用**<br>- 從 API 獲取天氣資料(如溫度、濕度)並保存到本地資料庫,使用 `Dispatchers.IO` 處理。 | **即時天氣應用**<br>- 根據多天的天氣數據進行計算,預測未來幾天的天氣趨勢和降水概率,使用 `Dispatchers.Default` 處理這些複雜計算。 | **即時天氣應用**<br>- 在開發過程中快速驗證 UI 相關行為的執行,使用 `Dispatchers.Unconfined` 進行測試。 | | **應用範例 2** | **新聞閱讀器應用**<br>- 在加載完新聞內容後,使用 `Dispatchers.Main` 更新 UI 以顯示文章和圖片。 | **新聞閱讀器應用**<br>- 從遠程伺服器下載最新的新聞文章並儲存到本地緩存,使用 `Dispatchers.IO` 處理。 | **新聞閱讀器應用**<br>- 解析新聞資料,根據用戶的興趣生成個性化新聞推送,使用 `Dispatchers.Default` 處理這些計算。 | **新聞閱讀器應用**<br>- 在開發過程中進行快速原型設計,使用 `Dispatchers.Unconfined` 來測試文章顯示邏輯。 | | **應用範例 3** | **健身追蹤應用**<br>- 當用戶完成運動後,使用 `Dispatchers.Main` 更新運動數據到 UI,顯示卡路里消耗等資訊。 | **健身追蹤應用**<br>- 從穿戴式設備獲取用戶的運動數據(如步數和心率),並將這些數據儲存到本地資料庫,使用 `Dispatchers.IO` 處理。 | **健身追蹤應用**<br>- 分析用戶的運動數據,生成運動報告和建議,使用 `Dispatchers.Default` 處理這些資料分析和計算。 | **健身追蹤應用**<br>- 在開發過程中快速測試不同運動算法的效果,使用 `Dispatchers.Unconfined` 進行實驗。 | - 我們可以透過傳遞 `Dispatcher` 參數給 `launch` 函數來控制 Coroutine 的執行緒。 - 常用的 Dispatchers 包括: - `Dispatchers.Main`: 在主執行緒中啟動 Coroutine,適合進行 UI 操作,因為只能從主執行緒更改 UI。 - `Dispatchers.IO`: 用於執行資料操作,例如網路請求、資料庫操作或檔案讀寫。 - `Dispatchers.Default`: 適合執行複雜且長時間運行的計算,避免阻塞主執行緒和 UI。 - `Dispatchers.Unconfined`: 不受限於特定執行緒,Coroutine 會在上次暫停後恢復的執行緒中繼續運行。 - `newSingleThreadContext`: 可以創建一個新的執行緒並在其中運行 Coroutine。 ![CleanShot 2024-08-24 at 23.55.12](https://hackmd.io/_uploads/BJpDzYwi0.png) ## 在 Coroutine 中切換上下文 - Coroutine 的強大之處在於可以輕鬆地在不同的上下文之間切換。 - 例如,我們可以在 IO Dispatcher 中執行網路請求,然後切換到 Main Dispatcher 來更新 UI。 ## 例子:網路請求與 UI 更新 ![CleanShot 2024-08-24 at 23.57.17](https://hackmd.io/_uploads/ByKyQFDjR.png) - 在 IO Dispatcher 中執行網路請求並獲取結果: ```kotlin val answer = doNetworkCall() ``` - 然後使用 `withContext` 切換到 Main Dispatcher 來更新 UI: ```kotlin withContext(Dispatchers.Main) { tvDummy.text = answer } ``` - 使用 `Log` 來檢查 Coroutine 執行緒的切換: - 在 IO Dispatcher 中記錄當前執行緒: ```kotlin Log.d("MainActivity", "Starting coroutine in thread: ${Thread.currentThread().name}") ``` - 在 Main Dispatcher 中記錄更新 UI 的執行緒: ```kotlin Log.d("MainActivity", "Setting text in thread: ${Thread.currentThread().name}") ``` ## 執行結果 - 當應用運行時,可以看到在不同的執行緒中分別執行網路請求和 UI 更新操作,確認 Coroutine Context 的有效性。 # runBlocking ## `runBlocking` 函數介紹 - `runBlocking` 函數會在主執行緒中啟動一個 Coroutine 並且會阻塞主執行緒。 - 與 `GlobalScope.launch(Dispatchers.Main)` 不同,`runBlocking` 會完全阻塞主執行緒,使 UI 更新也無法進行。 ## 使用情境 - 使用 `runBlocking` 可以在主執行緒中執行 Suspend 函數,而不需要異步執行。 - 在測試中可以使用 `runBlocking` 來同步執行 Suspend 函數。 - 適合用來快速測試或了解 Coroutine 的行為。 ## `runBlocking` 的執行流程 - 在 `runBlocking` 中調用 `delay` 函數,會阻塞主執行緒,類似於使用 `Thread.sleep`。 - 可以在 `runBlocking` 中使用 Suspend 函數,因為它提供了 Coroutine Scope。 ## 範例與日誌驗證 ![CleanShot 2024-08-25 at 00.27.07](https://hackmd.io/_uploads/By7e5YDj0.png) - 在 `runBlocking` 區塊前後添加日誌,並在區塊內部與外部調用 `delay` 進行比較: ```kotlin Log.d("MainActivity", "Before runBlocking") runBlocking { Log.d("MainActivity", "Start of runBlocking") delay(5000) Log.d("MainActivity", "End of runBlocking") } Log.d("MainActivity", "After runBlocking") ``` - 執行結果會顯示: - `Before runBlocking` - `Start of runBlocking` - 延遲 5 秒 - `End of runBlocking` - `After runBlocking` - 移除 `runBlocking` 並改用 `Thread.sleep` 也會得到相同結果,但無法在 `Thread.sleep` 中調用 Suspend 函數。 ## 在 `runBlocking` 中啟動 Coroutine ![CleanShot 2024-08-25 at 00.28.32](https://hackmd.io/_uploads/SyCV9KwiR.png) - 可以在 `runBlocking` 中啟動新的 Coroutine,這些 Coroutine 不會被阻塞,並且可以使用不同的 Dispatcher。 - 例子: ```kotlin runBlocking { launch(Dispatchers.IO) { delay(3000) Log.d("MainActivity", "Finished IO coroutine 1") } launch(Dispatchers.IO) { delay(3000) Log.d("MainActivity", "Finished IO coroutine 2") } Log.d("MainActivity", "End of main thread coroutine") } ``` - 結果顯示兩個 IO Coroutine 同時結束,並在主執行緒的 Coroutine 結束後打印結果。 # Jobs, Waiting, Cancelation ## Coroutine 中的 Job 介紹 - 啟動 Coroutine 時會返回一個 `Job`。 - `Job` 可用於管理 Coroutine,如等待完成或取消。 ## 等待 Coroutine 完成 - 使用 `job.join()` 等待 Coroutine 完成,阻塞當前執行緒。 - 可以在 `runBlocking` 中使用 `job.join()`。 ## 範例:使用 Job 等待 Coroutine 完成 - 啟動 Coroutine 並使用 `job.join()` 等待其完成。 - 在 Coroutine 完成後再繼續執行後續程式碼。 ![CleanShot 2024-08-25 at 00.32.27](https://hackmd.io/_uploads/SkKQsYvjR.png) ![CleanShot 2024-08-25 at 00.31.37](https://hackmd.io/_uploads/rkpgoKPoA.png) ## 取消 Coroutine 的 Job - 使用 `job.cancel()` 來取消正在運行的 Coroutine。 - 在取消後,Coroutine 內部未完成的工作將不再執行。 ![CleanShot 2024-08-25 at 00.32.38](https://hackmd.io/_uploads/BJ7VstPoA.png) ![CleanShot 2024-08-25 at 00.33.03](https://hackmd.io/_uploads/ryQIoFPiA.png) ## 協作式的 Coroutine 取消 - 取消 Coroutine 是協作式的,Coroutine 必須檢查取消狀態。 - 如果 Coroutine 執行長時間運算,需手動檢查是否被取消。 ![CleanShot 2024-08-25 at 00.34.18](https://hackmd.io/_uploads/By6qjYwoR.png) ![CleanShot 2024-08-25 at 00.34.41](https://hackmd.io/_uploads/HJ0iiFviC.png) ## 範例:手動檢查 Coroutine 取消狀態 ![CleanShot 2024-08-25 at 00.35.26](https://hackmd.io/_uploads/H1iRjFPjR.png) - 當 Coroutine 執行長時間計算時,使用 `isActive` 檢查是否被取消。 ## 使用 `withTimeout` 設定超時 ![CleanShot 2024-08-25 at 00.36.18](https://hackmd.io/_uploads/HkzGntwi0.png) - `withTimeout` 用於設置 Coroutine 的超時時間。 - 若 Coroutine 運行超過指定時間,將自動取消。 # Async and Await ## `async` 與 `await` 函數介紹 - `async` 和 `await` 用於在 Coroutine 中執行多個異步操作。 - 預設情況下,Coroutine 中的多個 `suspend` 函數是順序執行的。 ## 範例:順序執行的問題 ![CleanShot 2024-08-25 at 00.40.44](https://hackmd.io/_uploads/ByFfaYwsA.png) - 當我們執行兩個網路請求時,預設它們會一個接一個地執行,導致總耗時累加。 - 在這個範例中,每個網路請求需要 3 秒,所以總共需要 6 秒。 ## 測量執行時間 - 使用 `measureTimeMillis` 來測量一段代碼的執行時間。 ![CleanShot 2024-08-25 at 00.41.06](https://hackmd.io/_uploads/BykVpKvsR.png) ## 問題解決:使用 `async` 來異步執行 ![CleanShot 2024-08-25 at 00.41.37](https://hackmd.io/_uploads/rkzI6KPsC.png) ![CleanShot 2024-08-25 at 00.41.52](https://hackmd.io/_uploads/HkhLaYPiR.png) ![CleanShot 2024-08-25 at 00.42.45](https://hackmd.io/_uploads/ByfcTtvsC.png) - 使用 `async` 啟動新的 Coroutine,允許兩個網路請求同時執行,縮短總耗時。 - `async` 返回一個 `Deferred` 對象,可用於取得計算結果。 - 使用 `await` 函數來等待 `Deferred` 的結果,而不會阻塞其他操作。 ## 壞習慣的示範與優化 - 壞習慣:使用多個 `launch` 來啟動 Coroutine,並使用 `join` 來等待它們完成。 - 優化:使用 `async` 取代 `launch`,並用 `await` 來獲取結果,這種方式更簡潔且有效。 ## 注意事項 - 當需要 Coroutine 返回結果時,應使用 `async` 而非 `launch`。 - 若 Coroutine 無需返回結果,則應使用 `launch`。 # lifecycleScope and viewModelScope ## Coroutine Scopes 簡介 - `GlobalScope` 用於啟動一個與應用程式生命週期相同的 Coroutine,但通常不建議使用,因為大多數情況下不需要 Coroutine 活到應用程式結束。 - 在 Android 中,有兩個非常實用的預定義 Coroutine 範圍:`LifecycleScope` 和 `ViewModelScope`。 ![CleanShot 2024-08-25 at 00.57.28](https://hackmd.io/_uploads/H1Ob-cDi0.png) ## `LifecycleScope` 介紹 - `LifecycleScope` 允許 Coroutine 隨著 Activity 或 Fragment 的生命週期而管理。 - 當 Activity 或 Fragment 被銷毀時,所有在該 `LifecycleScope` 中啟動的 Coroutine 也會被自動取消。 ## 範例:避免使用 `GlobalScope` 的問題 ![CleanShot 2024-08-25 at 00.58.40](https://hackmd.io/_uploads/S10rW9wiC.png) - 使用 `GlobalScope` 啟動 Coroutine 會導致即使 Activity 被銷毀,Coroutine 仍然繼續執行,這可能導致記憶體洩漏。 - 若 Coroutine 使用了已被銷毀的 Activity 的資源,這些資源將無法被垃圾回收。 ## 修正:使用 `LifecycleScope` ![CleanShot 2024-08-25 at 00.59.11](https://hackmd.io/_uploads/ryRwb5viC.png) - 將 `GlobalScope` 替換為 `LifecycleScope` 可以避免上述問題。 - 在 `LifecycleScope` 中啟動的 Coroutine 會隨著 Activity 被銷毀而自動取消,避免資源泄漏。 ## `ViewModelScope` 介紹 - `ViewModelScope` 允許 Coroutine 隨著 ViewModel 的生命週期而管理。 - 與 `LifecycleScope` 類似,但它會讓 Coroutine 保持存活直到 ViewModel 被清除。 ## 注意事項 - 若 Coroutine 執行長時間的運算且不暫停,應定期檢查是否仍處於活動狀態,以便能夠正確取消。 - `LifecycleScope` 也適用於 Fragment 的生命週期管理。 # Coroutines with Firebase Firestore ## Coroutine 與 Firebase Firestore 的結合 - 當使用 Firebase Firestore 時,經常會遇到所謂的「回調地獄」(callback hell),這是由於多個網路操作相互依賴,並且依賴回調函數來獲取數據。 - Coroutine 可以幫助解決這個問題,因為它們能夠暫停並恢復執行,不需要依賴回調函數。 ![CleanShot 2024-08-25 at 01.19.49](https://hackmd.io/_uploads/rkaSL5Ps0.png) ## 範例:使用 Coroutine 儲存與讀取資料 - 要將 Coroutine 與 Firestore 結合使用,需要加入 `kotlinx-coroutines-play-services` 依賴。 - 透過 Coroutine,Firestore 的操作變得更為簡潔與直觀。 ![CleanShot 2024-08-25 at 01.20.33](https://hackmd.io/_uploads/rkTv8cPj0.png) ## 使用 Coroutine 儲存資料 - 在 Coroutine 中使用 `set` 函數將資料儲存到 Firestore 文件中,並使用 `await` 來等待操作完成。 - `await` 是一個 suspend 函數,會阻塞 Coroutine 直到操作完成。 ![CleanShot 2024-08-25 at 01.22.08](https://hackmd.io/_uploads/ryl0UqDj0.png) ![CleanShot 2024-08-25 at 01.22.24](https://hackmd.io/_uploads/SJoR8qwj0.png) ## 使用 Coroutine 讀取資料 - 使用 `get` 函數來獲取 Firestore 文件中的資料,並使用 `await` 等待資料返回。 ![CleanShot 2024-08-25 at 01.22.41](https://hackmd.io/_uploads/rJT1P5voC.png) - 將返回的資料轉換為 Kotlin 對象後,透過 `withContext` 切換到主線程更新 UI。 ![CleanShot 2024-08-25 at 01.23.21](https://hackmd.io/_uploads/Hk8Gw9viR.png) ## 範例總結 - Coroutine 讓 Firestore 的異步操作更加容易且避免了回調地獄。 ![CleanShot 2024-08-25 at 01.23.53](https://hackmd.io/_uploads/SyKVD5Ds0.png) - Coroutine 具備生命週期感知功能,可以避免在 Activity 被銷毀後仍然執行異步操作。 ## 其他注意事項 - 在使用 Coroutine 處理 Firestore 操作時,確保在合適的上下文中執行,以避免不必要的資源佔用。 # Coroutines with Retrofit ## Retrofit 與 Coroutine 的結合 - 傳統上,我們使用 `enqueue` 來進行 Retrofit 網路請求,這會在一個新的執行緒中執行請求並透過回調通知結果。 - 這種方式雖然有效,但效率較低,因為會啟動一個新的執行緒。 ![CleanShot 2024-08-25 at 01.26.45](https://hackmd.io/_uploads/rJGydcPjA.png) ## 使用 Coroutine 進行網路請求 - 使用 Coroutine 可以避免啟動額外的執行緒,代碼更簡潔高效。 - 可以在 Coroutine 中使用 `await` 來等待請求結果,並直接獲取資料。 ![CleanShot 2024-08-25 at 01.27.36](https://hackmd.io/_uploads/SJ4zu5Do0.png) ## 範例:使用 Coroutine 獲取資料 - 在 Coroutine 中使用 `await` 來等待 Retrofit 請求並獲取結果,將結果直接用於後續操作。 - 如果需要處理請求錯誤,可以使用 `awaitResponse` 來獲取整個 `Response` 對象,並檢查請求是否成功。 ![CleanShot 2024-08-25 at 01.28.04](https://hackmd.io/_uploads/HymVO5vjR.png) ![CleanShot 2024-08-25 at 01.28.14](https://hackmd.io/_uploads/r1s4d5viC.png) ## 優化方式:直接使用 Suspend 函數 - 可以在 Retrofit 的 API 介面中將函數定義為 `suspend` 函數,直接返回資料或 `Response` 對象,避免額外的 `Call` 包裝。 - 使用 `suspend` 函數後,Coroutine 會自動處理異步操作,代碼更簡潔,易於維護。 ![CleanShot 2024-08-25 at 01.28.22](https://hackmd.io/_uploads/By4rOqvjR.png) ## 總結 - 結合 Coroutine 與 Retrofit 不僅能減少代碼量,還能提升應用程式的執行效率。 - 直接使用 `suspend` 函數讓網路請求的處理更加直觀,且簡化了錯誤處理的邏輯。 # In-Depth Guide to Coroutine Cancellation & Exception Handling ## Coroutine 中的例外處理與取消機制 - Coroutine 是處理非同步程式設計的強大工具,但當深入了解時,會發現許多陷阱,特別是在例外處理和取消機制方面。 ![CleanShot 2024-08-25 at 02.00.50](https://hackmd.io/_uploads/ByiJgjDiC.png) ## 基本例外處理的誤解 - 許多人認為可以直接使用 `try-catch` 區塊來捕捉 Coroutine 中的例外,但這通常無效。 ![CleanShot 2024-08-25 at 02.01.33](https://hackmd.io/_uploads/SJKzgiPo0.png) - 如果例外未在 Coroutine 中直接處理(即未在引發例外的 Coroutine 中使用 `try-catch`),則例外將被傳遞到父 Coroutine,最終導致應用程序崩潰。 ![CleanShot 2024-08-25 at 02.02.37](https://hackmd.io/_uploads/HJiBgswiA.png) ## Coroutine Scope 的結構 - Coroutine Scope(例如 `LifecycleScope`、`ViewModelScope`)通常會啟動一個 Coroutine,而這個 Coroutine 可以啟動子 Coroutine。 - 如果子 Coroutine 中發生未捕捉的例外,該例外將被向上傳遞到父 Coroutine,直到外層 Coroutine 捕捉該例外或應用程序崩潰。 ## 例外傳遞與取消 - 當 Coroutine 被取消時,會引發 `CancellationException`,這並不會導致應用崩潰,但會被傳遞到所有相關的 Coroutine 中,以通知它們已被取消。 - 例外處理與取消機制類似,例外或取消狀態會向上傳遞,直到被適當處理。 ## 使用 `launch` 與 `async` 的差異 - `launch` 用於啟動一個不需要返回值的 Coroutine,而 `async` 用於啟動一個需要返回值的 Coroutine。 ![CleanShot 2024-08-25 at 02.04.00](https://hackmd.io/_uploads/HyKsgoPsA.png) - `async` 會返回一個 `Deferred`,可以使用 `await` 來獲取結果或捕捉例外。 - 如果 `async` 中發生例外,例外不會立即拋出,而是在調用 `await` 時才拋出。 ## `async` 中的例外處理 ![CleanShot 2024-08-25 at 02.04.39](https://hackmd.io/_uploads/SJSplsvs0.png) - 如果在 `async` 中發生例外,例外會在調用 `await` 時被拋出,而不是立即拋出。 - 如果 `async` 的結果未被等待(未調用 `await`),則不會立即發生崩潰。 - 在調用 `await` 時,可以使用 `try-catch` 來捕捉例外。 ![CleanShot 2024-08-25 at 02.05.28](https://hackmd.io/_uploads/r18x-jPoR.png) ![CleanShot 2024-08-25 at 02.05.37](https://hackmd.io/_uploads/H1AeZjPjC.png) ## 錯誤的例外處理方式 - 雖然可以在 `await` 語句中使用 `try-catch` 捕捉例外,但這並不是推薦的做法。 - 存在更好的方式來處理 Coroutine 中的例外與取消機制,應避免直接在 `await` 語句中使用 `try-catch`。 ## Coroutine 中的例外處理與 `CoroutineExceptionHandler` ![CleanShot 2024-08-25 at 03.13.56](https://hackmd.io/_uploads/SyyzZ3voR.png) ![CleanShot 2024-08-25 at 03.14.17](https://hackmd.io/_uploads/BkVGW2viA.png) ![CleanShot 2024-08-25 at 03.14.57](https://hackmd.io/_uploads/rJfSWhvsC.png) ![CleanShot 2024-08-25 at 03.15.15](https://hackmd.io/_uploads/BkJ8WhDo0.png) - 當我們想處理未捕捉的例外時,可以使用 `try-catch` 區塊,但必須在引發例外的 Coroutine 內部處理,或在 `async` 的 `await` 調用時處理。 - 另一種方法是使用 `CoroutineExceptionHandler`,這是一個特定的處理器,能夠處理未捕捉的例外。 ## 使用 `CoroutineExceptionHandler` 的例外處理 - 可以在 Coroutine 的根範圍內安裝 `CoroutineExceptionHandler`,以捕捉所有從子 Coroutine 傳遞上來的未捕捉例外。 - 這種方式不會處理取消例外 (`CancellationException`),因為取消例外是由 Coroutine 內部處理的,並不會導致應用崩潰。 ## Coroutine 範圍:`CoroutineScope` 與 `SupervisorScope` - 在 Coroutine 中,有兩種範圍決定子 Coroutine 的取消方式:`CoroutineScope` 和 `SupervisorScope`。 - `CoroutineScope`:當任何一個子 Coroutine 發生例外時,會取消所有的子 Coroutine 以及整個範圍。 - `SupervisorScope`:當一個子 Coroutine 發生例外時,其他子 Coroutine 不會受到影響,繼續執行。 ## 範例:`CoroutineScope` 對比 `SupervisorScope` - 使用 `CoroutineScope` 時,若一個子 Coroutine 失敗,所有子 Coroutine 都會被取消,即使已經捕捉了例外。 ![CleanShot 2024-08-25 at 03.16.42](https://hackmd.io/_uploads/B1PhZnwsC.png) ![CleanShot 2024-08-25 at 03.17.09](https://hackmd.io/_uploads/By-6-2DoA.png) - 使用 `SupervisorScope` 時,若一個子 Coroutine 失敗,其他子 Coroutine 仍會繼續執行,不會因例外被取消。 ![CleanShot 2024-08-25 at 03.17.37](https://hackmd.io/_uploads/Skykf3viC.png) | 特性 | CoroutineScope | SupervisorScope | |--------------------------|------------------------------------------------|------------------------------------------------| | 子協程失敗處理 | 如果任何一個子協程失敗,會取消整個範圍 (Scope) 並取消所有子協程。 | 失敗的子協程不會影響其他子協程或協程範圍 (Scope),失敗只會影響該協程。 | | 適用情境 | 當多個子協程相互依賴且需要所有協程都成功時。 | 當多個子協程彼此獨立且不希望一個協程失敗影響其他協程時。 | | 使用場合 | 需要管理相依的工作流程,如其中一個失敗需要全部取消。 | 需要管理獨立的工作流程,如其中一個失敗不影響整體運作。 | | 範圍終止 | 當任何一個子協程失敗時,會取消整個範圍。 | 只有在範圍中的所有協程都完成或被手動取消時,範圍才會終止。 | | 範例一:資料同步 App | 同步用戶資料到多個服務器,若任一伺服器同步失敗,需取消所有同步操作。 | 下載多個伺服器的資料,如果一個伺服器下載失敗,不影響其他伺服器的下載。| | 範例二:影片處理 App | 合併多段影片,如果其中一段合併失敗,應取消整個合併過程。 | 轉換多段影片格式,如果其中一段轉換失敗,其他段落繼續轉換。 | | 範例三:社交媒體 App | 發送多個通知至不同平台,若任一平台發送失敗,取消所有發送操作。 | 從多個社交平台拉取消息,若一個平台失敗,不影響其他平台的消息拉取。 | ## 常見錯誤:不正確的例外處理 - 在 Coroutine 中使用 `try-catch` 捕捉例外時,若捕捉了 `CancellationException`,這會導致外層範圍無法感知到取消,進而繼續執行已被取消的 Coroutine。 ![CleanShot 2024-08-25 at 03.42.47](https://hackmd.io/_uploads/HyS6vhDj0.png) ![CleanShot 2024-08-25 at 03.43.16](https://hackmd.io/_uploads/r1by_3Ps0.png) - 修正方法:要麼僅捕捉特定的例外(如 `HttpException`),要麼在捕捉一般例外時,檢查是否為 `CancellationException`,並重新拋出它。 ![CleanShot 2024-08-25 at 03.44.11](https://hackmd.io/_uploads/B1vM_2DiA.png) ![CleanShot 2024-08-25 at 03.44.34](https://hackmd.io/_uploads/H16XuhDiR.png) ![CleanShot 2024-08-25 at 03.44.43](https://hackmd.io/_uploads/HJKN_nDjC.png) ## 深入理解與資源浪費問題 - 當 Coroutine 被取消時,若沒有正確處理取消例外,可能導致子 Coroutine 繼續執行,造成不必要的資源消耗。 - 通常,我們希望在取消發生時,立即終止 Coroutine 的執行,以避免資源浪費。 # [協程的例外處理](https://hackmd.io/@RainBowT/ryuTWjvjR) # Terminology - **Kotlin**:一種現代化、靜態類型的程式語言,由 JetBrains 開發,並且是 Android 開發的官方支援語言。 - **協程 (Coroutine)**:Kotlin 中一種輕量級的執行單位,類似於執行緒 (Thread),但更加靈活和高效。 - **函數 (Function)**:一組可重用的程式碼指令,接受輸入參數並返回輸出結果。 - **執行緒 (Thread)**:一個程序內部的執行單位,每個執行緒都可以同時執行指令。 - **主執行緒 (Main Thread)**:Android 應用中負責處理 UI 更新的主要執行緒。 - **多執行緒 (Multi-threading)**:同時執行多個執行緒的技術,可以讓程式同時處理多個任務。 - **UI 更新 (UI Update)**:在 Android 中,更新使用者界面元素的過程,通常在主執行緒中進行。 - **網路請求 (Network Call)**:應用程式向伺服器發送請求並等待回應的過程。 - **暫停 (Suspend)**:協程中可以暫時停止執行並稍後繼續的一種功能。 - **上下文切換 (Context Switching)**:協程可以從一個執行緒切換到另一個執行緒繼續執行的過程。 - **輕量級 (Lightweight)**:形容協程佔用的系統資源極少,允許大量同時執行。 - **記憶體不足錯誤 (Out of Memory Error)**:當系統無法再分配記憶體給新執行緒時發生的錯誤。 - **建構工地 (Construction Site)**:影片中的比喻,用來表示執行緒的工作環境。 - **小工人 (Little Worker)**:影片中的比喻,用來表示協程在執行緒中的工作單位。 - **指令 (Instruction)**:程式碼中的基本操作步驟。 - **平行運行 (Run in Parallel)**:多個執行緒或協程同時執行的過程。 - **程序 (Program)**:一個電腦程式的整體結構,包括所有執行緒和指令。 - **暫停執行 (Pause Execution)**:暫時停止協程的運行,並在稍後繼續。 - **程式設計師 (Programmer)**:控制協程和執行緒行為的開發者。 - **Android 應用 (Android App)**:一個在 Android 平台上運行的應用程式。 - **依賴項 (Dependencies)**:在專案中必須包含的外部庫或模組,以便使用特定功能。 - **build.gradle**:一個用於配置 Android 專案的檔案,其中定義了專案的依賴項、插件等。 - **Global Scope**:Kotlin 中的一個協程範圍,協程在這個範圍中會與應用程式的生命週期一致。 - **launch**:啟動一個新的協程,並在指定的範圍內執行一系列指令。 - **協程範圍 (Coroutine Scope)**:協程的執行上下文,決定協程的生命週期和執行範圍。 - **異步執行 (Asynchronously Execute)**:在不阻塞主執行緒的情況下執行任務。 - **Log.d**:用於在 Android 應用中輸出除錯訊息的函數。 - **tag**:在 Android 中標識除錯訊息來源的標籤,通常是類名或模組名。 - **執行緒名稱 (Thread Name)**:表示執行緒的名稱,用於辨識不同的執行緒。 - **主執行緒 (Main Thread)**:應用程式的主執行緒,負責處理 UI 更新和主要邏輯。 - **預設分派器 (Default Dispatcher)**:Kotlin 協程中的預設執行緒池,負責協程的執行。 - **延遲 (Delay)**:Kotlin 協程中的函數,用於暫停協程一段時間而不阻塞執行緒。 - **暫停 (Pause)**:暫時停止協程的執行,稍後可繼續。 - **執行緒阻塞 (Thread Blocking)**:當執行緒停止執行並等待某個事件或操作完成時的狀態。 - **sleep**:一個執行緒方法,用於暫停執行緒的執行一段時間。 - **Lockcat**:Android 中的日誌系統,用於查看應用程式的輸出訊息和錯誤。 - **預設分派器工人 (Default Dispatcher Worker)**:在預設分派器中執行工作的執行緒。 - **建構工地 (Construction Site)**:影片中的比喻,用於表示執行緒的工作環境。 - **應用程式生命週期 (Application Lifecycle)**:應用程式從啟動到結束的整個過程。 - **取消協程 (Cancel Coroutine)**:當協程範圍結束或條件觸發時,終止協程的執行。 - **協程 (Coroutine)**:Kotlin 中的輕量級執行單位,可以在一個執行緒內運行多個協程。 - **延遲 (Delay)**:協程中的一個函數,用於暫停協程執行一段時間而不阻塞執行緒。 - **全域範圍 (Global Scope)**:Kotlin 中協程的一個範圍,協程在這個範圍中會與應用程式的生命週期一致。 - **掛起函數 (Suspend Function)**:Kotlin 中一種特殊的函數,只有在協程或其他掛起函數內才能執行。 - **掛起 (Suspend)**:暫時停止協程的執行,等待特定條件達成後繼續執行。 - **網路請求 (Network Call)**:應用程式向伺服器發送請求並等待回應的過程。 - **字串 (String)**:程式設計中的一種資料型態,表示文字資料。 - **返回值 (Return Value)**:函數執行後返回的結果。 - **延遲函數 (Delay Function)**:掛起函數的一種,用於延遲協程的執行。 - **Android Studio**:Google 推出的官方集成開發環境 (IDE),用於 Android 應用程式的開發。 - **錯誤 (Error)**:程式執行過程中發生的問題,可能會導致程式停止運行。 - **執行緒 (Thread)**:一個程序內部的執行單位,每個執行緒都可以同時執行指令。 - **模擬 (Simulate)**:在程式設計中,用人為設置的環境或條件來模擬真實情況。 - **印出 (Print)**:將資料輸出到控制台或日誌系統,用於除錯或記錄。 - **日誌 (Log)**:記錄程式執行過程中產生的訊息,用於分析和除錯。 - **重複 (Duplicate)**:複製程式碼片段,使其多次出現在不同的地方。 - **掛起箭頭 (Suspend Arrow)**:在 Android Studio 中,標記掛起函數的小箭頭圖示。 - **錯誤訊息 (Error Message)**:程式發生錯誤時顯示的訊息,用於幫助開發者解決問題。 - **主執行緒 (Main Thread)**:應用程式的主執行緒,負責處理 UI 更新和主要邏輯。 - **程式模擬 (Program Simulation)**:在開發過程中,用假資料或條件模擬程式的真實運行情況。 - **協程上下文 (Coroutine Context)**:定義協程在哪個執行緒或執行環境中運行的配置。 - **分派器 (Dispatcher)**:Kotlin 協程中的一個組件,決定協程應該在哪個執行緒或執行環境中執行。 - **Global Scope**:Kotlin 中的一個協程範圍,協程在這個範圍中會與應用程式的生命週期一致。 - **Dispatchers.Main**:分派器的一種,指定協程在主執行緒(UI 執行緒)中運行,適合用於更新 UI。 - **Dispatchers.IO**:分派器的一種,適合用於執行 I/O 密集型操作,如網路請求、文件讀寫等。 - **Dispatchers.Default**:分派器的一種,適合用於執行 CPU 密集型操作,如複雜計算或長時間運行的任務。 - **Dispatchers.Unconfined**:分派器的一種,協程在任何當前的執行緒中運行,而不會固定在特定的執行緒上。 - **新單一執行緒上下文 (New Single Thread Context)**:創建一個新的執行緒,並將協程運行在該執行緒中。 - **withContext**:Kotlin 中的一個函數,用於在協程內部切換上下文,讓協程在不同的執行環境中執行。 - **延遲函數 (Delay Function)**:掛起函數的一種,用於暫停協程的執行。 - **I/O 密集型操作 (I/O Intensive Operation)**:涉及大量輸入輸出操作的任務,如網路請求和文件讀寫。 - **CPU 密集型操作 (CPU Intensive Operation)**:涉及大量計算或處理的任務,如數據處理或複雜算法運行。 - **掛起函數 (Suspend Function)**:Kotlin 中的一種特殊函數,只有在協程或其他掛起函數內才能執行。 - **主執行緒 (Main Thread)**:應用程式的主要執行緒,負責處理 UI 更新和主要邏輯。 - **執行緒名稱 (Thread Name)**:用於辨識不同執行緒的名稱。 - **鎖定日誌 (Logcat)**:Android 中的日誌系統,用於查看應用程式的輸出訊息和錯誤。 - **UI 操作 (UI Operation)**:更新或操作使用者介面元素的過程,通常需要在主執行緒中執行。 - **上下文切換 (Context Switching)**:在協程中切換執行環境或執行緒的過程。 - **假設網路請求 (Simulated Network Call)**:模擬一個網路請求,用於測試或演示。 - **UI 元素 (UI Element)**:使用者介面中的單個組件,如按鈕、文字框等。 - **協程 (Coroutine)**:Kotlin 中的輕量級執行單位,可以在一個執行緒內運行多個協程。 - **掛起函數 (Suspend Function)**:Kotlin 中一種特殊的函數,只有在協程或其他掛起函數內才能執行。 - **延遲 (Delay)**:協程中的一個函數,用於暫停協程執行一段時間而不阻塞執行緒。 - **全域範圍 (Global Scope)**:Kotlin 中的一個協程範圍,協程在這個範圍中會與應用程式的生命週期一致。 - **Dispatchers.Main**:分派器的一種,指定協程在主執行緒(UI 執行緒)中運行,適合用於更新 UI。 - **執行緒阻塞 (Thread Blocking)**:當執行緒停止執行並等待某個事件或操作完成時的狀態。 - **主執行緒 (Main Thread)**:應用程式的主要執行緒,負責處理 UI 更新和主要邏輯。 - **runBlocking**:Kotlin 中的一個函數,啟動一個新的協程並阻塞其所處的執行緒,直到協程執行完成。 - **非同步執行 (Asynchronous Execution)**:在不阻塞主執行緒的情況下執行任務,允許多個任務同時進行。 - **jUnit**:一個常用的 Java 單元測試框架,用於測試程式碼。 - **延遲函數 (Delay Function)**:掛起函數的一種,用於延遲協程的執行。 - **Logcat**:Android 中的日誌系統,用於查看應用程式的輸出訊息和錯誤。 - **執行緒名稱 (Thread Name)**:用於辨識不同執行緒的名稱。 - **IO 密集型操作 (I/O Intensive Operation)**:涉及大量輸入輸出操作的任務,如網路請求和文件讀寫。 - **CPU 密集型操作 (CPU Intensive Operation)**:涉及大量計算或處理的任務,如數據處理或複雜算法運行。 - **協程上下文 (Coroutine Context)**:定義協程在哪個執行緒或執行環境中運行的配置。 - **分派器 (Dispatcher)**:Kotlin 協程中的一個組件,決定協程應該在哪個執行緒或執行環境中執行。 - **上下文切換 (Context Switching)**:在協程中切換執行環境或執行緒的過程。 - **日誌訊息 (Log Message)**:用於記錄程式執行過程中的事件或狀態訊息。 - **同步執行 (Synchronous Execution)**:任務按順序執行,每個任務必須等待前一個任務完成。 - **UI 操作 (UI Operation)**:更新或操作使用者介面元素的過程,通常需要在主執行緒中執行。 - **協程 (Coroutine)**:Kotlin 中的輕量級執行單位,可以在一個執行緒內運行多個協程。 - **掛起函數 (Suspend Function)**:Kotlin 中一種特殊的函數,只有在協程或其他掛起函數內才能執行。 - **延遲 (Delay)**:協程中的一個函數,用於暫停協程執行一段時間而不阻塞執行緒。 - **全域範圍 (Global Scope)**:Kotlin 中的一個協程範圍,協程在這個範圍中會與應用程式的生命週期一致。 - **Dispatchers.Default**:分派器的一種,適合用於執行 CPU 密集型操作,如複雜計算或長時間運行的任務。 - **主執行緒 (Main Thread)**:應用程式的主要執行緒,負責處理 UI 更新和主要邏輯。 - **runBlocking**:Kotlin 中的一個函數,啟動一個新的協程並阻塞其所處的執行緒,直到協程執行完成。 - **非同步執行 (Asynchronous Execution)**:在不阻塞主執行緒的情況下執行任務,允許多個任務同時進行。 - **Job**:協程啟動後返回的控制單位,可以用來監控和控制協程的執行狀態。 - **join**:協程中的一個掛起函數,用來等待協程完成執行。 - **cancel**:用於取消協程的函數,可以在協程內部或外部調用。 - **取消例外 (Cancellation Exception)**:協程取消時拋出的例外,可以用來傳遞取消原因。 - **合作式取消 (Cooperative Cancellation)**:協程取消的一種方式,協程必須主動檢查並響應取消請求。 - **Fibonacci 函數**:一種遞歸算法,用於計算 Fibonacci 數列。 - **遞歸 (Recursion)**:函數在內部調用自身的一種編程技術,常用於解決重複性問題。 - **withTimeout**:Kotlin 中的一個掛起函數,設置協程執行的最大時限,超過時限將自動取消協程。 - **超時 (Timeout)**:在特定時間內未完成的操作將被終止或取消的機制。 - **測試 (Testing)**:在開發過程中檢查程式碼是否正確執行的過程。 - **Logcat**:Android 中的日誌系統,用於查看應用程式的輸出訊息和錯誤。 - **重複 (Repeat)**:一種編程結構,用於多次執行相同的代碼區塊。 - **協程 (Coroutine)**:Kotlin 中的輕量級執行單位,可以在一個執行緒內運行多個協程。 - **掛起函數 (Suspend Function)**:Kotlin 中一種特殊的函數,只有在協程或其他掛起函數內才能執行。 - **延遲 (Delay)**:協程中的一個函數,用於暫停協程執行一段時間而不阻塞執行緒。 - **非同步執行 (Asynchronous Execution)**:在不阻塞主執行緒的情況下執行任務,允許多個任務同時進行。 - **順序執行 (Sequential Execution)**:按照順序逐個執行任務,前一個任務完成後再開始下一個任務。 - **Dispatchers.IO**:分派器的一種,適合用於執行 I/O 密集型操作,如網路請求、文件讀寫等。 - **measureTimeMillis**:Kotlin 的一個函數,用於測量代碼塊的執行時間,以毫秒為單位返回時間長度。 - **全域範圍 (Global Scope)**:Kotlin 中的一個協程範圍,協程在這個範圍中會與應用程式的生命週期一致。 - **Job**:協程啟動後返回的控制單位,可以用來監控和控制協程的執行狀態。 - **join**:協程中的一個掛起函數,用來等待協程完成執行。 - **async**:Kotlin 中的一個函數,用於啟動一個協程並返回一個 deferred 物件,可以用於獲取協程的計算結果。 - **deferred**:一種特殊的協程結果容器,用於保存異步計算的結果。 - **await**:協程中的一個掛起函數,用於等待一個 deferred 物件完成並返回結果。 - **launch**:Kotlin 中的一個函數,用於啟動一個協程,但不返回任何結果,通常用於執行需要並行但不需要返回值的任務。 - **併發 (Concurrency)**:在同一時間處理多個任務或操作的能力,允許多個協程或執行緒同時運行。 - **重複 (Repeat)**:一種編程結構,用於多次執行相同的代碼區塊。 - **網路請求 (Network Call)**:應用程式向伺服器發送請求並等待回應的過程。 - **I/O 密集型操作 (I/O Intensive Operation)**:涉及大量輸入輸出操作的任務,如網路請求和文件讀寫。 - **主執行緒 (Main Thread)**:應用程式的主要執行緒,負責處理 UI 更新和主要邏輯。 - **測試 (Testing)**:在開發過程中檢查程式碼是否正確執行的過程。 - **協程 (Coroutine)**:Kotlin 中的輕量級執行單位,可以在一個執行緒內運行多個協程。 - **Global Scope**:Kotlin 中的一個協程範圍,協程在這個範圍中會與應用程式的生命週期一致,直到應用程式被銷毀為止。 - **生命週期範圍 (Lifecycle Scope)**:一種協程範圍,與 Android 活動或片段的生命週期相關聯,當活動或片段被銷毀時,範圍內的協程也會被取消。 - **ViewModel Scope**:一種協程範圍,與 ViewModel 的生命週期相關聯,當 ViewModel 被清除時,範圍內的協程也會被取消。 - **掛起函數 (Suspend Function)**:Kotlin 中一種特殊的函數,只有在協程或其他掛起函數內才能執行。 - **延遲 (Delay)**:協程中的一個函數,用於暫停協程執行一段時間而不阻塞執行緒。 - **無限迴圈 (Infinite Loop)**:一種程式結構,會持續不斷地執行某些指令,直到程式明確終止它。 - **內存洩漏 (Memory Leak)**:程式中不再需要的資源未被正確釋放,導致內存被佔用,影響應用程式性能。 - **Garbage Collection (GC)**:Java 和 Kotlin 中的自動內存管理機制,用於釋放不再使用的物件所佔用的內存空間。 - **Logcat**:Android 中的日誌系統,用於查看應用程式的輸出訊息和錯誤。 - **片段 (Fragment)**:Android 中的一種 UI 組件,能夠在活動中重複使用,並且有自己獨立的生命週期。 - **啟動意圖 (Intent)**:Android 中用於啟動活動或服務的訊息對象,包含目標活動或服務的信息。 - **onDestroy**:Android 活動或片段生命週期方法之一,當活動或片段被銷毀時調用,用於進行資源釋放等清理操作。 - **主執行緒 (Main Thread)**:應用程式的主要執行緒,負責處理 UI 更新和主要邏輯。 - **結束 (Finish)**:Android 活動中用於結束當前活動並返回到上一活動的方法。 - **協程取消 (Coroutine Cancellation)**:當協程範圍或生命週期結束時,終止協程的執行。 - **長時間運行計算 (Long-running Calculation)**:需要較長時間執行的計算任務,可能會阻塞執行緒或影響應用程式性能。 - **依賴項 (Dependencies)**:在專案中必須包含的外部庫或模組,以便使用特定功能。 - **空值 (Nullable)**:表示一個變數可以為空 (null) 的特性,在 Kotlin 中使用 `?` 表示可為空的類型。 - **協程 (Coroutine)**:Kotlin 中的輕量級執行單位,可以在一個執行緒內運行多個協程。 - **Global Scope**:Kotlin 中的一個協程範圍,協程在這個範圍中會與應用程式的生命週期一致,直到應用程式被銷毀為止。 - **Firebase Firestore**:Firebase 的雲端 NoSQL 資料庫服務,用於儲存和同步應用程式資料。 - **回呼地獄 (Callback Hell)**:嵌套多層回呼函數導致程式碼難以維護的情況,通常發生於多個異步操作依賴於彼此的情況。 - **Lambda 函數**:一種匿名函數,可以被作為參數傳遞或在程式碼中定義,用於簡化回呼函數的使用。 - **KTX**:Kotlin 擴展庫 (Kotlin Extensions),專為 Kotlin 語言設計的 API 優化,簡化了 Android 和 Firebase 的使用。 - **依賴項 (Dependencies)**:在專案中必須包含的外部庫或模組,以便使用特定功能。 - **掛起函數 (Suspend Function)**:Kotlin 中一種特殊的函數,只有在協程或其他掛起函數內才能執行。 - **等待 (Await)**:協程中的一個掛起函數,用於等待非同步任務的完成,並返回結果。 - **Task**:Firebase 操作的返回類型,表示一個非同步操作的結果。 - **toObject**:Firestore 提供的一個函數,用於將文件數據轉換為指定的 Kotlin 類型。 - **withContext**:Kotlin 中的一個函數,用於在協程內部切換上下文,讓協程在不同的執行環境中執行。 - **Logcat**:Android 中的日誌系統,用於查看應用程式的輸出訊息和錯誤。 - **生命週期範圍 (Lifecycle Scope)**:一種協程範圍,與 Android 活動或片段的生命週期相關聯,當活動或片段被銷毀時,範圍內的協程也會被取消。 - **Kotlin Data Class**:Kotlin 中一種特殊的類型,用於表示數據結構,簡化了 getter、setter 和其他常用方法的定義。 - **UI 線程 (UI Thread)**:負責處理 Android 應用程式界面更新的線程,任何界面操作都必須在此線程中執行。 - **非同步操作 (Asynchronous Operation)**:不會阻塞主線程的操作,允許在其他線程上執行任務。 - **Intent**:Android 中用於啟動活動或服務的消息對象,包含目標活動或服務的信息。 - **集合 (Collection)**:Firestore 中的數據結構,相當於數據庫中的表,包含多個文檔。 - **協程 (Coroutine)**:Kotlin 中的輕量級執行單位,可以在一個執行緒內運行多個協程。 - **Retrofit**:一個強大的 Android 和 Java HTTP 客戶端,用於網路請求和 RESTful API 通訊。 - **JSONPlaceholder**:一個免費的 REST API,用於測試和原型設計,提供虛擬的資料來測試應用程式。 - **`enqueue`**:Retrofit 的一個方法,用於非同步執行 HTTP 請求並透過回呼處理結果。 - **回呼 (Callback)**:一種常見的非同步編程模式,用於在操作完成後通知結果。 - **`await`**:Kotlin 協程中的一個掛起函數,用於等待非同步操作完成並返回結果。 - **Dispatchers.IO**:分派器的一種,適合用於執行 I/O 密集型操作,如網路請求、文件讀寫等。 - **`awaitResponse`**:Retrofit 和 Kotlin 協程一起使用時的一個方法,用於等待並返回完整的 HTTP 回應。 - **回應物件 (Response Object)**:包含 HTTP 回應資料的物件,包括狀態碼、標頭和回應主體。 - **掛起函數 (Suspend Function)**:Kotlin 中一種特殊的函數,只有在協程或其他掛起函數內才能執行。 - **API 介面 (API Interface)**:定義網路請求的接口,用於指定請求的路徑、方法和參數。 - **`Call` 物件**:Retrofit 中的一個類型,表示一次 HTTP 請求的結果,可以同步或非同步地執行。 - **非同步操作 (Asynchronous Operation)**:不會阻塞主線程的操作,允許在其他線程上執行任務。 - **`GlobalScope.launch`**:Kotlin 中的一個方法,用於啟動一個在 `GlobalScope` 範圍內運行的協程。 - **I/O 密集型操作 (I/O Intensive Operation)**:涉及大量輸入輸出操作的任務,如網路請求和文件讀寫。 - **HTTP 請求 (HTTP Request)**:用戶端向伺服器發送的請求,通常用於獲取資料或執行某些操作。 - **清單 (List)**:Kotlin 和 Java 中的一種資料結構,用於存儲有序的資料集合。 - **`Response.body()`**:用於提取 HTTP 回應的主體資料,一般為 JSON 格式的資料。 - **`isSuccessful`**:HTTP 回應的一個方法,用於檢查請求是否成功(狀態碼 200-299)。 - **API 請求 (API Call)**:應用程式通過 API 向伺服器發送請求,獲取資料或執行操作。 - **異步編程 (Asynchronous Programming)**:在不阻塞主執行緒的情況下執行任務,使多個任務可以同時進行。 - **協程範圍 (Coroutine Scope)**:協程的執行環境,定義了協程的生命週期及其取消行為。 - **`try-catch` 區塊**:用於捕獲和處理程式中可能拋出的例外情況。 - **例外傳播 (Exception Propagation)**:例外從子協程向父協程傳播,最終可能導致應用程式崩潰。 - **`CancellationException`**:協程被取消時拋出的例外,不會導致程式崩潰,但會傳播給相關協程。 - **掛起函數 (Suspend Function)**:Kotlin 中的特殊函數,必須在協程或其他掛起函數內執行。 - **`launch` 函數**:啟動一個新的協程,並返回 `Job` 物件,通常用於不需要返回值的任務。 - **`async` 函數**:啟動一個新的協程,並返回 `Deferred` 物件,用於需要返回值的任務。 - **`Job`**:協程的控制單元,用於管理協程的執行和取消。 - **`Deferred`**:`Job` 的子類,代表一個可延遲的值,可以通過 `await` 來獲取結果。 - **`await` 函數**:用於等待 `Deferred` 的結果並處理異步操作的例外。 - **`LifecycleScope`**:與 Android 活動或片段的生命週期相關聯的協程範圍。 - **協程樹 (Coroutine Tree)**:協程之間的層次結構,例外和取消會沿著這個層次結構傳播。 - **非同步例外處理 (Asynchronous Exception Handling)**:協程中的例外處理機制,與傳統同步程式不同。 - **父協程 (Parent Coroutine)**:包含其他協程的協程,負責管理其子協程的生命週期。 - **子協程 (Child Coroutine)**:由父協程啟動的協程,其生命週期受父協程管理。 - **協程取消 (Coroutine Cancellation)**:當協程範圍或生命週期結束時,終止協程的執行。 - **例外處理模式 (Exception Handling Pattern)**:協程中正確處理例外的最佳實踐和模式。 - **`supervisorScope`**:協程範圍的一種變體,允許子協程的例外不會影響其他協程。