Android 源碼系列之從源碼的角度深入理解LeakCanary的內存泄露檢測機制(上)

        轉載請註明出處:http://blog.csdn.net/llew2011/article/details/52089842

        提到內存泄露有的小夥伴們可能遇見過,處理起來是非常棘手的。記得剛從事Android開發那會手機主流版本還是2.2的,手機內存普遍都很小,開發的時候在處理耗用內存過大的對象(比如Bitmap等)上,稍有不慎就會出現OOM,這很讓人頭疼,更爲頭疼的是發版後還是會收到有關OOM的反饋。爲了解決OOM的問題就反覆查代碼捋邏輯,然後藉助MAT工具來分析可能引發的內存泄露點,但是MAT使用起來很麻煩……直到去年著名的開源組織square在github上開源了著名的內存泄露檢測庫LeakCanary,它是專門用來檢測內存泄露的,有了這個庫後告別了MAT工具,更給力的是在程序中用一行代碼就搞定了,該庫從開源至今是最受歡迎的開源庫之一。用了該庫那麼久總該有點貢獻吧?因此我打算寫篇文章來幫助小夥伴們從源碼的角度深入理解一下LeakCanary的內存泄露檢測機制,如果你對該庫十分熟悉,請跳過本文(*^__^*) ……

        在分析LeakCanary源碼之前,我們有必要先來了解一下Java的內存分配策略以及Android中常見的內存泄漏。

Java內存分配策略

        一、Java程序運行時的內存分配策略有三種,它們分別是靜態分配,棧分配和堆分配;對應的三種存儲策略所使用的內存空間分別是靜態存儲區、棧存儲區和堆存儲區。
  • 靜態存儲區
            靜態存儲區主要存放靜態數據,全局static數據和常量。靜態存儲區在程序編譯時就已經分配好,在程序整個運行期間都存在。
  • 棧存儲區
            棧存儲區主要存放方法體內的局部變量。當一個方法被執行時,該方法體內的局部變量在棧存儲區內創建,方法執行結束後局部變量所持有的內存將會自動被釋放。
  • 堆存儲區
            堆存儲區通常就是指在程序運行時通過關鍵字new出來的內存區,這部分內存在不使用時將會由Java垃圾回收器來負責回收。

        二、堆與棧的區別

  • 在方法體內定義的一些基本類型的變量和對象的引用變量都稱爲局部變量,這些局部變量在棧內存中分配空間。當在一個方法塊中定義一個變量時,Java就會在棧中爲該變量分配內存空間,當超過該變量的作用域後,該變量也就無效了,分配給它的內存空間也將被釋放掉,該內存空間可以被重新使用。
  • 堆內存用來存放所有由new關鍵字創建的對象(包括該對象中的所有成員變量)和數組。在堆中分配的內存,將由Java垃圾回收器來自動管理。在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存的首地址,這個特殊的變量就是我們常說的引用變量。我們可以通過這個引用變量來訪問堆中的對象或者數組。

Java如何管理內存

        Java的內存管理簡單理解就是對象的分配和釋放。在Java中對象的內存分配是通過關鍵字new來進行分配的(注意:基本類型除外),這些對象在堆(Heap)存儲區中分配空間,而對象的釋放是由GC來決定和進行回收的。在Java的世界裏,內存的分配是由程序完成的,而內存的釋放是由GC完成的,這種分配和釋放兩條線的方法簡化了程序的工作,但是也加重了JVM的工作,這也是Java程序運行速度較慢得原因之一,因爲GC爲了能夠正確的釋放對象,它必須要監控每一個對象的運行狀態(包括對象的申請、引用、被引用、賦值等),當GC發現一個對象不再被引用了,那麼GC就會認爲該對象可以被回收。
        爲了更好的理解GC的工作原理,我們可以把對象假設爲有向圖的頂點,把引用關係看做是有向圖的邊,有向圖的邊從引用者指向被應用對象。另外,每個線程對象都可以看做一個有向圖的起始點,例如大多數程序都是從main()進程開始執行,那麼該圖就是以main()進程頂點開始的一顆根樹,在這個有向圖中,根頂點可以到達的對象都是有效對象,GC將不會回收這些對象;如果某個對象與根節點不可到達,那麼我們就認爲這個對象將不再被引用,可以被GC回收。如下圖所示:

        如上圖所示,GC會選擇一些它瞭解還存活的對象作爲內存遍歷的根節點(GC Roots),比如說thread stack 中的變量,JNI中的全局變量,zygote中的對象(class loader)等,然後開始對heap進行遍歷。到最後,部分沒有直接或者間接引用到GC Roots的就是需要回收的垃圾,就會被GC回收掉,如下圖藍色部分:

        Java使用有向圖的方式進行內存管理,這樣可以消除循環引用的問題,例如有三個對象A、B和C,在A中引用了B,在B中引用了C,在C中應用了A,只要它們和根進程不可到達,那麼GC也是可以回收它們的,這種方式的優點是管理內存的精度很高,但是效率較低。另外一種常用的內存管理技術是使用計數器,例如COM模型採用計數器方式管理構建,它與有向圖相比,精度很低(很難處理循環引用的問題),但是執行效率很高。

Android的內存溢出

        Android的內存溢出是如何發生的了?我們知道Android的虛擬機是基於寄存器的Dalvik,在不設置largeHeap的情況他它的最大堆內存空間爲16M,有的設備爲24M,根據不同的手機廠商該值是不同的,在設置了largeHeap的情況下該值會更大些但是也是有限制的,因此我們APP所能利用的內存空間是有限的。如果我們APP的內存超過了警戒線Android系統就會拋出OutOfMemory的異常錯誤,爲什麼會出現OOM的異常錯誤了?根據我們的開發經驗主要原因有兩個:一個是我們程序的bug導致資源得不到釋放造成內存泄露(比如長期保持對某些資源的引用);另一個是保存了多個耗用內存過大的對象(比如Bitmap等)。

Android常見內存泄露

  • Context造成的內存泄漏
            Context作爲應用的上下文環境,它提供了一系列訪問的接口以及一些應用層的操作,例如:startActivity()、sendBroadcast()等,所以在很多時候我們都需要依賴Conetxt來訪問相關資源數據,通常情況是把它作爲參數傳遞到某一個對象中,以供該對象使用。因爲Activity是Context的實現類,所以有時候爲了方便我們就直接傳遞當前Activity作爲Context來使用,但是這樣做會有一個很大的內存泄漏風險,比如在項目中用到了第三方的庫(友盟,Adapter等),在這些第三方庫中我們並不知道傳入到這些庫中他們是怎麼處理這個Context的,假如他們使用了靜態指針來保存傳入的Context對象,這樣就造成了內存泄漏,所以最好的方式是我們傳入一個與我們應用同生命週期的一個Context對象,而我們知道Application不僅是Context的實現類而且它的生命週期和應用的生命週期是相同的,所以我們在用到有用Context作爲參數進行傳遞的時候儘量使用Application對象(Dialog除外),這樣避免造成內存泄漏。
  • 靜態變量造成的內存泄漏
            我們先看一個由靜態變量造成內存泄漏的例子:
    public class MainActivity extends Activity {
    
    	private static Drawable mBackgroundDrawable;
    	
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		
    		TextView label = new TextView(this);
    		label.setText("Leaks are bad !");
    		if (null == mBackgroundDrawable) {
    			mBackgroundDrawable = getResources().getDrawable(R.id.bg);
    		}
    		label.setBackgroundDrawable(mBackgroundDrawable);
    		setContentView(label);
    	}
    }
    
           MainActivity中靜態成員變量mBackgroudDrawable緩存了drawable對象,這種寫法在Android 3.0之前會導致MainActivity銷燬後無法被系統回收,因爲在Android3.0之前View的setBackgroundDrawable()方法的源碼如下所示:
    public void setBackgroundDrawable(Drawable background) {
        
        // ...... 此處省略部分代碼 ......
    
        background.setCallback(this);
    
        // ...... 此處省略部分代碼 ......
    }
           根據源碼可知background對當前View保持了一個引用,而View又對當前的Activity保持了一個引用,當退出當前Activity時該Activity本該釋放,但是因爲mBackgroundDrawable是靜態成員變量,它的生命週期是伴隨着整個應用程序的,它間接持有了Activity的應用從而導致Activity對象不能被釋放,進而導致了內存泄露。在Android3.0之前Drawable的setCallback()方法是這樣的:
    public final void setCallback(Callback cb) {
        mCallback = cb;
    }
           但是在Android3.0之後寫法是這樣的:
    public final void setCallback(Callback cb) {
        mCallback = new WeakReference<Callback>(cb);
    }
    // 下邊是Android7.0版本寫法
    public final void setCallback(@Nullable Callback cb) {
        mCallback = cb != null ? new WeakReference<>(cb) : null;
    }
            由此可知在Android3.0之後Drawable在setCallback()方法中使用了軟引用,把內存泄露的問題修復了。
           【注意:】在程序中儘量避免使用static關鍵字。
  • 非靜態內部類造成的內存泄漏
           非靜態內部類也常常會造成內存泄露,請看下邊一個經典的例子:
    public class MainActivity extends Activity {
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new Thread() {
                @Override
                public void run() {
                    while(true) {
                        try {
                            sleep(10000);
                            // 執行長時間的耗時任務
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                };
            }.start();
        }
    }
    
           在上述例子中當MainActivity執行destroy()後因爲new出來的Thread還在執行,同時Thread爲MainActivity的匿名內部類,會持有MainActivity對象的引用(詳細可以通過查看字節碼求證)造成MainActivity不會被垃圾回收器回收,從而造成內存泄露。解決辦法可通過靜態內部類的方式:
    public class MainActivity extends Activity {
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new InnerTask().start();
        }
        
        private static class InnerTask extends Thread {
            @Override
            public void run() {
                while(true) {
                    try {
                        sleep(10000);
                        // 執行長時間的耗時任務
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
           由於靜態內部類不持有外部類的引用,與獨立類文件形式的類沒有本質區別。上面的例子需要注意的是在Activity退出時如果沒有必要應該將線程stop掉。
  • Handler造成的內存泄漏
           使用Handler不當往往也會造成內存泄露,先看下面的一個例子:
    public class MainActivity extends AppCompatActivity {
    
        private final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // do work
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    
           以上例子咋一看並沒有什麼問題,但是Android Lint時會提示我們一個這樣的warning:In Android,Handler classes should be static or leaks might occur。這個意思就是說在Android中,Handler類應該是靜態的否則可能會發生內存泄露。
           爲什麼會是這樣呢?這還要從Handler說起,我們都知道當Android應用程序啓動的時候在ActivityThread的main()方法中通過調用Looper.prepare()方法爲當前主線程創建了一個Looper對象,Looper的主要作用就是爲當前線程提供消息隊列以及不斷的循環處理Message對象。程序啓動後所有主要的事件(例如:屏幕上的點擊事件,Activity生命週期的方法等等)都包含在Message對象中,然後添加到Looper的消息隊列中一個個的處理,主線程的Looper存在整個應用程序的生命週期內。
           當在主線程創建一個Handler對象時,它會關聯到主線程的Looper的Message Queue,當Message被添加到Message Queue的時候會持有Handler的引用,當Looper處理到當前Message的時候會調用Handler的handleMessage()方法。在Java中非靜態內部類會隱式的持有外部類的一個引用,因此當通過Handler來send一個Message的時候,若Looper還沒有處理到該Message,那該Message就會持有Handler的引用而Handler又持有了當前Activity的引用,最終會導致Activity的所有資源在這個消息處理之前都不能釋放,所以會造成內存泄露。例子如下所示:
    public class MainActivity extends AppCompatActivity {
    
        private final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // do work
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    // do work
                }
            }, 600000);
    
            finish();
        }
    }
           在上面的代碼中當MainActivity結束後,在Message Queue處理這個Message之前,MainActivity會持續存活。由於Message持有Handler的引用而Handler又持有MainActivity的引用,所以就導致MainActivity的資源在Message被處理之前都不能被回收,所以會引發內存泄露。解決方法有以下幾種:
           解決辦法一:
    public class MainActivity extends AppCompatActivity {
    
        private final InnerHandler mHandler = new InnerHandler(this);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    // do work
                }
            }, 600000);
    
            finish();
        }
    
        private static class InnerHandler extends Handler {
            private final WeakReference<MainActivity> mainActivityWeakReference;
    
            public InnerHandler(MainActivity activity) {
                this.mainActivityWeakReference = null == activity ? null : new WeakReference<MainActivity>(activity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                if (null != mainActivityWeakReference) {
                    MainActivity mainActivity = mainActivityWeakReference.get();
                    if (null != mainActivity) {
                        // do work
                    }
                }
            }
        }
    }
           解決方法二:
    public class MainActivity extends AppCompatActivity {
    
        private static final Runnable TASK = new Runnable() {
            @Override
            public void run() {
                // do work
            }
        };
        private final Handler mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // do work
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.postDelayed(TASK, 600000);
    
            finish();
        }
    }
           解決方式三:
    public class MainActivity extends AppCompatActivity {
    
        private final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // do work
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    // do work
                }
            }, 600000);
    
            finish();
        }
    
        @Override
        protected void onDestroy() {
            if (null != mHandler) {
                mHandler.removeCallbacksAndMessages(null);
            }
            super.onDestroy();
        }
    }
            方法一和方法二同樣採用了內部靜態類的方式,方法二雖然避免了MainActivity的內存泄露,但是它靜態化一個Runnable靜態對象,對於這個靜態對象在MainAcitvity銷燬後並沒有什麼用處,也屬於一種小範圍的內存泄露,所以建議採用第一種方式解決。解決方式三在MainActivity的onDestroy()方法中調用mHandler的removeCallbacksAndMessage()方法移除消息隊列中那些還沒有被執行的Message對象防止間接持有MainActivity的引用而造成內存泄露。綜上所述建議採用方法一和方法三結合的方式,從根本上解決Handler造成的內存泄露。
  • HandlerThread造成的內存泄漏
           HandlerThread使用不當也會造成內存泄露,因爲HandlerThread實現的run()方法是一個無限循環,它不會自己結束,因此ThreadHandler的生命週期超過了Activity的生命週期,應該在Activity的onDestroy()時將HandlerThread線程停止掉,方法是mHanderThread.getLooper().quit()。
           對於非HandlerThread的子線程也應該確保Activity在銷燬前已經停止,例如在Activity的onDestroy()方法中調用mThread.join()方法。如果你對HandlerThread不是很瞭解,可以看我之前寫的一篇文章:Android 源碼系列之<七>從源碼的角度深入理解IntentService及HandlerThread 。
  • 註冊對象後未反註冊造成的內存泄漏
           提起註冊與反註冊,最常見的就是註冊廣播接收器,註冊觀察者等等;例如當我們註冊了一個Receiver後一定要調用unregisterReceiver()取消註冊。
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // ... 省略 ...
        }
    }, filter);
           上邊註冊了一個廣播接收器,這種通過匿名內部類註冊了廣播接收器是個很嚴重的錯誤,它會導致注入的廣播接收器不會被unregister而造成內存泄露。
  • 集合沒有清理造成的內存泄漏
           集合沒有清理往往也會造成內存泄露,我們通常會把一些對象的引用加入到集合中,當我們不需要該對象時,如果沒有把它的引用從集合中清理掉,這樣這個集合裝載的對象引用就越來越多,佔用內存也會越來越多……假如這個集合對象是static的話情況就會更嚴重。舉個例子:
    public class MainActivity extends AppCompatActivity {
    
        private ValueAnimator mValueAnimator;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
            mValueAnimator = ValueAnimator.ofInt(0, 1);
            mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    // do some work
                }
            });
            mValueAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    // do some work
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    // do some work
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
                    // do some work
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
                    // do some work
                }
            });
            mValueAnimator.setInterpolator(new LinearInterpolator());
            mValueAnimator.setDuration(3000);
            mValueAnimator.start();
        }
    }
           上邊的例子看起來也沒啥問題,可能我們也都這樣寫過,但是這確實存在內存泄露的風險,mValueAnimator中設置了兩個內部匿名監聽類,這倆內部匿名類默認持有MainActivity的引用,但是在mValueAnimator中設置監聽器是這樣處理的:
    public void addUpdateListener(AnimatorUpdateListener listener) {
        if (mUpdateListeners == null) {
            mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
        }
        mUpdateListeners.add(listener);
    }
    
    public void addListener(AnimatorListener listener) {
        if (mListeners == null) {
            mListeners = new ArrayList<AnimatorListener>();
        }
        mListeners.add(listener);
    }
           以上是Animator設置監聽器的源碼,根據源碼我們知道監聽器的設置最終是存儲在集合中的,那也就是說在MainActivity中的mValueAnimator間接持有了MainActivity的引用,會造成內存泄露的風險,應當在MainActivity的onDestroy()方法中清除監聽器,如下所示:
    @Override
    protected void onDestroy() {
        if (null != mValueAnimator) {
            mValueAnimator.removeAllUpdateListeners();
            mValueAnimator.removeAllListeners();
            mValueAnimator = null;
        }
        super.onDestroy();
    }
            這樣就避免了內存泄露的風險。
  • 資源對象沒有關閉造成的內存泄漏
            資源對象比如Cursor,File等往往都有用一些緩衝操作,在我們不使用的時候應該及時關閉它們,以便它們的緩衝所佔用的內存被及時回收,它們的緩衝不僅存在於Java虛擬機內,還會存在於Java虛擬機外。如果我們僅僅是把它的引用設置爲null,而不關閉它們,往往會造成內存泄漏。所以在用完有關資源的時候一定要手動關閉。
  • WebView造成的內存泄露
            對WebView打交道較多的童靴應該清楚WebView很容易造成內存泄露,下邊是我遇見的其中一個例子:
    10-24 23:19:08.360: E/BufferQueue(228): [com.**.**] dequeueBuffer: can't dequeue multiple buffers without setting the buffer count
    10-24 23:19:08.360: E/Surface(14162): dequeueBuffer failed (Invalid argument)
    10-24 23:19:08.360: E/ViewRootImpl(14162): Could not lock surface
    10-24 23:19:08.360: E/ViewRootImpl(14162): java.lang.IllegalArgumentException
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Surface.nativeLockCanvas(Native Method)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Surface.lockCanvas(Surface.java:246)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:2513)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.draw(ViewRootImpl.java:2487)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2331)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1961)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1060)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5782)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Choreographer.doCallbacks(Choreographer.java:574)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Choreographer.doFrame(Choreographer.java:544)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.os.Handler.handleCallback(Handler.java:733)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.os.Handler.dispatchMessage(Handler.java:95)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.os.Looper.loop(Looper.java:136)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at android.app.ActivityThread.main(ActivityThread.java:5086)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at java.lang.reflect.Method.invokeNative(Native Method)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at java.lang.reflect.Method.invoke(Method.java:515)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:893)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:702)
    10-24 23:19:08.360: E/ViewRootImpl(14162): 	at dalvik.system.NativeStart.main(Native Method)
            有關WebView造成內存泄露的問題請查看https://code.google.com/p/android/issues/detail?id=63738或者https://code.google.com/p/android/issues/detail?id=71742,所以要在Activity或者Fragment中合理的銷燬WebView,如下所示:
    @Override
    protected void onDestroy() {
        if (null != webView) {
            ViewParent viewParent = webView.getParent();
            if (viewParent instanceof ViewGroup) {
                ViewGroup parent = (ViewGroup) viewParent;
                parent.removeView(webView);
                webView.destroy();
            }
        }
        super.onDestroy();
    }
    測試一下看看
  • 不良代碼造成內存壓力
    ①、Bitmap使用不當
           Bitmap比較消耗內存,雖然系統能夠最終銷燬爲Bitmap分配的內存,但是在Bitmap使用完成後應該及時的recycle掉。recycle並不能確定立即會將Bitmap釋放掉,但是會給虛擬機一個暗示:該圖片可以釋放了。
    public void recycleBitmap(Bitmap bitmap) {
        if (null != bitmap && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmap = null;
        }
    }
    ②、構造Adapter時沒有使用緩存的ConvertView
           以構造ListView的BaseAdapter爲例,在BaseAdapter中提供了方法public View getView(int position, View convertView, ViewGroup parent)來向ListView提供每一個item所需要的View對象,初始時ListView會從BaseAdapter中根據當前屏幕布局實例化一定數量的View對象,同時ListView會將這些View對象緩存起來。當向上滾動ListView時,原先位於最上面的ListView的item對象會被回收,然後用來構造新出現的最下面的ListView的item。這個構造過程就是由getView()方法完成的,getView()方法的第二個形參convertView就是被緩衝起來的View對象。
           由此可以看出如果我們不使用convertView而是每次都在getView()方法中重新實例化一個View對象的話,會造成不必要的內存開銷,所以要使用緩存的conertView。
    ④、單例造成內存壓力
           單例也可能造成內存壓力,單例模式在開發中應該是設計模式中使用頻率最多的了,單例對象的生命週期也是伴隨着應用的生命週期的,如果我們在單例對象中使用集合來緩存一些對象的引用,那麼在緩存對象使用之後應當從緩存中刪除,否則單例對象會持續持有緩存對象的引用,造成那些使用過的緩存對象資源得不到釋放,佔用內存造成不必要的內存開銷,從而造成內存壓力。
        以上簡單的介紹了Java內存分配機制以及Android開發中常見的內存泄露情形。我們在平時開發中要細心謹慎,儘量避免不恰當的代碼造成程序出現內存泄露最終導致應用crash,由於篇幅原因,我將在下篇文章 Android 源碼系列之<十三>從源碼的角度深入理解LeakCanary的內存泄露檢測機制(中)中向小夥伴們介紹如何使用LeakCanary來檢查代碼中出現的內存泄露,敬請期待!!!最後感謝收看(*^__^*) ……


       【參考文章:】
       http://blog.csdn.net/gemmem/article/details/13017999

       http://blog.csdn.net/gemmem/article/details/8920039






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章