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

来源:互联网 发布:淘宝退款贴吧 编辑:程序博客网 时间:2024/06/11 08:36

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

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

3、简单例子:

public class NewActivity extends Activity {private Handler mHandler = new Handler();private TextView mTextview;@Overrideprotected 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() {@Overridepublic 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,将匿名类转化为静态类

@Overrideprotected 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;}@Overridepublic void run() {mTV.setText("Done");}}

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


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


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

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

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

@Overrideprotected 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);}@Overridepublic 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:

@Overrideprotected 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/

原创粉丝点击