1. 靜態 Activity
2. 靜態 View
3. 非靜態內部類
4. 匿名類
5. Handler
6. Thread
7. TimerTask
8. SensorManager
1.資源對象沒關閉造成的內存泄漏
2.構造Adapter時,沒有使用緩存的convertView
3.Bitmap對象不在使用時調用recycle()釋放內存
4.試着使用關於application的context來替代和activity相關的context
5.注冊沒取消造成的內存泄漏
6.集合中對象沒清理造成的內存泄漏
避免引用Context造成的內存泄露
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
interface 放在fragment內或者放在外面單獨存在,一般不會造成activity泄漏
public interface DialogCallback {
void showModifyInputDialog();
void showTheMessageDialog();
}
其實一般的匿名內部類是不會導致activity釋放不了的,只要你不在handle內進行奇怪的操作
private Handler uiHandler = new Handler(){
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
最近在維護代碼,發現一個自定義View(這個View是在一個AsyncTask的工作線程doInBackground中新建的,在UI線程onPostExecute中添加進window中的)經常會泄漏內存,導致其引用的Activity一直得不到釋放,每次退出再進去都會導致Activity的對象+1.
package com.xxx.launcher.view; import android.content.Context; import android.util.Log; import android.view.View; public class WeatherTextView extends SkinTextView { public WeatherTextView (Context context) {
super(context); postDelayed(mShowCityRunnable, 200);//這一步有問題 } @Override protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); if (visibility == View.VISIBLE) { post(mShowCityRunnable); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); onCancel(); }; public void onCancel(){ removeCallbacks(mShowCityRunnable); } private Runnable mShowCityRunnable = new Runnable() { @Override public void run() { Log.i("mShowCityRunnable-------TAG", "run"+mShowCityRunnable); setText(city); } }; }
最后通過MAT工具查看內存快照的比較,發現了如下的情況,把內存泄露的地方鎖定在了WeatherTextView$2的第二個內部類中mShowCityRunnable ,一開始始終都想不到這個內部類到底有什么地方泄露了,最后突然靈光一閃,是不是View的post()方法導致的,在網上一查,發現確實。
public boolean post(Runnable action) { Handler handler; AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { handler = attachInfo.mHandler; } else { // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true; } return handler.post(action); }
在post() 函數注釋中,明確寫着:This method can be invoked from outside of the UI thread only when this View is attached to a window.
當View還沒有attach到當前window時,mAttachInfo 值為 null,故而執行 else語句,再看一下getRunQueue()和其post() 方法:
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); static RunQueue getRunQueue() { RunQueue rq = sRunQueues.get(); if (rq != null) { return rq; } rq = new RunQueue(); sRunQueues.set(rq); return rq; } …… static final class RunQueue { private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>(); void post(Runnable action) { postDelayed(action, 0); } void postDelayed(Runnable action, long delayMillis) { HandlerAction handlerAction = new HandlerAction(); handlerAction.action = action; handlerAction.delay = delayMillis; synchronized (mActions) { mActions.add(handlerAction); } } void executeActions(Handler handler) { synchronized (mActions) { final ArrayList<handleraction> actions = mActions; final int count = actions.size(); for (int i = 0; i < count; i++) { final HandlerAction handlerAction = actions.get(i); handler.postDelayed(handlerAction.action, handlerAction.delay); } actions.clear(); } } …… }
這樣會把Runnable 插入到一個靜態的ThreadLocal的RunQueue隊列里(在工作線程中post,就會插入工作線程的RunQueue隊列),針對本文開頭給出的例子,那么插入的Runnable什么時候得到執行呢?
調用RunQueue.executeActions()方法只有一處,即在ViewRootImpl類的如下非靜態方法中
private void performTraversals() { if (mLayoutRequested && !mStopped) { // Execute enqueued actions on every layout in case a view that was detached // enqueued an action after being detached getRunQueue().executeActions(attachInfo.mHandler); } }
該方法是在UI線程執行的(見ViewRootImpl.handleMessage()), 故當UI線程執行到該performTraversals() 里的 getRunQueue() 時,得到的是UI線程中的RunQueue,這樣AsyncTask 線程中的 RunQueue永遠不會被執行到, 並且AsyncTask的是用線程池實現的,AsyncTask啟動的線程會長期存在,造成如下引用關系:
AsyncTask線程 => 靜態的ThreadLocal的RunQueue => Runnable => View=> Activity;
如此即使activity finish 了,確始終存在一個靜態引用鏈引用這該activity,而 Activity一般又引用着很多資源,比如圖片等,最終造成嚴重資源泄漏。
最后我是寫改成
package com.xxx.launcher.view; import android.content.Context; import android.util.Log; import android.view.View; public class WeatherTextView extends SkinTextView { public WeatherTextView (Context context) { super(context); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); postDelayed(mShowCityRunnable, 200); //在onAttachedToWindow方法中執行post方法 } @Override protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); if (visibility == View.VISIBLE) { post(mShowCityRunnable); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); onCancel(); }; public void onCancel(){ removeCallbacks(mShowCityRunnable); } private Runnable mShowCityRunnable = new Runnable() { @Override public void run() { Log.i("mShowCityRunnable-------TAG", "run"+mShowCityRunnable); setText(city); } }; }
這樣Activity就沒有再被其他東西引用了,就不會發生Activity的泄漏了,Activity就可以被釋放了。這樣,不管進入退出進入這個MainMenuActivity多少次,MainMenuActivity的對象就只會保存一份。
ps:至於為什么在兩個Histogram(直方圖)的比較圖中還是顯示MainMenuActivity+1,則是因為這是類名,類被加載之后,在進程結束之前不會被回收
===============================================================================================================================
===============================================================================================================================
這種泄漏一般是因為mStorageManager 注冊了但是沒有取消注冊
mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
mStorageManager.registerListener(mStoragelistener);
取消注冊就可以了
if (mStorageManager != null) { mStorageManager.unregisterListener(mStoragelistener); }