Android 性能優(yōu)化實戰(zhàn) - 直播間場景 「涉及到 Kotlin Coroutine, Websocket , SharedFlow, StateFlow 」

說一下場景

線上總有反饋說從直播間掉線,然后測試開始壓測,發(fā)現(xiàn)對于低端設備在我們業(yè)務中推流場景下只能到60听怕,即會發(fā)生異常;

先說一下優(yōu)化前的問題

優(yōu)化前業(yè)務流程圖.png
  • 業(yè)務邏輯層捧挺,在一個協(xié)程里順序處理每一個 ws 接收者,eg:IM,Inner Notification,直播間..., 處理完后尿瞭,才會開始開始下一條 ws 消息的處理;
  • 對于 IM 的ws 消息闽烙,也會走進 直播間 ws 消息的處理,只是直播間 ws 解析 command 后声搁,發(fā)現(xiàn)自己無需處理黑竞,才會return;
  • 直播間有個特殊命令「簡稱 special cmd」確保用戶始終和直播間保持鏈接,若 n 秒內未接收到 special cmd疏旨,則會執(zhí)行退房處理. 問題是在第1條的描述中很魂,ws 會有堆積,若之前的消息處理邏輯過多檐涝,則會導致這個 special cmd 始終無法處理;
  • 在直播間中遏匆,用戶會刷評論/送禮等 level 較低的消息,若設備低端谁榜,無法處理大量低 level 消息時幅聘,可做丟棄或用其他 coroutine 處理,不要影響直播間中處理主流程 ws 的coroutine;
  • ws 中 jsonstring 重復解析問題窃植,Gson 解析耗時問題眾所周知帝蒿,重復解析且解析方式問題,導致 CPU 占用增大 and 發(fā)熱 and 耗時增多巷怜,進而降低設備 CPU 處理能力葛超,惡性循環(huán);

優(yōu)化思路

  • 不同場景只處理屬于自己的 ws;
  • 解決 ws 積壓導致后面消息無法及時處理的問題;
  • 對于直播間等重 ws 的業(yè)務場景,low_level 且對 UI 線程影響較大的 msg 單獨 coroutine 處理丛版,eg:評論,送禮「有動畫」;
  • Gson 解析中高頻 key 解析優(yōu)化 and 優(yōu)化 Gson 解析使用方式;

優(yōu)化實戰(zhàn)

優(yōu)化后業(yè)務流程圖.png

二次分發(fā)

  • 目前所有的 ws 都在 totalFlow 中巩掺,需要做二次分發(fā). 根據(jù)目前公司提供的后端數(shù)據(jù)偏序,只能根據(jù) ws 中攜帶的 command 做 filter 過濾页畦;
  • 對于少量且低頻的 msg,eg:IM, InnerNotification 等封裝為一個 otherFlow;
  • 對于大量且重要的 msg,eg:直播間流程中的各 msg 封裝為一個 liveFlow;
  • 對于大量且可根據(jù)設備處理能力丟棄 or 延遲處理的msg,eg:評論,送禮 等封裝為一個 highFreqFlow;

otherFlow, liveFlow, highFreqFlow 均為 MutableSharedFlow;
其中 extraBufferCapacity 分別為 5, 800, 300「5:基本不會有積壓; 800:重要流程消息,為防止被丟棄設為 較高閥值研儒,服務端目前最高并發(fā)貌似也不會超過 800; 300:評論,送禮都是可丟棄的豫缨,且評論區(qū)最高存儲消息 count 為 150」;
onBufferOverflow 均為 BufferOverflow.DROP_OLDEST;
處理之后也解決了 special cmd 無法及時處理,導致退房的bug.

Gson 解析

對于剛需字段 command端朵,初次解析后好芭,在 ws 的 data 里增加一個 command 即可, so easy.

data class Broadcast(val message: JSONObject, val command: Int)

對于 Gson 解析,改之前寫法如下

val a = jsonString.optJSONObject("A")?.toString() ?: ""
val b = jsonString.optJSONObject("B")?.toString() ?: ""
val aa = try {
    GsonManager.gson().fromJson<User>(a, object : TypeToken<User>() {}.type)
} catch (e: Exception) {
    User()
}
val bb = GsonManager.gson().fromJson<User>(b, object : TypeToken<User>() {}.type)
val c = jsonString.optLong("c")
val d = jsonString.optInt("d")
var e = jsonString.optString("e", "")
val f = jsonString.optBoolean("f", false)
val g = jsonString.optJSONObject("g")?.toString() ?: ""
val extra = try {
    GsonManager.gson().fromJson<CCC>(g, object : TypeToken<CCC>() {}.type)
} catch (e: Throwable) {
    null
}

改完后

val data = try {
    GsonManager.gson().fromJson<AB>(jsonString, object : TypeToken<AB>() {}.type)
} catch (e: Throwable) {
    return
}

data class AB(
    @SerializedName("a")
    var a: User,
    @SerializedName("b")
    var b: User,
    @SerializedName("c")
    var c: Long,
    @SerializedName("d")
    var d: Int,
    @SerializedName("e")
    var e: String,
    @SerializedName("f")
    var f: Boolean? = null,
    @SerializedName("g")
    var g: CCC? = null,
)

至于 Gson 解析耗時原理的話冲呢,自行查閱吧舍败,我也不太熟...

其他優(yōu)化

在對上面的 totalFlow 做初步解析的時候,用到了很多 filter 方法,每一次 filter 都是新建的一個 flow邻薯,盡量一次 filter 完成功能裙戏,如下

public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
    if (predicate(value)) return@transform emit(value)
}

//優(yōu)化前如下:
flow.filter {
//**
}.map {
//***
}.filter {
//**
}.catch {
//**
}

//優(yōu)化后如下:
flow.mapNotNull{
//**
}.catch {
//**
}

另外對于瘋狂刷評論等操作,肯定會導致 評論區(qū)的UI 瘋狂刷新厕诡,可以新建一個隊列緩存 comment msg累榜,每秒取 3-4 次,每次取的 msg count 根據(jù)設備 level 來定灵嫌,減少 UI 繪制壓力壹罚;

成果

單叢直播壓測的角度來講,對于低端設備寿羞,結論如下:


成果.png

優(yōu)化前 推流-評論-60 掉線;
注:以上數(shù)字為每秒發(fā)送聊天消息*條猖凛;

卡頓檢測工具

說一下發(fā)現(xiàn) gson 耗時的檢測工具:
這版優(yōu)化前,還有一版優(yōu)化绪穆,當時發(fā)現(xiàn)的問題是大量 gson 解析發(fā)生在 UI thread, 看下圖:


cpu profiler.png

原因是: ws 接收的 coroutine dispatcher 是 Main.
不過也能發(fā)現(xiàn) gson 耗時問題.

拓展
目前進一步的優(yōu)化所用工具為 tencent matrix.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末形病,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霞幅,更是在濱河造成了極大的恐慌漠吻,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件司恳,死亡現(xiàn)場離奇詭異途乃,居然都是意外死亡,警方通過查閱死者的電腦和手機扔傅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門耍共,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猎塞,你說我怎么就攤上這事试读。” “怎么了荠耽?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵钩骇,是天一觀的道長。 經(jīng)常有香客問我铝量,道長倘屹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任慢叨,我火速辦了婚禮纽匙,結果婚禮上,老公的妹妹穿的比我還像新娘拍谐。我一直安慰自己烛缔,他們只是感情好馏段,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著践瓷,像睡著了一般毅弧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上当窗,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天够坐,我揣著相機與錄音,去河邊找鬼崖面。 笑死元咙,一個胖子當著我的面吹牛,可吹牛的內容都是我干的巫员。 我是一名探鬼主播庶香,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼简识!你這毒婦竟也來了赶掖?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤七扰,失蹤者是張志新(化名)和其女友劉穎奢赂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颈走,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡膳灶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了立由。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轧钓。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锐膜,靈堂內的尸體忽然破棺而出毕箍,到底是詐尸還是另有隱情,我是刑警寧澤道盏,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布而柑,位于F島的核電站,受9級特大地震影響捞奕,放射性物質發(fā)生泄漏牺堰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一颅围、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恨搓,春花似錦院促、人聲如沸筏养。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渐溶。三九已至,卻和暖如春弄抬,著一層夾襖步出監(jiān)牢的瞬間茎辐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工掂恕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拖陆,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓懊亡,卻偏偏與公主長得像依啰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子店枣,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容