转载请标明出处:http://blog.csdn.net/xx326664162/article/details/50084373 文章出自:薛瑄的博客
你也可以查看我的其他同类文章,也会让你有一定的收货!
示例代码
public class NonStaticNestedClassLeakActivity extends ActionBarActivity {
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_non_static_nested_class_leak);
textView = (TextView)findViewById(R.id.textview);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override public void
textView.setText("Done");
}//a mock for long time work
}, 800000L);
}
}
打开MAT,进行分析
非静态内部类惹的祸
MAT是对java heap中变量分析的一个工具,它可以用于分析内存泄露。
- 点击OQL图标
- 在窗口输入select * from instanceof android.app.Activity并按Ctrl + F5或者!按钮(这个操作和在这篇文章的第三步操作的结果是一样)
- 奇迹出现了,现在你发现泄露了许多的activity
我们来分析一下为什么GC没有回收它
图一
在OQL(Object Query Language)窗口下输入的查询命令可以获得所有在内存中的Activities
点击一个activity对象,右键选中Path to GC roots
图二
注意:这里红色方框中左下方的黄色小点,代表有GC Root
在打开的新窗口中,可以发现三个GC Root,
第一个是弱引用可以选中在左边的信息中查看
第二个GC Root 消息队列:
一旦你把Runnable或者Message发送到Handler中,它就会被放入LooperThread的消息队列,并且被保持引用,直到Message被处理。发送postDelayed这样的消息,延迟多少秒,它就会泄露至少多少秒。而发送没有延迟的消息的话,当队列中的消息过多时,也会造成临时的泄露。
第三个GC Root 主线程:
- Activity是被this$0 所引用的,它实际上是匿名类对当前类的引用。
- this$0引用callback
- 接着callback又被Message中一串的next所引用,最后到主线程(< java local>)才结束。
任何情况下你在class中创建非静态内部类,内部类会(自动)拥有对当前类的一个强引用。
后两个GC Root都是由postDelayed()发送消息延迟造成的
解决方法:
1、尝试使用static inner class来解决
现在把Runnable变成静态的class
public class MainActivity extends AppCompatActivity {
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
Handler handler = new Handler();
handler.postDelayed(new DoRunable(textView),900000);
}
private static final class DoRunable implements Runnable {
private TextView textView;
public DoRunable(TextView textView) {
this.textView = textView;
}
@Override
public void run() {
textView.setText("Done");
}
}
}
摇一摇手机,在Android studio中触发垃圾回收,导出内存
下面三幅图中,请注意红色方框中的内容,这是在图一的查询结果上,右键选择菜单项的结果,比如:图二中的选择,跳出新的界面,将鼠标放到标题栏上,将会处在这个小提示(标题栏的完整描述)
选择Path to GC Roots ->With all references
选择Merge Shortest Paths to GC Roots. -> exclude all weak/soft references
选择Merge Shortest Paths to GC Roots. ->With all references
比较三幅图发现,结果都不一样,原因如下:
Tables | Are |
---|---|
Path to GC Roots-with all references | 从GC Roots节点到该对象的引用路径,包含所有引用类型 |
Merge Shortest Paths to GC Roots -> exclude all weak/soft references | 从该对象到GC Roots节点的最短引用路径,去除所有弱引用,其他类似 |
Merge Shortest Paths to GC Roots-with all references | 从该对象到GC Roots节点最短引用路径,包含所有引用类型 |
图一是所有的引用
图二是从该对象到GC Roots节点的最短引用路径,但是过滤掉弱引用和软引用,因为图三所示的引用属于弱引用,所有被过滤掉
图三是从该对象到GC Roots节点的最短引用路径,不过滤任何引用,在这里显示所有引用中,路径最短的一个,所以显示图中的情形
按道理说,在导出内存前,手动触发垃圾回收,应该不会有弱引用,但不知道为何这里依旧存在,估计是要多次触发。
在图二中,发现对象被textView引用了mContext,该引用是强引用,所以不能回收。
原作者的图,是选择了Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references
2、使用弱引用 + static Runnable
现在我们把刚刚内存泄露的罪魁祸首 - TextView改成弱引用。
再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机,不手动触发GC
再次注意我们对TextView保持的是弱引用,现在让它运行,摇晃手机,手动触发GC
使用Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references,查看这两个对象
但为什么还是有两个实例,我也不明白,求高人指教
我们应该记住:
- 使用静态内部类
- Handler/Runnable的依赖要使用弱引用。
当把postDelayed设置为一个短时间,比如50ms的情况下,写这么多代码就有点亏了。还有一个更简单的方法,如下。
3、onDestroy中手动控制声明周期
Handler可以使用removeCallbacksAndMessages(null),它将移除这个Handler所拥有的Runnable与Message。
在第二种的基础上,加入下面代码:
//Fixed by manually control lifecycle
@Override protected void onDestroy() {
super.onDestroy();
myHandler.removeCallbacksAndMessages(null);
}
横竖摇动手机,没有手动触发GC机制,依然是会存在很多实例,但是可以被GC回收,说明不是泄露
横竖摇动手机,手动触发GC机制,导出内存
这里得到的结果,依然是两个实例,原作者的结果是一个实例
使用Merge Shortest Paths to GC Roots ->exclude all phantim/weak/soft etc. references,查看这两个对象
这两个实例不是自己写的代码,所导致的泄露,所以该方法可行。
看到这两个实例,都是对系统view的引用,至于为什么会出现这样,请高人指点一下
结论
在Handler中使用postDelayed需要额外的注意,为了解决问题,我们有三种方法
- 使用静态内部Handler/Runnable + 弱引用
- 在onDestory的时候,手动清除Message
- 使用Badoo开发的第三方的 WeakHandler
这三种你可以任意选用,第二种看起来更加合理,但是需要额外的工作。第三种方法是我最喜欢的,当然你也要注意WeakHandler不能与外部的强引用共同使用。
内存泄露的8种情况:https://www.jianshu.com/p/ac00e370f83d
以及对应的解决方法:https://www.jianshu.com/p/c5ac51d804fa
转载:http://www.jianshu.com/p/c49f778e7acf
关注我的公众号,轻松了解和学习更多技术