[Android Memory] App調試內存泄露之Context篇(上)

轉載自:http://www.cnblogs.com/qianxudetianxia/p/3645106.htmlhtml

Context做爲最基本的上下文,承載着Activity,Service等最基本組件。當有對象引用到Activity,並不能被回收釋放,必將形成大範圍的對象沒法被回收釋放,進而形成內存泄漏。java

下面針對一些經常使用場景逐一分析。android

1. CallBack對象的引用oracle

    先看一段代碼:異步

@Override
protectedvoid onCreate(Bundle state){
  super.onCreate(state);
   
  TextView label =new TextView(this);
  label.setText("Leaks are bad");
   
  setContentView(label);
}

你們看看有什麼問題嗎?ide

    沒問題是吧,繼續看:學習

private static Drawable sBackground;
   
@Override
protected void onCreate(Bundle state){
  super.onCreate(state);
   
  TextView label =new TextView(this);
  label.setText("Leaks are bad");
   
  if(sBackground ==null){
    sBackground = getDrawable(R.drawable.large_bitmap);
  }
  label.setBackgroundDrawable(sBackground);
   
  setContentView(label);
}

有問題嗎?fetch

    哈哈,先Hold住一下,先來講一下android各版本發佈的歷史:ui

 

/*
2.2        2010-3-20,Froyo
2.3        2010-12-6, Gingerbread
3.0        2011-2-22, Honeycomb
4.0        2011-10-11 Ice Cream Sandwich
*/

 

瞭解源碼的歷史,是頗有益於咱們分析android代碼的。this

    好,開始分析代碼。

    首先,查看setBackgroundDrawable(Drawable background)方法源碼裏面有一行代碼引發咱們的注意:

public void setBackgroundDrawable(Drawable background) {
    // ... ...
    background.setCallback(this);
    // ... ...
}

    因此sBackground對view保持了一個引用,view對activity保持了一個引用。

    當退出當前Activity時,當前Activity本該釋放,可是由於sBackground是靜態變量,它的生命週期並無結束,而sBackground間接保持對Activity的引用,致使當前Activity對象不能被釋放,進而致使內存泄露。

    因此結論是:有內存泄露!

    這是Android官方文檔的例子:http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html

    到此結束了嗎?

    我發現網上太多直接抄或者間接抄這篇文章,一搜一大片,而且吸引了大量的Android初學者不斷的轉載學習。

    可是通過本人深刻分析Drawable源碼,事情發生了一些變化。

    Android官方文檔的這篇文章是寫於2009年1月的,當時的Android Source至少是Froyo以前的。

    Froyo的Drawable的setCallback()方法的實現是這樣的:

public final void setCallback(Callback cb) {
        mCallback = cb;
}

在GingerBread的代碼仍是如此的。

    可是當進入HoneyComb,也就是3.0以後的代碼咱們發現Drawable的setCallback()方法的實現變成了:

public final void setCallback(Callback cb) {
        mCallback = new WeakReference<Callback>(cb);
}

也就是說3.0以後,Drawable使用了軟引用,把這個泄露的例子問題修復了。(至於軟引用怎麼解決了之後有機會再分析吧)

    因此最終結論是,在android3.0以前是有內存泄露,在3.0以後無內存泄露!

    若是認真比較代碼的話,Android3.0先後的代碼改進了大量相似代碼,前面的Cursor篇裏的例子也是在3.0以後修復了。

    從這個例子中,咱們很好的發現了內存是怎麼經過回調泄露的,同時經過官方代碼的update也瞭解到了怎麼修復相似的內存泄露。

2. System Service對象

    經過各類系統服務,咱們可以作一些系統設計好的底層功能:

//ContextImpl.java
@Override
public Object getSystemService(String name) {
    ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
    return fetcher == null ? null : fetcher.getService(this);
}
 
static {
    registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() {
            public Object getService(ContextImpl ctx) {
            return AccessibilityManager.getInstance(ctx);
            }});
 
    registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
            public Object getService(ContextImpl ctx) {
            return new CaptioningManager(ctx);
            }});
 
    registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
            public Object createService(ContextImpl ctx) {
            IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
            IAccountManager service = IAccountManager.Stub.asInterface(b);
            return new AccountManager(ctx, service);
            }});
    // ... ...
}

這些其實就是定義在Context裏的,按理說這些都是系統的服務,應該都沒問題,可是代碼到了各家廠商一改,事情發生了一些變化。

      一些廠商定義的服務,或者廠商本身修改了一些新的代碼致使系統服務引用了Context對象不能及時釋放,我曾經碰到過Wifi,Storage服務都有內存泄露。

     咱們改不了這些系統級應用,咱們只能修改本身的應用。

     解決方案就是:使用ApplicationContext代替Context。

     舉個例子吧:

// For example
mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
改爲:
mStorageManager = (StorageManager) getApplicationContext().getSystemService(Context.STORAGE_SERVICE);

3. Handler對象

    先看一段代碼:

public class MainActivity extends QActivity {
        // lint tip: This Handler class should be static or leaks might occur
    class MyHandler extends Handler {
        ... ...
    }
}

Handler泄露的關鍵點有兩個:

    1). 內部類

    2). 生命週期和Activity不必定一致

    第一點,Handler使用的比較多,常常須要在Activity中建立內部類,因此這種場景仍是不少的。

    內部類持有外部類Activity的引用,當Handler對象有Message在排隊,則沒法釋放,進而致使Activity對象不能釋放。

    若是是聲明爲static,則該內部類不持有外部Acitivity的引用,則不會阻塞Activity對象的釋放。

    若是聲明爲static後,可在其內部聲明一個弱引用(WeakReference)引用外部類。

 

public class MainActivity extends Activity {
    private CustomHandler mHandler;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new CustomHandler(this);
    }
 
    static class CustomHandlerextends Handler {
        // 內部聲明一個弱引用,引用外部類
        private WeakReference<MainActivity > activityWeakReference;
        public MyHandler(MyActivity activity) {
            activityWeakReference= new WeakReference<MainActivity >(activity);
        }
                // ... ...   
    }
}

 

第二點,其實不單指內部類,而是全部Handler對象,如何解決上面說的Handler對象有Message在排隊,而不阻塞Activity對象釋放?

    解決方案也很簡單,在Activity onStop或者onDestroy的時候,取消掉該Handler對象的Message和Runnable。

    經過查看Handler的API,它有幾個方法:removeCallbacks(Runnable r)和removeMessages(int what)等。

 

// 一切都是爲了避免要讓mHandler拖泥帶水
@Override
public void onDestroy() {
    mHandler.removeMessages(MESSAGE_1);
    mHandler.removeMessages(MESSAGE_2);
    mHandler.removeMessages(MESSAGE_3);
    mHandler.removeMessages(MESSAGE_4);
 
    // ... ...
 
    mHandler.removeCallbacks(mRunnable);
 
    // ... ...
}

 

  上面的代碼太長?好吧,出大招:

@Override
public void onDestroy() {
    //  If null, all callbacks and messages will be removed.
    mHandler.removeCallbacksAndMessages(null);
}

有人會問,當Activity退出的時候,我還有好多事情要作,怎麼辦?我想必定有辦法的,好比用Service等等.

 

4. Thread對象

    同Handler對象可能形成內存泄露的原理同樣,Thread的生命週期不必定是和Activity生命週期一致。

    並且由於Thread主要面向多任務,每每會形成大量的Thread實例。

    據此,Thread對象有2個須要注意的泄漏點:

    1). 建立過多的Thread對象

    2). Thread對象在Activity退出後依然在後臺執行

    解決方案是:

    1). 使用ThreadPoolExecutor,在同時作不少異步事件的時候是很經常使用的,這個不細說。

    2). 當Activity退出的時候,退出Thread

    第一點,例子太多,建議你們參考一下afinal中AsyncTask的實現學習。

    第二點,如何正常退出Thread,我在以前的博文中也提到過。示例代碼以下:

// ref http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
private volatile Thread blinker;
 
public void stop() {
    blinker = null;
}
 
public void run() {
    Thread thisThread = Thread.currentThread();
    while (blinker == thisThread) {
        try {
            thisThread.sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }
}

有人會問,當Activity退出的時候,我還有好多事情要作,怎麼辦?請看上面Handler的分析最後一行。

    (未完待續)

相關文章
相關標籤/搜索