Android中的内部类引起的内存泄露
2015-08-31 11:07
501 查看
引子
什么是内部类?什么是内存泄露?为什么Android的内部类容易引起内存泄露?如何解决?
什么是内部类?
什么是内部类?什么又是外部类、匿名类、局部类、顶层类、嵌套类?大家可以参考我这篇文章 ,再查查一些资料,先弄清楚什么是内部类和内部类的特性再向下看。
经常会遇见Android程序中这样使用handler:
上述代码中,mHandler字段指向一个匿名Handler类。匿名类是内部类吗?匿名类会持有外部类的对象吗? 答案是:匿名类是内部类,但是是特殊的内部类,如果把匿名类放到一个static方法中,它是不会持有外部类实例的。而在上面的代码中,这个mHandler会持有外部类(SomeActivity)实例的引用,因为它处于一个对象的上下文中,而不是类型上下文中。
什么是”持有外部类实例的引用“?你可以这么理解:
就是说,创建InnerClass对象的时,必须传递进去一个OuterClass对象,赋值给InnerClass的一个字段outer,该字段是OuterClass对象的引用。回忆一下GC原理,如果InnerClass对象没有被标记为垃圾对象,那么outer指向的OuterClass对象会可能被标记为垃圾对象吗?答案是:InnerClass对象与GC Root有引用路径,InnerClass对象又引用了OuterClass对象,那么OuterClass对象到GC Root也是有引用路径的,所以,OuterClass不可能是垃圾对象。
为什么发生内存泄露?
由上文可以看出:当mHandler没有被回收时,其外围Activity对象不能被回收。当Activity被用户关闭(finish),而此时mHandler还未被回收,那么Activity对象就不会被回收,造成Activity内存泄露。
问题的关键转入到了这个问题:为什么Activity被finish了,mHandler还不能被回收?
发送消息时,我们使用了这个函数:mHandler.sendEmptyMessage(0)函数。通过查看源码追踪调用关系,发现走到了:
Message对象有个target字段,该字段是Handler类型,引用了当前Handler对象。一句话就是:你通过Handler发往消息队列的Message对象持有了Handler对象的引用。假如Message对象一直在消息队列中未被处理释放掉,你的Handler对象就不会被释放,进而你的Activity也不会被释放。
这种现象很常见,当消息队列中含有大量的Message等待处理,你发的Message需要等几秒才能被处理,而此时你关闭Activity,就会引起内存泄露。如果你经常send一些delay的消息,即使消息队列不繁忙,在delay到达之前关闭Activity也会造成内存泄露。
有什么解决方案?
方案#1:在关闭Activity时(finish/onStop等函数中),取消还在排队的Message:
mHandler.removeCallbacksAndMessages(null);
方案#2:使用WeakReference截断StrongReference。问题的症结既然是内部类持有外部类对象的引用,那我不用内部类就行了,直接使用静态成员类。但mHandler又需要与Activity对象交互,那就来个WeakReference,指向外部Activity对象。
当Activity想关闭销毁时,mHandler对它的弱引用没有影响,该销毁销毁;当mHandler通过WeakReference拿不到Activity对象时,说明Activity已经销毁了,就不用处理了,相当于丢弃了消息。
另外,当你使用Handler有内存泄露时候,Android Studio的Lint会有如下提示:
什么是内部类?什么是内存泄露?为什么Android的内部类容易引起内存泄露?如何解决?
什么是内部类?
什么是内部类?什么又是外部类、匿名类、局部类、顶层类、嵌套类?大家可以参考我这篇文章 ,再查查一些资料,先弄清楚什么是内部类和内部类的特性再向下看。
经常会遇见Android程序中这样使用handler:
public class SomeActivity { // ...... private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what) { case 0: // do something break; case 1: // do something break; default: break; } } }; private void someMethod () { mHandler.sendEmptyMessage(0); } }
上述代码中,mHandler字段指向一个匿名Handler类。匿名类是内部类吗?匿名类会持有外部类的对象吗? 答案是:匿名类是内部类,但是是特殊的内部类,如果把匿名类放到一个static方法中,它是不会持有外部类实例的。而在上面的代码中,这个mHandler会持有外部类(SomeActivity)实例的引用,因为它处于一个对象的上下文中,而不是类型上下文中。
什么是”持有外部类实例的引用“?你可以这么理解:
public class InnerClass { private OuterClass outer; public InnerClass(OuterClass outer) { this.outer = outer; } }
就是说,创建InnerClass对象的时,必须传递进去一个OuterClass对象,赋值给InnerClass的一个字段outer,该字段是OuterClass对象的引用。回忆一下GC原理,如果InnerClass对象没有被标记为垃圾对象,那么outer指向的OuterClass对象会可能被标记为垃圾对象吗?答案是:InnerClass对象与GC Root有引用路径,InnerClass对象又引用了OuterClass对象,那么OuterClass对象到GC Root也是有引用路径的,所以,OuterClass不可能是垃圾对象。
为什么发生内存泄露?
由上文可以看出:当mHandler没有被回收时,其外围Activity对象不能被回收。当Activity被用户关闭(finish),而此时mHandler还未被回收,那么Activity对象就不会被回收,造成Activity内存泄露。
问题的关键转入到了这个问题:为什么Activity被finish了,mHandler还不能被回收?
发送消息时,我们使用了这个函数:mHandler.sendEmptyMessage(0)函数。通过查看源码追踪调用关系,发现走到了:
Message对象有个target字段,该字段是Handler类型,引用了当前Handler对象。一句话就是:你通过Handler发往消息队列的Message对象持有了Handler对象的引用。假如Message对象一直在消息队列中未被处理释放掉,你的Handler对象就不会被释放,进而你的Activity也不会被释放。
这种现象很常见,当消息队列中含有大量的Message等待处理,你发的Message需要等几秒才能被处理,而此时你关闭Activity,就会引起内存泄露。如果你经常send一些delay的消息,即使消息队列不繁忙,在delay到达之前关闭Activity也会造成内存泄露。
有什么解决方案?
方案#1:在关闭Activity时(finish/onStop等函数中),取消还在排队的Message:
mHandler.removeCallbacksAndMessages(null);
方案#2:使用WeakReference截断StrongReference。问题的症结既然是内部类持有外部类对象的引用,那我不用内部类就行了,直接使用静态成员类。但mHandler又需要与Activity对象交互,那就来个WeakReference,指向外部Activity对象。
public class SomeActivity { private Handler mHandler = new MyHandler(this); private static class MyHandler extends Handler { private WeakReference<SomeActivity> ref; public MyHandler(SomeActivity activity) { if (activity != null) { ref = new WeakReference<SomeActivity>(activity); } } @Override public void handleMessage(Message msg) { if (ref == null) { return; } SomeActivity v = ref.get(); if (v == null) { return; } // handle message } } }
当Activity想关闭销毁时,mHandler对它的弱引用没有影响,该销毁销毁;当mHandler通过WeakReference拿不到Activity对象时,说明Activity已经销毁了,就不用处理了,相当于丢弃了消息。
另外,当你使用Handler有内存泄露时候,Android Studio的Lint会有如下提示:
相关文章推荐
- android:windowSoftInputMode属性详解
- Android中获取字符串长度、宽度
- Android 延迟执行
- Android Gif图片显示(GifView)
- Android中级篇之百度地图SDK v3.5.0-基础地图[图解百度地图基础教程]
- android 代码中设置字体大小
- Android编程规范
- Android上常见度量单位【xdpi、hdpi、mdpi、ldpi】解读
- (4.1.35)Android捕获全局异常信息并实现上传
- android 设置一体化状态栏
- android开发拍照点击保存无法返回上一界面
- Android 上传图片到服务器时将bitmap转换为byte[]最后转换为String
- Android笔记(十二)Toast和Dialog
- Android仿QQ聊天记录待定与删除功能效果
- android SQLite
- Buttons in button bars should be borderless; use style="?android:attr/buttonBarButtonStyle" (and ?an
- android :gravity 与android: layout_gravity的区别
- Android怎么通过代码判断是否权限被禁止
- android 设置继承FragmentStatePagerAdapter(里面之间的传值)
- android 图片浏览功能 图片放大缩小 使用 photoview 双击或双指缩放的ImageView