Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android 內存洩漏的一些情況。,android洩漏

Android 內存洩漏的一些情況。,android洩漏

編輯:關於android開發

Android 內存洩漏的一些情況。,android洩漏


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

 

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);
        }
    };
}

MainMenuActivity多少次,MainMenuActivity的對象就只會保存一份。

ps:至於為什麼在兩個Histogram(直方圖)的比較圖中還是顯示MainMenuActivity+1,則是因為這是類名,類被加載之後,在進程結束之前不會被回收

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

===============================================================================================================================

===============================================================================================================================

 

這種洩漏一般是因為mStorageManager 注冊了但是沒有取消注冊

mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
mStorageManager.registerListener(mStoragelistener);

取消注冊就可以了

if (mStorageManager != null) {
    mStorageManager.unregisterListener(mStoragelistener);
}

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved