在 Jetpack Compose 中安全地使用數據流

語言: CN / TW / HK

作者 / Manuel Vivo, Android DevRel, Google

我們 推薦 以生命週期感知方式在 Android 上收集數據流。如果您正在用 Jetpack Compose 構建 Android 應用,請使用 collectAsStateWithLifecycle API 以生命週期感知方式從用户界面收集數據流。

  • 使用界面狀態

    http://developer.android.google.cn/topic/architecture/ui-layer#consume-ui-state

藉助 collectAsStateWithLifecycle ,您可以在不需要應用資源時釋放它們,例如當應用處於後台時。此類資源可能包括 Firebase 查詢、位置或網絡更新及數據庫連接等,在不需要它們的情況下讓其處於活躍狀態會影響用户設備的運行健康狀況。

請繼續閲讀本文,以詳細瞭解此 API、以生命週期感知方式收集數據流的理由,以及此 API 與 collectAsState API 的差異。

collectAsStateWithLifecycle

collectAsStateWithLifecycle 是一個可組合函數,可從數據流中收集值,並以生命週期感知方式將最新值表示為 Compose State 。每當數據流發出新值時,此 State 對象的值都會更新,從而讓組合 (Composition) 中每個使用 State.value 的對象進行重新組合。

  • State

    http://developer.android.google.cn/reference/kotlin/androidx/compose/runtime/State

默認情況下, collectAsStateWithLifecycle 使用 Lifecycle.State.STARTED 從數據流中開始和結束收集值。這些動作會在生命週期 (Lifecycle) 移入和移出目標狀態時發生。您可以通過 minActiveState 參數配置此生命週期狀態。

△ 默認情況下,當應用處於後台時 collectAsStateWithLifecycle 會取消收集數據流

  • Lifecycle.State.STARTED

    http://developer.android.google.cn/reference/android/arch/lifecycle/Lifecycle.State#started

以下代碼片段展示瞭如何使用 collectAsStateWithLifecycle 來收集可組合函數中的 ViewModel 所公開的 StateFlow 的 uiState 字段: 

/* Copyright 2022 Google LLC.  
SPDX-License-Identifier: Apache-2.0 */


@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
fun AuthorRoute(
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
viewModel: AuthorViewModel = hiltViewModel()
) {
val uiState: AuthorScreenUiState by viewModel.uiState.collectAsStateWithLifecycle()


AuthorScreen(
authorState = uiState.authorState,
newsState = uiState.newsState,
modifier = modifier,
onBackClick = onBackClick,
onFollowClick = viewModel::followAuthorToggle,
)
}

每當 AuthorViewModel uiState 發出新的 AuthorScreenUiState 值時,都會重新組合 AuthorRoute 。有關 collectAsStateWithLifecycle 的更多用法,請參考 " Now in Android " 應用及相關 遷移 PR

  • AuthorViewModel

    http://github.com/android/nowinandroid/blob/main/feature-author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt
  • AuthorRoute

    http://github.com/android/nowinandroid/blob/main/feature-author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorScreen.kt
  • Now in Android

    http://github.com/android/nowinandroid/search?q=collectAsStateWithLifecycle
  • 遷移 PR

    http://github.com/android/nowinandroid/pull/166

如果您要在項目中使用 collectAsStateWithLifecycle API,請將 androidx.lifecycle.lifecycle-runtime-compose 工件添加到項目中。

/* Copyright 2022 Google LLC.  
SPDX-License-Identifier: Apache-2.0 */


// app/build.gradle file
dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.0-alpha01"
}

注意: 這是一個 尚處於 Alpha 版 的全新 API,且該 API 還要求您使用  ExperimentalLifecycleComposeApi 註釋。

  • 版本 2.6.0-alpha01

    http://developer.android.google.cn/jetpack/androidx/releases/lifecycle#version_26_2
  • ExperimentalLifecycleComposeApi

    http://developer.android.google.cn/reference/kotlin/androidx/lifecycle/compose/ExperimentalLifecycleComposeApi

工作原理

collectAsStateWithLifecycle 在 實現機制 上使用了 repeatOnLifecycle API,我們也推薦大家在 Android 視圖 (View) 系統中收集數據流的 API。

  • collectAsStateWithLifecycle 的實現機制

    http://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/FlowExt.kt;l=168
  • repeatOnLifecycle

    http://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#(androidx.lifecycle.Lifecycle).repeatOnLifecycle(androidx.lifecycle.Lifecycle.State,kotlin.coroutines.SuspendFunction1)

藉助 collectAsStateWithLifecycle ,您無需輸入下方的樣板代碼,這些代碼同樣以生命週期感知的方式從可組合函數收集數據流: 

/* Copyright 2022 Google LLC.        
SPDX-License-Identifier: Apache-2.0 */
@Composable
fun AuthorRoute(...) {
val lifecycle = LocalLifecycleOwner.current.lifecycle
val uiState by produceState<AuthorScreenUiState>(
initialValue = viewModel.uiState.value
key1 = lifecycle
key2 = viewModel
) {
lifecycle.repeatOnLifecycle(state = STARTED) {
viewModel.uiState.collect { value = it }
}
}


AuthorScreen(...)
}

在架構中收集數據流

應用架構中的類型不應該知道其他類型的實現細節。界面不應該知道 ViewModel 如何產生界面狀態。如果界面在屏幕上不可見,則應停止收集數據流,以釋放應用資源 (如果可行的話)。

界面可以通過使用 collectAsStateWithLifecycle 收集界面狀態來幫助釋放資源。ViewModel 可以通過以收集器感知的方式生成界面狀態來完成相同的操作。如果沒有收集器,例如當界面在屏幕上不可見時,則停止收集來自數據層的上游數據流。您可以在生成界面狀態時使用 .stateIn(WhileSubscribed) 數據流 API 來執行此操作。如需瞭解更多信息,請觀看 " Kotlin Flows 實戰 " 講座的 這一部分 。如要測試以這種方法生成界面狀態的 ViewModel,請查看 測試指南

△ 在界面層中,使用 collectAsStateWithLifecycle 收集界面狀態,並在數據層公開響應式數據流時使用 .stateIn(WhileSubscribed) 生成界面狀態。這樣一來應用的其餘部分便能在不需要的時候釋放資源

  • .stateIn(WhileSubscribed)

    http://github.com/android/nowinandroid/blob/main/feature-author/src/main/java/com/google/samples/apps/nowinandroid/feature/author/AuthorViewModel.kt#L104
  • Kotlin Flows 實戰

    http://www.youtube.com/watch?v=fSB6_KE95bU&t=1009s
  • 測試 StateFlow

    http://developer.android.google.cn/kotlin/flow/test#statein

數據流的使用者和生產者不需要知道彼此的實現方式。在具備多個環境、變體、代碼庫和功能的大型應用中找出實現細節是非常耗時的。更糟糕的是,依賴於實現細節的代碼維護起來非常困難。

讓資源在後台保持活躍狀態

Android 應用可以在海量 Android 設備上運行。但遺憾的是,所有設備和用户擁有的資源都是有限的,因此應用通常在受限環境中運行。運行 Android 應用時,有一些重要因素會影響用户體驗和設備系統健康:

  • CPU 使用 : 在所有設備組件中,CPU 的耗電量最高。而電池續航時間一直是用户關注的重點,因此如果發生 CPU 濫用的情況,用户可能會卸載您的應用;

  • 流量消耗 : 在未連接 Wi-Fi 時減少應用的網絡流量,可以幫助用户節省流量費用;

  • 內存用量 : 應用對內存的使用方式也會對設備的整體穩定性和性能產生非常大的影響。

如果 Android 開發者想滿足用户的需求、確保設備系統健康,或 " 為數十億用户打造產品 ",則應該根據其目標市場、設備或國家/地區的實際情況來優化上述這些因素。根據設備類型和設備上 Android 版本的不同,讓不必要的資源保持活躍可能會產生負面影響。在界面層中使用 collectAsStateWithLifecycle 可以讓層次結構的其餘部分得以釋放資源。

  • 為數十億用户打造產品

    http://developer.android.google.cn/docs/quality-guidelines/build-for-billions

與 collectAsState 的差異

開發者們經常會問道: 如果 collectAsStateWithLifecycle 是從 Android 可組合函數中收集數據流最安全的方法,那現在為什麼還需要 collectAsState API?為什麼不將生命週期感知功能添加到 collectAsState 中,而是創建新的 API?

可組合函數的生命週期與 Compose 運行的平台無關。正如 " 可組合項的生命週期 " 頁面中所述,可組合函數的實例進入組合,執行 0 次或多次重組,然後離開組合:

http://developer.android.google.cn/jetpack/compose/lifecycle

△ 組合中可組合函數實例的生命週期

collectAsState API 遵循組合的生命週期。此 API 在可組合項進入組合時開始收集數據流,並在可組合項離開組合時停止收集。 collectAsState 是用於收集數據流且與平台無關的 API。

但是,在 Android 應用中使用 Compose 時,Android 生命週期也會對資源的管理方式產生非常大的影響。即使 Compose 在 Android 應用處於後台時停止重組, collectAsState 也會繼續收集數據流。這使得層次結構的其餘部分無法釋放資源。

collectAsState collectAsStateWithLifecycle 在 Compose 中各有用途。後者用於開發 Android 應用,前者用於在其他平台進行開發。

collectAsState 遷移到 collectAsStateWithLifecycle 非常容易: 

/* Copyright 2022 Google LLC.  
SPDX-License-Identifier: Apache-2.0 */


@Composable
fun AuthorRoute(...) {
val lifecycle = LocalLifecycleOwner.current.lifecycle
val uiState by produceState<AuthorScreenUiState>(
initialValue = viewModel.uiState.value
key1 = lifecycle
key2 = viewModel
) {
lifecycle.repeatOnLifecycle(state = STARTED) {
viewModel.uiState.collect { value = it }
}
}


AuthorScreen(...)
}

推薦大家以生命週期感知方式在 Android 上收集數據流,這樣做可以使應用的其他部分在需要時釋放資源。

如果您正在使用 Jetpack Compose 構建 Android 應用,請使用 collectAsStateWithLifecycle 可組合函數來執行此操作。

另外: 感謝 Jose Alcérreca Marton Braun Alejandra Stamato 和 Jake Roseman 對文章內容進行審核。

  • Jose Alcérreca

    http://medium.com/u/e0a4c9469bb5
  • Marton Braun

    http://medium.com/u/ec2087b3c81f
  • Alejandra Stamato

    http://medium.com/u/92c44d274e60

您可以通過下方二維碼或在文章底部私信,向我們提交反饋,分享您喜歡的內容、發現的問題。您的反饋對我們非常重要,感謝您的支持!

推薦閲讀

如頁面未加載,請刷新重試

點擊屏末   閲讀原文  |   進一步瞭解 Android 開發信息