有关Android Handler内存泄漏分析及解决办法

1、Android的开发工具是java,这能帮助我们解决很底层的问题 包括:内存管理,平台依赖。然而,有时候项目依然会报OOM错误,so垃圾收集器在哪?

2、我主要研究一种情况:内存中较大对象很长一段时间内不能被释放。这方面并不完全算作内存溢出,对象会在某一时间点上被收集,so我们不屌它。虽然有时候他也会导致oom,所以不建议这么干滴。(这话咋说的这么矛盾,作者精分了?)

3、简单例子:

public class NewActivity extends Activity {
	private Handler mHandler = new Handler();
	private TextView mTextview;

	@Override
	protected void onCreate(android.os.Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.inflate_textview);
		
		mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
		mHandler.postDelayed(new Runnable() {
			@Override
			public void run() {
				mTextview.setText("Done");
			}
		}, 80000);
	};
}

4、这是一个非常基本的Activity,注意到匿名类Runnable被handler延迟执行了好多次,我们运行并且旋转屏幕多次,然后dump memory 分析它


5、现在内存里有很多Activity,这tm很不好啊,为啥gc没收了他们。

6、获取所有内存的Activity查询语句是在OQL(Object Query Language)创建的,非常简单粗暴。


7、能够看出,其中一个Activity是被this$0引用了,这是一个来自内部匿名类指向其主类的间接引用(OMG) this$0是被callback方法引用的,callback又被一堆next()方法引用到主线程。

8、任何时候你创建一个非静态内部类在你自己的类中,java会自动为其创建一个间接引用指向其主类。

9、只要你的handler调用了Runnable或Message,它将被存储在LoopThread所引用的Message消息队列中,直到Message被执行。发送delayed消息是一个明显的内存泄漏,泄漏的时间大于等于延迟的时间。如果消息队列很长的话发送非延迟消息也可能引发一个临时泄漏。

10、解决方案1:Static Runnable

让我们试着克服这个把我们弄疯了的this$0,将匿名类转化为静态类

@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.inflate_textview);
	
	mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
	mHandler.postDelayed(new DoneRunnable(mTextview), 80000);
};

private static final class DoneRunnable implements Runnable{
	private final TextView mTV;
	public DoneRunnable(TextView tv) {
		this.mTV = tv;
	}
		
	@Override
	public void run() {
		mTV.setText("Done");
	}
	
}

11、运行、旋转屏幕、释放内存堆:


12、啥?还tm这样,看看谁持有了Activities的引用


13、看这颗树的最底部,activity被DoneRunnable中的mTextView类的mContext引用,用静态内部类没办法解决内存泄漏,需要来点更狠的

14、解决方案2:弱引用的Static Runnable

我们继续用之前的修复办法并且弱引组织Activity被释放的TextView

@Override
protected void onCreate(android.os.Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.inflate_textview);
	
	mTextview = (TextView) findViewById(R.id.xxwj_newtv4);
	mHandler.postDelayed(new DoneRunnable(mTextview), 80000);
};
	
private static final class DoneRunnable implements Runnable{
	private final WeakReference<TextView> mTV;
	public DoneRunnable(TextView tv) {
		this.mTV = new WeakReference<TextView>(tv);
	}
	
	@Override
	public void run() {
		final TextView tt = mTV.get();
		if(tt != null){
			tt.setText("Done");
		}
	}
	
}

15、重新运行,旋转,释放内存:


16、woops!只有一个Activity实例,解决了我们的内存泄漏问题

17、使用弱引用时要小心谨慎,他们能在任何时候被置为空,解决办法就是先赋值给一个局部变量(强引用),然后在使用前检查是否为空

18、解决handler内存泄漏我们需要做的:

1、用静态内部类或外部类

2、操纵Handler/Runnable时用弱引用

19、如果和一开始的代码进行对比,会发现在可读性和清晰度方面有很大的不同。一开始的代码很短和清晰,这么写太麻烦

20、解决方案3:在onDestroy中清空所有Message

Handler类中有个牛逼方法removeCallbacksAndMessages(),它可以接受空字段作为参数,它将移除所有特定Handler的Runnable和Message:

@Override
protected void onDestroy() {
	mHandler.removeCallbacksAndMessages(null);
	super.onDestroy();
}


21、完美!这比上一个解决办法要好很多。唯一难搞的是你需要在所有的Activity/Fragment中调用onDestory()方法来清除message,听着都恶心

22、解决方案4:用WeakHandler

隆重介绍一下Badoo团队整的WeakHeadler,Handler的替代方案,安全多了。


23、代码很像吧,经实测内存中也只有一个实例,简单吧,代码和内存都一样简洁。使用方法gradle一下即可

24、总结:

本文一共介绍了3种方案:

1、声明一个继承Runnable类的静态内部类,并弱引用要操作的控件

2、在onDestroy()中调用removeCallbacksAndMessages()

3、调用Badoo团队的WeakHandler来替代os.Handler

666、WeakHandler只能替代postDelay(New Runnable())方法造成的内存泄漏,没办法替代os.Handler中接收消息的操作,所以正常情况下需要调用方法(2)


原文地址:https://techblog.badoo.com/blog/2014/08/28/android-handler-memory-leaks/

猜你喜欢

转载自blog.csdn.net/jbb0403/article/details/75109890
今日推荐