Android 上的 Kotlin Flow(數(shù)據(jù)流), 由淺入深

在協(xié)程中,F(xiàn)low 是一種可以順序發(fā)出多個值的類型,而不是只返回單個值的掛起函數(shù)。例如,你可以使用 Flow 從數(shù)據(jù)庫接收實時更新。

數(shù)據(jù)流建立在協(xié)程之上,可以提供多個值。Flow 在概念上是可以異步計算的數(shù)據(jù)流。發(fā)出的值必須是同一類型。例如, Flow<Int> 是一個發(fā)出整數(shù)值的流。

數(shù)據(jù)流與生成一組序列值的 Iterator 非常相似,但它使用掛起函數(shù)來異步生成和使用值。這意味著,例如,F(xiàn)low 可以安全地發(fā)出網(wǎng)絡請求以生成下一個值,而不會阻塞主線程。

數(shù)據(jù)流涉及三個實體:

  • 提供方會生成添加到數(shù)據(jù)流中的數(shù)據(jù)。得益于協(xié)程,數(shù)據(jù)流還可以異步生成數(shù)據(jù)。
  • (可選)中介可以修改發(fā)送到數(shù)據(jù)流的值,或修正數(shù)據(jù)流本身。
  • 使用方則使用數(shù)據(jù)流中的值。

在 Android 中,代碼庫通常是界面數(shù)據(jù)的提供方,其將界面用作最終顯示數(shù)據(jù)的使用方。 而其他時候,UI 層是用戶輸入事件的生產(chǎn)者,而層次結(jié)構(gòu)的其他層使用它們。 提供方和使用方之間的層通常充當中介,修改數(shù)據(jù)流以使其適應下一層的要求。

創(chuàng)建 Flow

要創(chuàng)建流,請使用flow 構(gòu)建器 API。 流構(gòu)建器函數(shù)創(chuàng)建一個新的 Flow,你可以在其中使用 emit 函數(shù)手動將新值發(fā)送到數(shù)據(jù)流中。

在以下示例中,數(shù)據(jù)源以固定的時間間隔自動獲取最新新聞資訊。 由于掛起函數(shù)不能返回多個連續(xù)值,數(shù)據(jù)源創(chuàng)建并返回一個數(shù)據(jù)流來滿足這個要求。 在這種情況下,數(shù)據(jù)源充當提供方。

class NewsRemoteDataSource(
    private val newsApi: NewsApi,
    private val refreshIntervalMs: Long = 5000
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        while(true) {
            val latestNews = newsApi.fetchLatestNews()
            emit(latestNews) // 將請求的結(jié)果發(fā)送到數(shù)據(jù)流
            delay(refreshIntervalMs) // 暫停協(xié)程一段時間
        }
    }
}

// 提供一種通過掛起功能發(fā)出網(wǎng)絡請求的方法的接口
interface NewsApi {
    suspend fun fetchLatestNews(): List<ArticleHeadline>
}

flow 構(gòu)建器在協(xié)程中執(zhí)行。 因此,它受益于相同的異步 API,但有一些限制:

  • 數(shù)據(jù)流是有順序的。當協(xié)程內(nèi)的提供方調(diào)用掛起函數(shù)時,提供方會掛起,直到掛起函數(shù)返回。 在示例中,提供方會掛起,直到 fetchLatestNews 網(wǎng)絡請求完成為止。只有這樣,請求結(jié)果才會發(fā)送到數(shù)據(jù)流中。
  • 使用 flow 構(gòu)建器,生產(chǎn)者不能從不同的 CoroutineContext 發(fā)出值。 因此,不要通過創(chuàng)建新的協(xié)程或使用 withContext 代碼塊在不同的 CoroutineContext 中調(diào)用 emit。 在這些情況下,您可以使用其他流構(gòu)建器,例如 callbackFlow

修改數(shù)據(jù)流

中介可以使用中間運算符來修改數(shù)據(jù)流,而無需使用這些值。 這些操作符是函數(shù),當應用于數(shù)據(jù)流時,會設置一系列暫不執(zhí)行的鏈式運算,直到將來使用這些值時才會執(zhí)行這些操作。 在 Flow 參考文檔中了解有關(guān)中間運算符的更多信息。

在下面的示例中,存儲庫層使用中間運算符 map 來轉(zhuǎn)換要在 View 上顯示的數(shù)據(jù):

class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource,
    private val userData: UserData
) {
    /**
     * 返回對應流轉(zhuǎn)換的最喜歡的最新新聞資訊。這些操作是惰性的,不會觸發(fā)流程。 
     * 它們只是轉(zhuǎn)換流在該時間點發(fā)出的當前值。
     */
    val favoriteLatestNews: Flow<List<ArticleHeadline>> =
        newsRemoteDataSource.latestNews
            // 過濾收藏主題列表的中間操作
            .map { news -> news.filter { userData.isFavoriteTopic(it) } }
            // 將最新消息保存在緩存中的中間操作
            .onEach { news -> saveInCache(news) }
}

中間運算符可以接連應用,形成鏈式運算,在數(shù)據(jù)項被發(fā)送到數(shù)據(jù)流時延遲執(zhí)行。 請注意,僅將一個中間運算符應用于數(shù)據(jù)流不會啟動數(shù)據(jù)流收集。

從 Flow(數(shù)據(jù)流) 中收集

使用終端運算符可觸發(fā)數(shù)據(jù)流開始監(jiān)聽值。如需獲取數(shù)據(jù)流中的所有發(fā)出值,請使用 collect。 你可以在官方 Flow 文檔中了解更多關(guān)于終端運算符的信息。

因為 collect 是一個掛起函數(shù),所以它需要在協(xié)程中執(zhí)行。 它接受 lambda 作為在每個新值上調(diào)用的參數(shù)。 由于它是一個掛起函數(shù),調(diào)用 collect 的協(xié)程可能會掛起,直到流程關(guān)閉。

繼續(xù)前面的示例,這里是一個 ViewModel 的簡單實現(xiàn),它使用來自存儲庫層的數(shù)據(jù):

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    init {
        viewModelScope.launch {
                // 使用 collect 觸發(fā)流并使用其元素
            newsRepository.favoriteLatestNews.collect { favoriteNews ->
                // 使用最新喜歡的新聞資訊更新視圖
            }
        }
    }
}

收集數(shù)據(jù)流會觸發(fā)提供方刷新最新消息,并以固定時間間隔發(fā)出網(wǎng)絡請求的結(jié)果。 由于提供方在 while(true) 循環(huán)中始終保持活動狀態(tài),因此當 ViewModel 被清除并 viewModelScope 被取消時,數(shù)據(jù)流將被關(guān)閉。

收集數(shù)據(jù)流可能會因以下原因停止:

  • 如上例所示,協(xié)程收集被取消。此操作也會讓底層提供方停止活動。
  • 提供方完成發(fā)出數(shù)據(jù)項。在這種情況下,數(shù)據(jù)流將關(guān)閉,調(diào)用 collect 的協(xié)程則繼續(xù)執(zhí)行。

除非使用其他中間運算符指定流,否則數(shù)據(jù)流始終為冷數(shù)據(jù)并延遲執(zhí)行。 這意味著每次在流上調(diào)用終端操作符時都會執(zhí)行提供方的代碼。 在前面的示例中,擁有多個流收集器會導致數(shù)據(jù)源在不同的固定時間間隔內(nèi)多次獲取最新消息。 要在多個消費者同時收集時優(yōu)化和共享數(shù)據(jù)流,請使用 shareIn 運算符。

捕捉意外的異常

提供方的數(shù)據(jù)實現(xiàn)可以來自第三方庫。 這意味著它可能會拋出意外的異常。 要處理這些異常,請使用 catch 中間運算符。

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // 中介捕獲操作員。 如果拋出異常,
                // 捕獲并更新 UI
                .catch { exception -> notifyError(exception) }
                .collect { favoriteNews ->
                    // 使用最新喜歡的新聞資訊更新視圖
                }
        }
    }
}

在前面的示例中,當發(fā)生異常時,不會調(diào)用 collect lambda,因為尚未收到新數(shù)據(jù)項。

catch 還可執(zhí)行 emit 操作,向數(shù)據(jù)流發(fā)出數(shù)據(jù)項。示例存儲庫層可以改為對緩存值執(zhí)行 emit 操作:

class NewsRepository(...) {
    val favoriteLatestNews: Flow<List<ArticleHeadline>> =
        newsRemoteDataSource.latestNews
            .map { news -> news.filter { userData.isFavoriteTopic(it) } }
            .onEach { news -> saveInCache(news) }
            // 如果發(fā)生錯誤,則發(fā)出最后緩存的值
            .catch { exception -> emit(lastCachedNews()) }
}

在此示例中,當發(fā)生異常時,將調(diào)用 collect lambda,因為由于異常而將新數(shù)據(jù)項發(fā)送到數(shù)據(jù)流中。

在不同的 CoroutineContext 中執(zhí)行

默認情況下,flow 構(gòu)建器的提供方在從它收集的協(xié)程的 CoroutineContext 中執(zhí)行,并且如前所述,它不能從不同的 CoroutineContext 對值執(zhí)行 emit 操作。 在某些情況下,這種行為可能是不可取的。 例如,在本文章中使用的示例中,存儲庫層不應在 viewModelScope 使用的 Dispatchers.Main 上執(zhí)行操作。

要更改流的 CoroutineContext,請使用中間運算符 flowOnflowOn 改變了上游流的 CoroutineContext,這意味提供方和任何在 flowOn 之前(或之上)應用的中間操作符。 下游數(shù)據(jù)流(晚于 flowOn 的中間運算符和使用方)不受影響,并會在 CoroutineContext 上執(zhí)行以從數(shù)據(jù)流執(zhí)行 collect 操作。 如果有多個 flowOn 操作符,每個操作符都會從其當前位置更改上游數(shù)據(jù)流。

class NewsRepository(
    private val newsRemoteDataSource: NewsRemoteDataSource,
    private val userData: UserData,
    private val defaultDispatcher: CoroutineDispatcher
) {
    val favoriteLatestNews: Flow<List<ArticleHeadline>> =
        newsRemoteDataSource.latestNews
            .map { news -> // 在默認調(diào)度程序上執(zhí)行
                news.filter { userData.isFavoriteTopic(it) }
            }
            .onEach { news -> // 在默認調(diào)度程序上執(zhí)行
                saveInCache(news)
            }
            // flowOn 影響上游流 ↑
            .flowOn(defaultDispatcher)
            // 下游流 ↓ 不受影響
            .catch { exception -> // 在消費者的上下文中執(zhí)行
                emit(lastCachedNews())
            }
}

使用此代碼,·
onEachmap 運算符使用 defaultDispatcher,而 catch 運算符和使用者在 viewModelScope 使用的 Dispatchers.Main 上執(zhí)行。

由于數(shù)據(jù)源層正在執(zhí)行 I/O 工作,因此你應該使用針對 I/O 操作進行了優(yōu)化的調(diào)度程序:

class NewsRemoteDataSource(
    ...,
    private val ioDispatcher: CoroutineDispatcher
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        // 在 IO 調(diào)度程序上執(zhí)行
        ...
    }
        .flowOn(ioDispatcher)
}

Jetpack 庫中的數(shù)據(jù)流

Flow 已集成到許多 Jetpack 庫中,并且在 Android 第三方庫中很受歡迎。 Flow 非常適合實時數(shù)據(jù)更新和無限的數(shù)據(jù)流。

你可以將 Flow 與 Room 結(jié)合使用,以便在數(shù)據(jù)庫發(fā)生更改時收到通知。 使用數(shù)據(jù)訪問對象 (DAO) 時,返回 Flow 類型以獲取實時更新。

@Dao
abstract class ExampleDao {
    @Query("SELECT * FROM Example")
    abstract fun getExamples(): Flow<List<Example>>
}

每當 Example 數(shù)據(jù)表發(fā)生更改時,系統(tǒng)都會發(fā)出包含數(shù)據(jù)庫新數(shù)據(jù)項的新列表。

將 callback-based APIs 轉(zhuǎn)換為數(shù)據(jù)流

callbackFlow 是一個數(shù)據(jù)流構(gòu)建器,可讓你將 callback-based APIs轉(zhuǎn)換為 數(shù)據(jù)流。
flow 構(gòu)建器不同,callbackFlow 允許使用 send 函數(shù)從不同的 CoroutineContext 或使用 offer 函數(shù)在協(xié)程外部發(fā)出值。
在協(xié)程內(nèi)部,callbackFlow 使用一個 channel,它在概念上與阻塞隊列非常相似。 通道都有容量配置,限定了可緩沖元素數(shù)的上限。 callbackFlow 中創(chuàng)建的通道的默認容量為 64 個元素。 當你嘗試將新元素添加到完整頻道時,send 會將數(shù)據(jù)提供方掛起,直到有新元素的空間,而 offer 不會將相關(guān)元素添加到通道中,并會立即返回 false。

Kotlin Channel和阻塞隊列很類似,區(qū)別在于Channel用掛起的send操作代替了阻塞的put,用掛起的receive操作代替了阻塞的take。

使用lifecycle-runtime-ktx庫中的launchWhenX方法,對Channel的收集協(xié)程會在組件生命周期 < X時掛起,從而避免異常。也可以使用repeatOnLifecycle(State) 來在UI層收集,當生命周期 < State時,會取消協(xié)程,恢復時再重新啟動協(xié)程。

看起來使用Channel承載事件是個不錯的選擇,并且一般來說事件分發(fā)都是一對一,因此并不需要支持一對多的BroadcastChannel(后者已經(jīng)逐漸被廢棄,被SharedFlow替代)

如何創(chuàng)建Channel?看一下Channel對外暴露可供使用的構(gòu)造方法,考慮傳入合適的參數(shù)。

public fun <E> Channel(

    // 緩沖區(qū)容量,當超出容量時會觸發(fā)onBufferOverflow指定的策略
    capacity: Int = RENDEZVOUS,  

    // 緩沖區(qū)溢出策略,默認為掛起,還有DROP_OLDEST和DROP_LATEST
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,

    // 處理元素未能成功送達處理的情況,如訂閱者被取消或者拋異常
    onUndeliveredElement: ((E) -> Unit)? = null

): Channel<E>

首先Channel是熱的,即任意時刻發(fā)送元素到Channel即使沒有訂閱者也會執(zhí)行。所以考慮到存在訂閱者協(xié)程被取消時發(fā)送事件的情況,即存在Channel處在無訂閱者時的空檔期收到事件情況。例如當Activity使用repeatOnLifecycle方法啟動協(xié)程去消費ViewModel持有的Channel里的事件消息,當前Activity因為處于STOPED狀態(tài)而取消了協(xié)程。

StateFlow(狀態(tài)流) 和 SharedFlow(共享流)

StateFlowSharedFlowFlow API,允許數(shù)據(jù)流以最優(yōu)方式發(fā)出狀態(tài)更新并向多個使用方發(fā)出值。

StateFlow和SharedFlow,兩者擁有Channel的很多特性,可以看作是將Flow推向臺前,將Channel雪藏幕后的一手重要操作。

首先二者都是熱流,并支持在構(gòu)造器外發(fā)射數(shù)據(jù)。簡單看下它們的構(gòu)造方法

public fun <T> MutableSharedFlow(

    // 每個新的訂閱者訂閱時收到的回放的數(shù)目,默認0
    replay: Int = 0,

    // 除了replay數(shù)目之外,緩存的容量,默認0
    extraBufferCapacity: Int = 0,

    // 緩存區(qū)溢出時的策略,默認為掛起。只有當至少有一個訂閱者時,onBufferOverflow才會生效。當無訂閱者時,只有最近replay數(shù)目的值會保存,并且onBufferOverflow無效。 
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
)
//MutableStateFlow等價于使用如下構(gòu)造參數(shù)的SharedFlow

MutableSharedFlow(
    replay = 1,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)

StateFlow

StateFlow 是一個狀態(tài)容器式可觀察數(shù)據(jù)流,可以向其收集器發(fā)出當前狀態(tài)更新和新狀態(tài)更新。還可通過其 value 屬性讀取當前狀態(tài)值。如需更新狀態(tài)并將其發(fā)送到數(shù)據(jù)流,請為 MutableStateFlow 類的 value 屬性分配一個新值。

在 Android 中,StateFlow 非常適合需要讓可變狀態(tài)保持可觀察的類。

按照 Kotlin 數(shù)據(jù)流中的示例,可以從 LatestNewsViewModel 公開 StateFlow,以便 View 能夠監(jiān)聽界面狀態(tài)更新,并自行使屏幕狀態(tài)在配置更改后繼續(xù)有效。

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    // Backing property to avoid state updates from other classes
    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    // The UI collects from this StateFlow to get its state updates
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // Update View with the latest favorite news
                // Writes to the value property of MutableStateFlow,
                // adding a new element to the flow and updating all
                // of its collectors
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
    data class Success(news: List<ArticleHeadline>): LatestNewsUiState()
    data class Error(exception: Throwable): LatestNewsUiState()
}

負責更新 MutableStateFlow 的類是提供方,從 StateFlow 收集的所有類都是使用方。與使用 flow 構(gòu)建器構(gòu)建的冷數(shù)據(jù)流不同,StateFlow 是熱數(shù)據(jù)流:從此類數(shù)據(jù)流收集數(shù)據(jù)不會觸發(fā)任何提供方代碼。StateFlow 始終處于活躍狀態(tài)并存于內(nèi)存中,而且只有在垃圾回收根中未涉及對它的其他引用時,它才符合垃圾回收條件。

當新使用方開始從數(shù)據(jù)流中收集數(shù)據(jù)時,它將接收信息流中的最近一個狀態(tài)及任何后續(xù)狀態(tài)。您可在 LiveData 等其他可觀察類中找到此操作行為。

與處理任何其他數(shù)據(jù)流一樣,View 會監(jiān)聽 StateFlow

class LatestNewsActivity : AppCompatActivity() {
    private val latestNewsViewModel = // getViewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // 在生命周期范圍內(nèi)啟動協(xié)程
        lifecycleScope.launch {
            // 每次 ifecycle 處于 STARTED 狀態(tài)(或更高)時,repeatOnLifecycle 在新的協(xié)程中啟動塊,
            // 并在它停止時取消它。
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // 觸發(fā)流程并開始監(jiān)聽值。
                // 請注意,當生命周期開始時會發(fā)生這種情況,當生命周期停止時會停止收集
                latestNewsViewModel.uiState.collect { uiState ->
                    // 收到的新值
                    when (uiState) {
                        is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
                        is LatestNewsUiState.Error -> showError(uiState.exception)
                    }
                }
            }
        }
    }
}

警告:如果需要更新界面,切勿使用 launchlaunchIn 擴展函數(shù)從界面直接收集數(shù)據(jù)流。即使 View 不可見,這些函數(shù)也會處理事件。此行為可能會導致應用崩潰。 為避免這種情況,請使用 repeatOnLifecycle API(如上所示)。

注意repeatOnLifecycle API 僅在 androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01 庫及更高版本中提供。

如需將任何數(shù)據(jù)流轉(zhuǎn)換為 StateFlow,請使用 stateIn 中間運算符。

StateFlow、Flow 和 LiveData

StateFlowLiveData 具有相似之處。兩者都是可觀察的數(shù)據(jù)容器類,并且在應用架構(gòu)中使用時,兩者都遵循相似模式。

但請注意,StateFlowLiveData 的行為確實有所不同:

  • StateFlow 需要將初始狀態(tài)傳遞給構(gòu)造函數(shù),而 LiveData 不需要。
  • 當 View 進入 STOPPED 狀態(tài)時,LiveData.observe() 會自動取消注冊使用方,而從 StateFlow 或任何其他數(shù)據(jù)流收集數(shù)據(jù)的操作并不會自動停止。如需實現(xiàn)相同的行為,您需要從 Lifecycle.repeatOnLifecycle 塊收集數(shù)據(jù)流。

利用 shareIn 使冷數(shù)據(jù)流變?yōu)闊釘?shù)據(jù)流

StateFlow 是熱數(shù)據(jù)流,只要該數(shù)據(jù)流被收集,或?qū)λ娜魏纹渌迷诶厥崭写嬖冢摂?shù)據(jù)流就會一直存于內(nèi)存中。您可以使用 shareIn 運算符將冷數(shù)據(jù)流變?yōu)闊釘?shù)據(jù)流。

以在 Kotlin 數(shù)據(jù)流中創(chuàng)建的 callbackFlow 為例,您無需為每個收集器都創(chuàng)建一個新數(shù)據(jù)流,而是可以使用 shareIn 在收集器間共享從 Firestore 檢索到的數(shù)據(jù)。您需要傳入以下內(nèi)容:

  • 用于共享數(shù)據(jù)流的 CoroutineScope。此作用域函數(shù)的生命周期應長于任何使用方,以使共享數(shù)據(jù)流在足夠長的時間內(nèi)保持活躍狀態(tài)。
  • 要重放 (replay) 至每個新收集器的數(shù)據(jù)項數(shù)量。
  • “啟動”行為政策。
class NewsRemoteDataSource(...,
    private val externalScope: CoroutineScope,
) {
    val latestNews: Flow<List<ArticleHeadline>> = flow {
        ...
    }.shareIn(
        externalScope,
        replay = 1,
        started = SharingStarted.WhileSubscribed()
    )
}

在此示例中,latestNews 數(shù)據(jù)流將上次發(fā)出的數(shù)據(jù)項重放至新收集器,只要 externalScope 處于活躍狀態(tài)并且存在活躍收集器,它就會一直處于活躍狀態(tài)。當存在活躍訂閱者時,SharingStarted.WhileSubscribed()“啟動”政策將使上游提供方保持活躍狀態(tài)。可使用其他啟動政策,例如使用 SharingStarted.Eagerly 可立即啟動提供方,使用 SharingStarted.Lazily 可在第一個訂閱者出現(xiàn)后開始共享數(shù)據(jù),并使數(shù)據(jù)流永遠保持活躍狀態(tài)。

注意:如需詳細了解 externalScope 的模式,請查看這篇文章

SharedFlow

shareIn 函數(shù)會返回一個熱數(shù)據(jù)流 SharedFlow,此數(shù)據(jù)流會向從其中收集值的所有使用方發(fā)出數(shù)據(jù)。SharedFlowStateFlow 的可配置性極高的泛化數(shù)據(jù)流。

您無需使用 shareIn 即可創(chuàng)建 SharedFlow。例如,您可以使用 SharedFlow 將 tick 信息發(fā)送到應用的其余部分,以便讓所有內(nèi)容定期同時刷新。除了獲取最新資訊之外,您可能還想要使用用戶最喜歡的主題集刷新用戶信息部分。在以下代碼段中,TickHandler 公開了 SharedFlow,以便其他類知道要在何時刷新其內(nèi)容。與 StateFlow 一樣,請在類中使用類型 MutableSharedFlow 的后備屬性將數(shù)據(jù)項發(fā)送給數(shù)據(jù)流:

// 當應用程序的內(nèi)容需要刷新時集中的類
class TickHandler(
    private val externalScope: CoroutineScope,
    private val tickIntervalMs: Long = 5000
) {
    // Backing property to avoid flow emissions from other classes
    private val _tickFlow = MutableSharedFlow<Unit>(replay = 0)
    val tickFlow: SharedFlow<Event<String>> = _tickFlow

    init {
        externalScope.launch {
            while(true) {
                _tickFlow.emit(Unit)
                delay(tickIntervalMs)
            }
        }
    }
}

class NewsRepository(
    ...,
    private val tickHandler: TickHandler,
    private val externalScope: CoroutineScope
) {
    init {
        externalScope.launch {
            // Listen for tick updates
            tickHandler.tickFlow.collect {
                refreshLatestNews()
            }
        }
    }

    suspend fun refreshLatestNews() { ... }
    ...
}

您可通過以下方式自定義 SharedFlow 行為:

通過 replay,您可以針對新訂閱者重新發(fā)送多個之前已發(fā)出的值。
通過 onBufferOverflow,您可以指定相關(guān)政策來處理緩沖區(qū)中已存滿要發(fā)送的數(shù)據(jù)項的情況。默認值為 BufferOverflow.SUSPEND,這會使調(diào)用方掛起。其他選項包括 DROP_LATESTDROP_OLDEST
MutableSharedFlow 還具有 subscriptionCount 屬性,其中包含處于活躍狀態(tài)的收集器的數(shù)量,以便您相應地優(yōu)化業(yè)務邏輯。MutableSharedFlow 還包含一個 resetReplayCache 函數(shù),供您在不想重放已向數(shù)據(jù)流發(fā)送的最新信息的情況下使用。

SharedFlow 在無訂閱者時會丟棄數(shù)據(jù)。SharedFlow 類似BroadcastChannel, 支持被多個訂閱者訂閱,可以使同一個事件會被多次消費。

相關(guān)官方文檔:
https://developer.android.com/kotlin/flow
https://kotlinlang.org/docs/flow.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內(nèi)容