1. 程式人生 > >Android導致記憶體洩漏的幾種情形

Android導致記憶體洩漏的幾種情形

集合類

集合類如果僅僅有新增元素的方法,而沒有相應的刪除機制,導致記憶體被佔用。如果這個集合類是全域性性的變數(比如類中的靜態屬性,全域性性的map等即有靜態引用或final一直指向它),那麼沒有相應的刪除機制,很可能導致集合所佔用的記憶體只增不減。

單例模式

不正確使用單例模式是引起記憶體洩漏的一個常見問題,單例物件在被初始化後將在JVM的整個生命週期中存在(以靜態變數的方式),如果單例物件持有外部物件的引用,那麼這個外部物件將不能被JVM正常回收,導致記憶體洩漏。
如果需要Context,儘量引用Application,而不用Activity。

Android元件或特殊集合物件的使用

BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在Activity onDestory或者某類生命週期結束之後一定要unregister或者close掉,否則這個Activity類會被system強引用,不會被記憶體回收。不要直接對Activity進行直接引用作為成員變數,如果不得不這麼做,請用private WeakReference mActivity來做,相同的,對於Service等其他有自己生命週期的物件來說,直接引用都需要謹慎考慮是否會存在記憶體洩漏的可能。

Handler

Handler的生命週期與Activity不一致

  1. 由於Handler屬於TLS(Thread Local Storage)變數,生命週期和Activity是不一致的。
  2. 當Android應用啟動的時候,會先建立一個UI主執行緒的Looper物件,Looper實現了一個簡單的訊息佇列,一個一個的處理裡面的Message物件。主執行緒Looper物件在整個應用生命週期中存在。
  3. 當在主執行緒中初始化Handler時,該Handler和Looper的訊息佇列關聯(沒有關聯會報錯的)。傳送到訊息佇列的Message會引用傳送該訊息的Handler物件,這樣系統可以呼叫 Handler#handleMessage(Message) 來分發處理該訊息。
  4. 只要Handler傳送的Message尚未被處理,則該Message及傳送它的Handler物件將被執行緒MessageQueue一直持有。因此這種實現方式一般很難保證跟View或者Activity的生命週期保持一致,故很容易導致無法正確釋放。

handler引用Activity阻止了GC對Acivity的回收

  1. 在Java中,非靜態(匿名)內部類會預設隱性引用外部類物件。而靜態內部類不會引用外部類物件。
  2. 如果外部類是Activity,則會引起Activity洩露。
  3. 當Activity finish後,延時訊息會繼續存在主執行緒訊息佇列中1分鐘,然後處理訊息。而該訊息引用了Activity的Handler物件,然後這個Handler又引用了這個Activity。這些引用物件會保持到該訊息被處理完,這樣就導致該Activity物件無法被回收,從而導致了上面說的 Activity洩露。

如上所述,Handler的使用要尤為小心,否則將很容易導致記憶體洩漏的發生。

錯誤示例:

    private final Handler mHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            // ...  
        }  
    }; 

解決辦法:
使用顯形的引用,1.靜態內部類。 2. 外部類
使用弱引用 2. WeakReference

正確示例:

    private static class MyHandler extends Handler {  
        private final WeakReference<HandlerActivity2> mActivity;  

        public MyHandler(HandlerActivity2 activity) {  
            mActivity = new WeakReference<HandlerActivity2>(activity);  
        }  

        @Override  
        public void handleMessage(Message msg) {  
            System.out.println(msg);  
            if (mActivity.get() == null) {  
                return;  
            }  
            mActivity.get().todo();  
        }  
    }

    @Override  
    public void onDestroy() {  
        //  If null, all callbacks and messages will be removed.  
        mHandler.removeCallbacksAndMessages(null);  
    }

ThreadHandler

HandlerThread handlerThread = new HandlerThread("test");
handlerThread.start(); //建立HandlerThread後一定要記得start()

獲取HandlerThread的Looper

Looper looper = handlerThread.getLooper();

建立Handler,通過Looper初始化

Handler handler = new Handler(looper);

通過以上三步我們就成功建立HandlerThread。通過handler傳送訊息,就會在子執行緒中執行。
如果我們在Activity的onCreate中進行上面的初始化,不再進行其他工作,那麼就有可能造成記憶體洩漏。
因為不過Activity finish後,程序沒有退出,那麼建立的test會一直存在。

解決辦法:
在onDestory中呼叫handlerThread.quit()或handlerThread.quitSafely();

Thread記憶體洩漏

執行緒也是造成記憶體洩漏的一個重要的源頭。執行緒產生的記憶體洩漏主要原因在於執行緒生命週期的不可控。比如執行緒是Activity的內部類,則執行緒物件中儲存了Activity的一個引用,當執行緒的run函式耗時較長沒有結束時,執行緒物件是不會被銷燬的,因此它引用的老的Activity也不會被銷燬,因此就出現了記憶體洩漏的問題。

一些不良程式碼造成的記憶體壓力

這些程式碼並不造成記憶體洩漏,但是它們,或是對沒使用的記憶體沒有進行有效及時的釋放,或是沒有有效的利用已有的物件而是頻繁的申請新記憶體。

Bitmap沒呼叫recycle()

Bitmap物件在不使用時,我們應該先呼叫recycle()釋放記憶體,然後將它設定為null。因為載入Bitmap物件的記憶體空間,一部分是java的,一部分C的(因為Bitmap分配的底層是通過JNI呼叫的)。而這個recycle()就是針對C部分的記憶體釋放。

構造Adapter時,沒有使用快取的convertView。