您的位置:首页 > 移动开发 > Android开发

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

2017-07-14 15:40 435 查看
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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android