iT邦幫忙

2021 iThome 鐵人賽

DAY 15
0

Keyword:Coroutine Leak,Structured Concurrency


Memory Leak

在剛入行工作的時候,有經驗的前輩常常提醒要注意Memory Leak,如果有發生Memory Leak的問題,就可能逐漸讓App卡頓,嚴重時還會閃退.導致客戶解除安裝,降低留存率.

雖然這麼說,但是Memory Leak很不容易在開發時發現,由於對功能沒有顯著的影響,有的時候連測試環節也沒能抓到問題,最後偷渡到架上版.這個問題只能靠著Code Review進行預防,但是難免有漏網之魚.

Memory Leak的原因

最常看見的說法就是由於有匿名物件在內,這個匿名物件會有外部的物件隱性引用,導致外部的物件無法回收.

但是我們常常使用匿名物件,例如點擊事件的監聽,甚至DataBinding,ViewBinding等等的功能也常常用到匿名物件,這些物件為什麼不會導致Memory Leak? 同樣是匿名物件啊.

原來,導致Leak的不是匿名,而是使用到了Thread,仍然活著的Thread在JVM中是屬於Garbage Collector Root,而Garbage Collector Root 引用到的所有物件在GC的時候都不會被回收,最後就發生了Memory Leak.

這點在App上發生的更為頻繁,因為App發生事件的時機點是不可控的,也許使用者剛按下"讀取歷史資料"的功能,這功能是耗時工作,因此App生成了一個新Thread來執行.然而使用者這時突然按下了返回離開了頁面,如果沒有做好預防,這邊的剛建立的Thread就會發生Memory Leak.

尋求解決之道

資深的工程師,可以透過經驗在開發時避免這些問題,最常見的做法是在物件關閉的發生時,將所有使用到的Thread一並關閉.

但是當團隊有新成員加入,或是是剛入行的菜鳥時,就非常有可能忘記這件事,因為關閉Thread這件事是超出使用的物件範圍,而是由外部控制.

而由於Kotllin的Coroutine目前也是藉由JVM的Thread來實作,並不是真正意義上的Coroutine環境,所以這些Thread發生的問題,如果使用不當,仍然存在.

官方考慮到了這點,所以限制了Coroutine在使用時,必須要在一個CoroutineScope中,在CoroutineScope結束的時候,其中所有正在執行的Coroutine也會一並被停止,因此便不會發生Coroutine Memory Leak的問題.

回來看看我們前幾天寫的Android Coroutine內容.viewModelScope限制了這個Coroutine只能在這個ViewModel的生命週期中執行,當ViewModel的生命週期結束後,這個Scope的內容也會停止

fun fetchCafeData(city: String = "") {
        viewModelScope.launch() {
            val result = async { dataRepository.fetchCafesFromNetwork(city) }
            cafeList.value = result.await()
        }
    }

和一般的Thread寫法比較一下

...
fun fetchCafeData(city: String = "") {
   thread{
				val result = dataRepository.fetchCafesFromNetwork(city) 
		}
}
...
override fun onCleared() {
        super.onCleared()
				stopAllThread()
    }

可以發現Coroutine執行的生命週期範圍,就綁定在使用的區塊上面一行,並且不提供生命週期範圍即不給使用,這大大降低了忘記填寫釋放時機的可能性.

結構化

這樣的設計方式,Kotlin官方稱之為Structured Concurrency,結構化並行

(以下三張圖出自)

GitHub - Zewo/Venice: Coroutines, structured concurrency and CSP for Swift on macOS and Linux.

在沒有結構化並行的環境,只要一個不小心,任何一個Function內部的Thread都有可能,並且有能力可以超脫整個Function的,影響到整個環境中.這件事非常詭異,在一個Function內建立的物件可以Leak到整個Global環境中,造成管理上的麻煩.

https://github.com/officeyuli/itHome2021/raw/main/day15/687474703a2f2f6c696264696c6c2e6f72672f696e646578312e6a706567.jpeg

但有了結構化並行,就能夠在使用時限制Thread的作用域,進而降低Leak的風險

https://github.com/officeyuli/itHome2021/raw/main/day15/687474703a2f2f6c696264696c6c2e6f72672f696e646578322e6a706567.jpeg

Structured Concurrency還能在多個子代Thread適用同樣的規則,一並管理這些Thread,同樣的,當父輩的Thread結束時,所有子代的Thread也會一並結束.最後會形成一個美麗的Thread樹.

https://github.com/officeyuli/itHome2021/raw/main/day15/687474703a2f2f6c696264696c6c2e6f72672f696e646578332e6a706567.jpeg

在Android之中,有官方提供的viewModelScope,lifecycleScope等等來協助管理,但在KMM的iOS專案之中,就沒這麼容易了,我們明天來額外做些小手腳.


上一篇
Day 14:Coroutine,那是什麼?好吃嘛?
下一篇
Day 16:自己動手,豐衣足食.IOS的Coroutine管理
系列文
挑戰 Kotlin Multiplatform Mobile 跨平台開發,透過共同的Kotlin模組同時打造iOS與Android應用!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言