Android 記憶體流失的一些情況。

來源:互聯網
上載者:User

標籤:

最近在維護代碼,發現一個自訂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);}

 

 

Android 記憶體流失的一些情況。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.