Android学习系列(36)--App调试内存泄露之Context篇(上)

Context做为最基本的上下文,承载着Activity,Service等最基本组件。当有对象引用到Activity,并不能被回收释放,必将形成大范围的对象没法被回收释放,进而形成内存泄漏。html

下面针对一些经常使用场景逐一分析。java

1. CallBack对象的引用android

    先看一段代码: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);
}

    有问题吗?学习

    哈哈,先Hold住一下,先来讲一下android各版本发布的历史:fetch

/*
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代码的。ui

    好,开始分析代码。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的分析最后一行。

    (未完待续)

相关文章
相关标签/搜索