Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 再談android內存洩漏—常見的八種導致 APP 內存洩漏的問題

再談android內存洩漏—常見的八種導致 APP 內存洩漏的問題

編輯:關於Android編程

像 Java 這樣具有垃圾回收功能的語言的好處之一,就是程序員無需手動管理內存分配。這減少了段錯誤(segmentation fault)導致的閃退,也減少了內存洩漏導致的堆空間膨脹,讓編寫的代碼更加安全。然而,Java 中依然有可能發生內存洩漏。所以你的安卓 APP 依然有可能浪費了大量的內存,甚至由於內存耗盡(OOM)導致閃退。

傳統的內存洩漏是由忘記釋放分配的內存導致的,而邏輯上的內存洩漏則是由於忘記在對象不再被使用的時候釋放對其的引用導致的。如果一個對象仍然存在強引用,垃圾回收器就無法對其進行垃圾回收。在安卓平台,洩漏 Context 對象問題尤其嚴重。這是因為像 Activity 這樣的 Context 對象會引用大量很占用內存的對象,例如 View 層級,以及其他的資源。如果 Context 對象發生了內存洩漏,那它引用的所有對象都被洩漏了。安卓設備大多內存有限,如果發生了大量這樣的內存洩漏,那內存將很快耗盡。

如果一個對象的合理生命周期沒有清晰的定義,那判斷邏輯上的內存洩漏將是一個見仁見智的問題。幸運的是,activity 有清晰的生命周期定義,使得我們可以很明確地判斷 activity 對象是否被內存洩漏。onDestroy() 函數將在 activity 被銷毀時調用,無論是程序員主動銷毀 activity,還是系統為了回收內存而將其銷毀。如果 onDestroy 執行完畢之後,activity 對象仍被 heap root 強引用,那垃圾回收器就無法將其回收。所以我們可以把生命周期結束之後仍被引用的 activity 定義為被洩漏的 activity。

Activity 是非常重量級的對象,所以我們應該極力避免妨礙系統對其進行回收。然而有多種方式會讓我們無意間就洩露了 activity 對象。我們把可能導致 activity 洩漏的情況分為兩類,一類是使用了進程全局(process-global)的靜態變量,無論 APP 處於什麼狀態,都會一直存在,它們持有了對 activity 的強引用進而導致內存洩漏,另一類是生命周期長於 activity 的線程,它們忘記釋放對 activity 的強引用進而導致內存洩漏。下面我們就來詳細分析一下這些可能導致 activity 洩漏的情況。

1. 靜態 Activity

洩漏 activity 最簡單的方法就是在 activity 類中定義一個 static 變量,並且將其指向一個運行中的 activity 實例。如果在 activity 的生命周期結束之前,沒有清除這個引用,那它就會洩漏了。這是因為 activity(例如 MainActivity) 的類對象是靜態的,一旦加載,就會在 APP 運行時一直常駐內存,因此如果類對象不卸載,其靜態成員就不會被垃圾回收。

 

2. 靜態 View

另一種類似的情況是對經常啟動的 activity 實現一個單例模式,讓其常駐內存可以使它能夠快速恢復狀態。然而,就像前文所述,不遵循系統定義的 activity 生命周期是非常危險的,也是沒必要的,所以我們應該極力避免。

但是如果我們有一個創建起來非常耗時的 View,在同一個 activity 不同的生命周期中都保持不變呢?所以讓我們為它實現一個單例模式,就像這段代碼。現在一旦 activity 被銷毀,那我們就應該釋放大部分的內存了。

內存洩漏了!因為一旦 view 被加入到界面中,它就會持有 context 的強引用,也就是我們的 activity。由於我們通過一個靜態成員引用了這個 view,所以我們也就引用了 activity,因此 activity 就發生了洩漏。所以一定不要把加載的 view 賦值給靜態變量,如果你真的需要,那一定要確保在 activity 銷毀之前將其從 view 層級中移除。

3. 內部類

現在讓我們在 activity 內部定義一個類,也就是內部類。這樣做的原因有很多,比如增加封裝性和可讀性。如果我們創建了一個內部類的對象,並且通過靜態變量持有了 activity 的引用,那也會發生 activity 洩漏。

不幸的是,內部類能夠引用外部類的成員這一優勢,就是通過持有外部類的引用來實現的,而這正是 activity 洩漏的原因。

4. 匿名類

類似的,匿名類同樣會持有定義它們的對象的引用。因此如果在 activity 內定義了一個匿名的 AsyncTask 對象,就有可能發生內存洩漏了。如果 activity 被銷毀之後 AsyncTask 仍然在執行,那就會組織垃圾回收器回收 activity 對象,進而導致內存洩漏,直到執行結束才能回收 activity。

5. Handlers

同樣的,定義一個匿名的 Runnable 對象並將其提交到 Handler 上也可能導致 activity 洩漏。Runnable 對象間接地引用了定義它的 activity 對象,而它會被提交到 Handler 的 MessageQueue 中,如果它在 activity 銷毀時還沒有被處理,那就會導致 activity 洩漏了。

6. Threads

同樣的,使用 Thread 和 TimerTask 也可能導致 activity 洩漏。

7. Timer Tasks

只要它們是通過匿名類創建的,盡管它們在單獨的線程被執行,它們也會持有對 activity 的強引用,進而導致內存洩漏。

8. Sensor Manager

最後,系統服務可以通過 context.getSystemService 獲取,它們負責執行某些後台任務,或者為硬件訪問提供接口。如果 context 對象想要在服務內部的事件發生時被通知,那就需要把自己注冊到服務的監聽器中。然而,這會讓服務持有 activity 的引用,如果程序員忘記在 activity 銷毀時取消注冊,那就會導致 activity 洩漏了。

現在,我們展示了八種很容易不經意間就洩漏大量內存的情景。請記住,最壞的情況下,你的 APP 可能會由於大量的內存洩漏而內存耗盡,進而閃退,但它並不總是這樣。相反,內存洩漏會消耗大量的內存,但卻不至於內存耗盡,這時,APP 會由於內存不夠分配而頻繁進行垃圾回收。垃圾回收是非常耗時的操作,會導致嚴重的卡頓。在 activity 內部創建對象時,一定要格外小心,並且要經常測試是否存在內存洩漏。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved