kotlin 回調(diào)轉(zhuǎn)協(xié)程掛起函數(shù)

github博客

λ:

今天起 android demo 項(xiàng)目新加個(gè)sdk:騰訊云IM弃榨,最近正在用嘱吗,而且接口多,涉及到的需求也挺全擂错。正好練手。同時(shí)也有flutter的sdk掷漱。順路把flutter也寫了粘室。

大多數(shù)sdk或者庫在提供api時(shí),對(duì)于異步處理一般都是提供回調(diào)卜范。好處是通用,兼容鹿榜,不管java, kotlin海雪,不用管其他依賴庫。 壞處就不用再提了舱殿。

IM也不例外是一堆回調(diào)奥裸,MVVM模式下,一層層傳回調(diào)上去就很low沪袭,所以把IM用到的接口整理成Service湾宙,在里邊把回調(diào)包成kotlin 協(xié)程掛起函數(shù)。

suspendCancellableCoroutine

public suspend inline fun <T> suspendCancellableCoroutine(crossinline block: (CancellableContinuation<T>) -> Unit): T 

public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T

協(xié)程庫提供的兩個(gè)內(nèi)聯(lián)函數(shù)冈绊。通過操作其中的CancellableContinuation提交結(jié)果侠鳄。點(diǎn)進(jìn)去看源碼,查看支持的操作死宣。

public interface Continuation<in T> {
    public val context: CoroutineContext

    public fun resumeWith(result: Result<T>)
}

public interface CancellableContinuation<in T> : Continuation<T> {
    public val isActive: Boolean

    public val isCompleted: Boolean

    public val isCancelled: Boolean

    public fun cancel(cause: Throwable? = null): Boolean

    public fun invokeOnCancellation(handler: CompletionHandler)

    ... 試驗(yàn)性接口
}

public inline fun <T> Continuation<T>.resume(value: T): Unit = resumeWith(Result.success(value))

public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit = resumeWith(Result.failure(exception))

public inline fun <T> Continuation(context: CoroutineContext, crossinline resumeWith: (Result<T>) -> Unit): Continuation<T>

忽略掉被打了標(biāo)簽的接口(不確定伟恶,試驗(yàn)性,即將廢棄毅该,等)博秫,看函數(shù)名基本就知道干嘛用,還剩這么點(diǎn)眶掌。 同時(shí)提供一堆拓展函數(shù)挡育。

所以可以通過 resumeresumeWithException 提交回調(diào)返回的結(jié)果。

通過invokeOnCancellation注冊(cè)取消時(shí)要執(zhí)行的任務(wù)朴爬,比如關(guān)閉流之類的即寒。

如IM中獲取所有已加入群,當(dāng)回調(diào)返回為失敗時(shí)寝殴,直接提交一個(gè)空列表蒿叠。或者提交個(gè)Throwable蚣常。

suspend fun getJoinedGroupList(): List<V2TIMGroupInfo> =
        suspendCancellableCoroutine { continuation ->
            V2TIMManager.getGroupManager().getJoinedGroupList(object : V2TIMValueCallback<List<V2TIMGroupInfo>> {
                    override fun onSuccess(t: List<V2TIMGroupInfo>) {
                        continuation.resume(t)
                    }

                    override fun onError(code: Int, desc: String?) {
                        continuation.resume(emptyList())
//                     continuation.resumeWithException(Exception("code: $code, desc: $desc"))
                    }
                })
        }

callbackFlow, SharedFlow, StateFlow

有些回調(diào)是在實(shí)時(shí)監(jiān)聽數(shù)據(jù)市咽。比如位置信息,音量變化抵蚊,IM中數(shù)據(jù)變化施绎,新消息送達(dá)等等溯革。所以這種回調(diào)用 kotlin Flow 和 Channel 處理。

callbackFlow

現(xiàn)在callbackFlow仍標(biāo)記為@ExperimentalCoroutinesApi谷醉。所以等 雞啄完了米致稀,狗舔完了面,火燒斷了鎖 俱尼。我再用抖单。

允許在不同的CoroutineContext中提交數(shù)據(jù)。刨一下源碼:

public fun <T> callbackFlow(@BuilderInference block: suspend ProducerScope<T>.() -> Unit): Flow<T> = CallbackFlowBuilder(block)

private class CallbackFlowBuilder<T>(...) : ChannelFlowBuilder<T>(...)

private open class ChannelFlowBuilder<T>(...) : ChannelFlow<T>(...)

最底層就是個(gè)ChannelFlow遇八,也就是開個(gè)帶緩沖區(qū)Channel來收集數(shù)據(jù)矛绘,在Flow里接收數(shù)據(jù)。

CallbackFlowBuilder在此基礎(chǔ)上加了awaitClose: 當(dāng)流要關(guān)閉時(shí)要執(zhí)行的操作刃永,常見的是注銷掉回調(diào)函數(shù)货矮。如果沒有awaitClose,將會(huì)拋出IllegalStateException異常斯够。

所以提交數(shù)據(jù)的方式和SendChannel一樣囚玫。

callbackFlow<T> {
    send(T) // 發(fā)送數(shù)據(jù)
    offer(T) // 允許在協(xié)程外提交
    sendBlocking(T) //嘗試用offer提交,如果失敗則runBlocking{ send(T) }读规,阻塞式提交
    awaitClose(block: () -> Unit = {}) // 關(guān)閉時(shí)執(zhí)行的操作
}
// demo
fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow {
    val callback = object : Callback {
        override fun onNextValue(value: T) {
            try {
                sendBlocking(value)
            } catch (e: Exception) {

            }
        }
        override fun onApiError(cause: Throwable) {
            cancel(CancellationException("API Error", cause))
        }
        override fun onCompleted() = channel.close()
    }
    api.register(callback)
    awaitClose { api.unregister(callback) }
}

SharedFlow

取代BroadcastChannel抓督。

SharedFlow, MutableSharedFlow 都是 interface。 同時(shí)提供了fun MutableSharedFlow用于快速構(gòu)造掖桦。

public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T> 

replay:重播n個(gè)之前收到的數(shù)據(jù)給新訂閱者本昏。 >= 0

extraBufferCapacity:除了重播之外緩沖區(qū)大小。當(dāng)緩沖區(qū)不滿時(shí)枪汪,提交數(shù)據(jù)不會(huì)掛起

onBufferOverflow:緩沖區(qū)溢出時(shí)的策略(replay != 0 || extraBufferCapacity != 0 才有效)涌穆。默認(rèn)SUSPEND: 暫停發(fā)送,DROP_OLDEST:刪掉最舊數(shù)據(jù)雀久,DROP_LATEST:刪掉最新數(shù)據(jù)宿稀。

通過emit(T)在協(xié)程中提交數(shù)據(jù)。

tryEmit(T): Boolean 嘗試在不掛起的情況下提交數(shù)據(jù)赖捌,成功則返回true祝沸。 如果onBufferOverflow = BufferOverflow.SUSPEND ,在緩沖區(qū)滿時(shí)越庇,tryEmit會(huì)返回false罩锐,直到有新空間。而如果是DROP_OLDESTDROP_LATEST卤唉,不會(huì)阻塞涩惑,tryEmit永為true

所以我用MutableSharedFlow替代callbackFlow的提交過程桑驱。

// demo
val resFlow = MutableSharedFlow<Res<T>>(
    extraBufferCapacity = 1,
    onBufferOverflow: BufferOverflow = BufferOverflow.DROP_OLDEST
)

fun registerCallback() {
    val callback = object : Callback {
        override fun onNextValue(value: T) {
            resFlow.tryEmit(Res.Success(value))
        }
        override fun onApiError(cause: Throwable) {
            resFlow.tryEmit(Res.Failed(cause))
        }
        override fun onCompleted() {
            resFlow.tryEmit(Res.Finish)
        }
    }
    api.register(callback)
}

StateFlow

繼承自SharedFlow竭恬,同樣也提供了快速構(gòu)造的函數(shù)跛蛋。函數(shù)必須提交一個(gè)初始的value。底層相當(dāng)于開了一個(gè) MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST).

public interface StateFlow<out T> : SharedFlow<T> {
    public val value: T
}

public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
    public override var value: T
    public fun compareAndSet(expect: T, update: T): Boolean
}

@Suppress("FunctionName")
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)

compareAndSet: 如果當(dāng)前值為expect, 則更新為update痊硕。如果更新則返回true(包括current == expect && current == update 的情況)赊级。

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

    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews.collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

sealed class LatestNewsUiState {
    data class Success(news: List<ArticleHeadline>): LatestNewsUiState()
    data class Error(exception: Throwable): LatestNewsUiState()
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市岔绸,隨后出現(xiàn)的幾起案子理逊,更是在濱河造成了極大的恐慌,老刑警劉巖亭螟,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挡鞍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡预烙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門道媚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扁掸,“玉大人,你說我怎么就攤上這事最域∏捶郑” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵镀脂,是天一觀的道長(zhǎng)牺蹄。 經(jīng)常有香客問我,道長(zhǎng)薄翅,這世上最難降的妖魔是什么沙兰? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮翘魄,結(jié)果婚禮上鼎天,老公的妹妹穿的比我還像新娘。我一直安慰自己暑竟,他們只是感情好斋射,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著但荤,像睡著了一般罗岖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上腹躁,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天桑包,我揣著相機(jī)與錄音,去河邊找鬼潜慎。 笑死捡多,一個(gè)胖子當(dāng)著我的面吹牛蓖康,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播垒手,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蒜焊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了科贬?” 一聲冷哼從身側(cè)響起泳梆,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榜掌,沒想到半個(gè)月后优妙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡憎账,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年套硼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胞皱。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邪意,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出反砌,到底是詐尸還是另有隱情雾鬼,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布宴树,位于F島的核電站策菜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏酒贬。R本人自食惡果不足惜又憨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望同衣。 院中可真熱鬧竟块,春花似錦、人聲如沸耐齐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埠况。三九已至耸携,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辕翰,已是汗流浹背夺衍。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喜命,地道東北人沟沙。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓河劝,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親矛紫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赎瞎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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