iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 19
0
Mobile Development

Android Kotlin開發 -小嫩雞的30篇精選筆記系列 第 19

Android : 淺淺淺談記憶體洩漏Memory leak

  • 分享至 

  • xImage
  •  

簡介

什麼是記憶體洩漏?
一個已經廢棄的物件因為一些原因無法被回收,導致它一直占用記憶體造成浪費,即稱為記憶體洩漏。當一個物件持有一個生命週期比它短的物件的引用,就有可能引起記憶體洩漏。例如單例模式引起的記憶體洩漏。因為單例擁有靜態特性,其生命週期跟整個app一樣長,在實例化一個需要context的單例對象時,若傳入activity的context,會使該單例一直持有該activity的引用,導致activity即便退出了也無法被回收,產生記憶體洩漏。

GC root 垃圾回收機制

  • 是一種在jvm虛擬機上運行的垃圾回收機制。
  • 系統會定期選擇適當的時機,凍結應用程式一切動作,掃描並回收掉廢棄物件。這就是為什麼當記憶體快要爆掉時,畫面會一直leg,因為系統一直暫停所有運作去掃描有什麼用不到的物件可以清掉來釋放記憶體空間。
  • 要怎麼定義哪些是廢棄物件?來看一下gc root的運作機制:
    gc root會從靜態物件,以及運行中的執行緒為起點去掃描,譬如一個運行中的activiy,有使用到(也就是引用)一個list,list裡面存放(引用)了幾個string,GCRoot就會這樣不斷連結出去,最後去看哪些物件是沒有被連結到的邊緣人,就知道他們是廢物,該回收他們了。

1. 非靜態內部類別引起的memory leak

  • 非靜態內部類別,然後又建立它的靜態實例
class MainActivity : AppCompatActivity() {

        companion object Hi{
            lateinit var a:A
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if(a==null) a = A()

    }
        inner class A{

        }

有時候我們會在Activity內部建立了一個非靜態內部類別的單例,每次啟動Activity時都會使用該單例的資料,雖然避免了資源的重複建立,卻會造成記憶體洩漏,因為非靜態內部類別A會持有外部類別MainActivity的引用,而我們又建立了一個A靜態實例a,a的生命週期和app的一樣長,所以a會一直持有MainActivity的引用,導致MainActivity不能被回收。
正確的做法為:將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例。

  • handler引起的memory leak
    mainThread是生命週期最長的執行緒。當你人不在mainThread裡,可是你有一段code想在mainThread執行,就會宣告一個handler來把code放進去,也就是說,handler持有了mainThread的引用,這樣handler裡的東西才有辦法在mainThread上執行嘛。若這時候你的handler是一個activiy的內部類別,那就很危險,因為內部類別會隱性持有外部類別的引用(這樣它才能使用外部類別的變數啊),所以當activiy退出之後,因為他被內部類別handler持有引用,handler又跟長生不老的mainThread綁在一起,所以這個activity跟其所持有的整串物件都沒辦法被回收,發生memory leak。
class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
    }

    inner class h:Handler(){
        
    }

}

實際上你在建立內部類別handler時,IDE就提醒你會出事了。

2. context引起的memory leak

例如,需要帶context的單例對象。

class AppManager(val context: Context) {

    companion object {
        private var instance: AppManager? = null
        fun getInstance(context: Context): AppManager? {
            if (instance != null) {
                instance = AppManager(context)
            }
            return instance
        }
    }

}

若呼叫getInstance方法的時候,帶進來的參數是activity的context就會出事

總結:如何避免memory leak

一般Context造成的記憶體洩漏,幾乎都是當Context銷燬的時候,卻因為被引用導致銷燬失敗,而Application的Context物件可以理解為隨著程序存在的,所以我們總結出使用Context的正確姿勢:

1:當Application的Context能搞定的情況下,並且生命週期長的物件,優先使用Application的Context。

2:不要讓生命週期長於Activity的物件持有到Activity的引用。

3:儘量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類例項的引用,如果使用靜態內部類,將外部例項引用作為弱引用持有。


上一篇
Kotlin : java的靜態成員與特性在Kotlin中該如何使用
下一篇
Android x Kotlin: 工廠模式淺解[上]
系列文
Android Kotlin開發 -小嫩雞的30篇精選筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言