Android Handler造成内存泄露的分析和解决

首先回顾下GC回收机制 :
Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

Handler造成内存泄漏的原因(如果对Handle机制不太了解的,建议先查看上篇写的Handler机制):

当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。

解决方案:

我们可以通过将Handler设置为static,静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。

具体代码如下:

package com.handler;

import java.io.InputStream;
import java.lang.ref.WeakReference;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.widget.ImageView;

/**
 * 使用Handler造成内存泄露的分析和解决
 * 
 * @author zhongyao
 * 
 */
public class MainActivity extends ActionBarActivity {

	private ImageView iv;
	private final static int SUCCESS = 1;
	private String imgUrl = "http://m3.biz.itc.cn/pic/new/n/54/56/Img5435654_n.jpg";

	static class MyHandler extends Handler {
		private final WeakReference<Activity> mActivityReference;

		MyHandler(Activity activity) {
			mActivityReference = new WeakReference<Activity>(activity);
		}

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			final MainActivity activity = (MainActivity) mActivityReference.get();
			if (activity != null) {
				switch (msg.what) {
				case SUCCESS:
					Bitmap bitmap = (Bitmap) msg.obj;
					activity.iv.setImageBitmap(bitmap);
					break;

				default:
					break;
				}
			}
		}
	}

	private final MyHandler handler = new MyHandler(this);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		iv = (ImageView) findViewById(R.id.iv);

		new Thread(new Runnable() {

			@Override
			public void run() {
				doTask();
			}
		}).start();

	}

	protected void doTask() {
		try {
			HttpGet get = new HttpGet(imgUrl);
			HttpClient client = new DefaultHttpClient();
			HttpResponse response = client.execute(get);
			if (response.getStatusLine().getStatusCode() == 200) {
				InputStream inputStream = response.getEntity().getContent();
				if (inputStream != null) {
					final Bitmap bitmap = BitmapFactory
							.decodeStream(inputStream);

					Message msg = new Message();
					msg.obj = bitmap;
					msg.what = SUCCESS;
					handler.sendMessage(msg);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

这样我们就解决了Handler内存泄漏的问题。欢迎讨论...

补充另一种较好的方法:

private Handler mHandle = new Handler();
private Runnable mRunnable = new Runnable() {
    @Override
    public void run() {
        if (!isFinishing()) {
            finish();
        }
    }
};
 
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    if (mHandle != null && mRunnable != null) {
        mHandle.postDelayed(mRunnable, 500);
      } 
 
@Override
protected void onDestroy() {
    super.onDestroy();
    if (mHandle != null && mRunnable != null) {
        mHandle.removeCallbacks(mRunnable);
        //或者
        //mHandle.removeCallbacksAndMessages(null);
    }
    ShareLogger.logD("WeiboShareCallbackActivity-->onDestroy");
}

意思就是说使用完handler的时候,在onDestroy方法中移除所有的消息和回调,即清空该消息队列。

发布了95 篇原创文章 · 获赞 195 · 访问量 26万+

猜你喜欢

转载自blog.csdn.net/u012440207/article/details/51195064
今日推荐