1、單例模式引起的內存泄露
由於單例模式的靜態特性,使得它的生命周期和我們的應用一樣長,如果讓單例無限制的持有Activity的強引用就會導致內存泄漏
如錯誤代碼示例:
public class UserInfoBean { private static UserInfoBean userInfoBean; private Context mContext; private UserInfoBean(Context context) { this.mContext = context; } public static UserInfoBean getUserInfoBean(Context context) { if (userInfoBean == null) { synchronized (UserInfoBean.class) { if (userInfoBean == null) { userInfoBean = new UserInfoBean(context); } } } return userInfoBean; } }
正確代碼:
將 this.mContext = context改成:this.mContext = context.getApplicationContext();或者代碼中用到的Context可以使用自己定義的MyApplication中的MyApplication.getInstance獲取;
2、Handler引起的內存泄露
Handler引起的內存泄漏在開發中最為常見的。Handler、Message、MessageQueue都是相互關聯在一起的,如果Handler發送的Message尚未被處理,那么該Message以及發送它的Handler對象都會被線程MessageQueue一直持有。
由於Handler屬於TLS(Thread Local Storage)變量,生命周期和Activity是不一致的,因此這種實現方式很難保證跟Activity的生命周期一直,所以很容易無法釋放內存
如錯誤代碼:
private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler.sendMessageDelayed(Message.obtain(), 60000*5); }
在上面的例子中生命了一個延時5分鍾執行的Message,當該Activity退出的時候,延時任務(Message)還在主線成的MessageQueue中等待,此時的Message持有Handler的強引用,並且由於Handler是我們的Activity類的非靜態內部類,所以Handler會持有該Activity的強引用,此時該Activity退出時無法進行內存回收,造成內存泄漏。
解決辦法:將Handler聲明為靜態內部類,這樣它就不會持有外部類的引用了,Handler的的生命周期就與Activity無關了。不過倘若用到Context等外部類的非static對象,還是應該通過使用Application中與應用同生命周期的Context比較合適
正確代碼:
private static final class MyHandler extends Handler { private WeakReference<HomeMainActivity> mActivity; public MyHandler(HomeMainActivity mainActivity) { mActivity = new WeakReference<>(mainActivity);
//or
//mActivity=mainActivity.getApplicationContext; } @Override public void handleMessage(Message msg) { super.handleMessage(msg); HomeMainActivity mainActivity = mActivity.get(); if (null != mActivity) { //相關處理 } } }
private final MyHandler mHandler = new MyHandler(this);
mHandler.sendMessageDelayed(Message.obtain(), 60000*5);
雖然我們結束了Activity的內存泄漏問題,但是經過Handler發送的延時消息還在MessageQueue中,Looper也在等待處理消息,所以我們要在Activity銷毀的時候處理掉隊列中的消息。
@Override protected void onDestroy() { super.onDestroy(); //傳入null,就表示移除所有Message和Runnable mHandler.removeCallbacksAndMessages(null); }
3、匿名內部類在異步線程中的使用引起的內存泄漏
Android開發經常會繼承實現 Activity 或者 Fragment 或者 View。如果使用了匿名類,而又被異步線程所引用,如果沒有任何措施同樣會導致內存泄漏的:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_inner_bad); Runnable runnable1 = new MyRunnable(); Runnable runnable2 = new Runnable() { @Override public void run() { } }; } private static class MyRunnable implements Runnable{ @Override public void run() { } } }
runnable1 和 runnable2的區別就是,runnable2使用了匿名內部類,我們看看引用時的引用內存
可以看到,runnable1是沒有什么特別的。但runnable2多出了一個MainActivity的引用,若是這個引用再傳入到一個異步線程,此線程在和Activity生命周期不一致的時候,也就造成了Activity的泄露。
4、集合引發的內存泄漏
我們通常會把一些對象的引用加入到集合容器(比如ArrayList)中,當我們不再需要該對象時,並沒有把它的引用從集合中清理掉,當集合中的內容過於大的時候,並且是static的時候就造成了內存泄漏,所有最好在onDestory清空;
private List<String> nameList; private List<Fragment> list; @Override public void onDestroy() { super.onDestroy(); if (nameList != null){ nameList.clear(); nameList = null; } if (list != null){ list.clear(); list = null; } }
5、Android WebView Memory Leak WebView內存泄漏(查看作者原文)
WebView解析網頁時會申請Native堆內存用於保存頁面元素,當頁面較復雜時會有很大的內存占用。如果頁面包含圖片,內存占用會更嚴重。並且打開新頁面時,為了能快速回退,之前頁面占用的內存也不會釋放。有時瀏覽十幾個網頁,都會占用幾百兆的內存。這樣加載網頁較多時,會導致系統不堪重負,最終強制關閉應用,也就是出現應用閃退或重啟。
要使用WebView不造成內存泄漏,首先應該做的就是不能在xml中定義webview節點,而是在需要的時候動態生成。即:可以在使用WebView的地方放置一個LinearLayout類似ViewGroup的節點,然后在要使用WebView的時候,動態生成即:
WebView mWebView = new WebView(getApplicationgContext()); LinearLayout mll = findViewById(R.id.xxx); mll.addView(mWebView);
然后一定要在onDestroy()方法中顯式的調用
protected void onDestroy() { super.onDestroy(); mWebView.removeAllViews(); mWebView.destroy() }
注意: new WebView(getApplicationgContext()) ;必須傳入ApplicationContext如果傳入Activity的Context的話,對內存的引用會一直被保持着。有人用這個方法解決了當Activity被消除后依然保持引用的問題。但是你會發現,如果你需要在WebView中打開鏈接或者你打開的頁面帶有flash,獲得你的WebView想彈出一個dialog,都會導致從ApplicationContext到ActivityContext的強制類型轉換錯誤,從而導致你應用崩潰。這是因為在加載flash的時候,系統會首先把你的WebView作為父控件,然后在該控件上繪制flash,他想找一個Activity的Context來繪制他,但是你傳入的是ApplicationContext。后果,你可以曉得了哈。
其他常見的引起內存泄漏原因
- 構造Adapter時,沒有使用緩存的 convertView
- Bitmap在不使用的時候沒有使用recycle()釋放內存
- 非靜態內部類的靜態實例容易造成內存泄漏:即一個類中如果你不能夠控制它其中內部類的生命周期(譬如Activity中的一些特殊Handler等),則盡量使用靜態類和弱引用來處理(譬如ViewRoot的實現)。
- 警惕線程未終止造成的內存泄露;譬如在Activity中關聯了一個生命周期超過Activity的Thread,在退出Activity時切記結束線程。一個典型的例子就是HandlerThread的run方法是一個死循環,它不會自己結束,線程的生命周期超過了Activity生命周期,我們必須手動在Activity的銷毀方法中調用thread.getLooper().quit();才不會泄露。
- 對象的注冊與反注冊沒有成對出現造成的內存泄露;譬如注冊廣播接收器、注冊觀察者(典型的譬如數據庫的監聽)等。
- 創建與關閉沒有成對出現造成的泄露;譬如Cursor資源必須手動關閉,WebView必須手動銷毀,流等對象必須手動關閉等。
- 不要在執行頻率很高的方法或者循環中創建對象(比如onMeasure),可以使用HashTable等創建一組對象容器從容器中取那些對象,而不用每次new與釋放。
- 避免代碼設計模式的錯誤造成內存泄露;譬如循環引用,A持有B,B持有C,C持有A,這樣的設計誰都得不到釋放
參考文章: