Handler使用引起的内存泄漏原因以及解决办法
2016-10-28 11:38
621 查看
问题来源
在之前我的一篇文章中我就指出了我在项目中如何会出现 内存泄漏的问题的,有兴趣的小伙伴可以点击获取原文看看这个问题出现的来龙去脉,在这里我就不过多做说明了。下面我们用最简单直接粗暴的方式进入主题。在我们开发android应用程序的时候,通常会写出如下代码
private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg){ //do something } };
其实上面的代码是会产生内存泄漏的,如果你有使用Android lint工具的话,它会给我们提示一个警告
In Android, Handler classes should be static or leaks might occur。
翻译过来就是:在android中,Handler这个类应该被定义成静态的,否则可能出现内存泄漏的情况
发生内存泄漏的原因
说的挺吓人的,就一个Handler处理消息事件而已,怎么可能会出现内存泄漏的情况呢?说是内存泄漏,那到底如何发生内存泄漏的呢?又在哪里发生的内存泄漏?接下来我们来一起探究一下到底是如何发生内存泄漏的。当一个android应用程序启动的时候,frameworks会自动为这个应用程序在主线程创建一个Looper对象。这个被创建的Looper对象也有它主要的工作,它主要的工作就是不断地处理消息队列中的消息对象。在android应用程序中,所有主要的框架事件(例如Activity的生命周期方法,按钮的点击事件等等)都包含在消息对象里面,然后被添加到Looper要处理的消息队列中,主线程的Looper一直存在于整个应用程序的生命周期中。
当一个Handler在主线程中被初始化。那它就一直都和Looper的消息队列相关联着。当消息被发送到Looper关联的消息队列的时候,会持有一个Handler的引用,以便于当Looper处理消息的时候,框架可以调用Handler的handleMessage(Message msg)。
在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。静态内部类则不会持有外部类的引用。
的的确确,上面的代码确实是很难以发现内存泄漏的问题的。那我们来看看下面的代码,会更加容易发现问题。
public class MainActivity extends AppCompatActivity { private Handler mLeakHandler = new Handler(){ @Override public void handleMessage(Message msg) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //发送延时消息 mLeakHandler.postDelayed(new Runnable() { @Override public void run() { } }, 1000 *60 *10); finish(); } }
当Activity被finished掉的时候,被延时的消息会在被处理之前存在于主线程的消息队列中十分钟,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的MainActivity的引用。这些引用会一直保持到该消息被处理,从而阻止了MainActivity被垃圾回收器回收。因此这就导致了MainActivity无法被回收,进而导致MainActivity持有的很多资源都无法回收,这就是我们常说的内存泄漏。
解决办法
要解决这样的一个问题,有如下几种方式:最直接的思路就是避免使用非静态内部类。使用Handler的时候,放在一个新建的文件中来继承Handler或者使用静态的内部类来替代。静态内部类不会隐含的持有外部类的引用,因此这个activity也就不会出现内存泄漏问题。
如果你需要在Handler内部调用外部Activity的方法,你可以让这个Handler持有这个Activity的弱引用,这样便不会出现内存泄漏的问题了。
另外,对于匿名类Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。
注意:如果使用Handler发送循环消息,最好是在Activity的OnDestroy方法中调用mLeakHandler.removeCallbacksAndMessages(null);移除消息。(这不是解决内存泄漏的方法)
两种解决办法如下:
弱引用(WeakReference)
public class SampleActivity extends Activity { /** * Instances of static inner classes do not hold an implicit * reference to their outer class. * 弱引用的方式 */ private static class MyHandler extends Handler { private final WeakReference<SampleActivity> mActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference<SampleActivity>(activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { //to Something } } }
静态
//定义成static的,因为静态内部类不会持有外部类的引用 private final MyHandler mHandler = new MyHandler(this); private static final Runnable sRunnable = new Runnable() { @Override public void run() {//to something} }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler.postDelayed(sRunnable, 1000 * 60 * 10); finish(); } }
总结
其实android中出现的大部分的内存泄漏都是Activity使用非静态内部类导致的,所以我们在使用内部类的时候要格外注意,如果其持有的引用超过了生命周期的范围,就极有可能会出现内存泄漏。以上的几种方式,由你们个人喜好来决定。如果有同学对弱引用不太了解的话,可以看看我写的这篇Java中关于引用的文章。相关文章推荐
- 关于装了VMware之后VS2005无法使用的原因以及解决办法
- handlder引起的内存泄漏问题以及解决办法
- 把全局变量定义在.h文件中的引起连接错误的原因以及解决办法
- ASInsHelp 服务加载失败的可能原因以及解决办法,希望对大家有帮助.
- FOXPRO 程序在Winxp下面 insufficient memory 问题出现的原因以及解决办法
- min-height 属性的使用以及IE6.0不兼容问题的解决办法
- ORA-01795异常的原因以及解决办法
- 错误“操作必须使用一个可更新的查询”原因及解决办法
- FC6 中串口不能正常使用的原因与解决办法
- Hibernate数据的lazy问题,以及使用spring整合hibernate lazy的解决办法(好文转载)
- 关于ADO.Net使用TableAdapter时产生:更新要求有效的deletecommand或update,insert,selectcommand的解决办法,以及“违反并发性”处理
- windows xp sp3(XP3)无法安装或使用iis的根本原因及解决办法研究
- 错误“操作必须使用一个可更新的查询”原因及解决办法
- 错误“操作必须使用一个可更新的查询”原因及解决办法
- 白苹果问题解决办法以及原因简单分析
- 使用链接服务器无法使用transaction查询的错误原因和解决办法。
- 错误“操作必须使用一个可更新的查询”原因及解决办法
- 使用IntrospectorCleanupListener 解决quartz引起的内存泄漏
- ORA-01795异常的原因以及解决办法
- asp+access中 打开网页时出现“操作必须使用一个可更新的查询”原因及解决办法