Android 从java字节码告诉你 为什么Handler会造成内存泄露
来源:互联网 发布:淘宝新政策 编辑:程序博客网 时间:2024/05/17 02:52
http://www.cnblogs.com/punkisnotdead/p/4943260.html
很多人面试的时候,都知道Handler 极易造成内存泄露,但是有一些讲不出来为什么,好一点的 会告诉你looper msg 之类的,但是你再往下问 为什么msg持有handler handler为什么
持有activity'的引用的时候 他们就答不出来了。这里我通过几个简单的例子 和极少部分的源码 来帮助大家彻底理解这一个流程。
那首先 我们来看一个例子,首先定义1个外部类 一个内部类:
1 package com.test.zj; 2 3 public class OuterClass { 4 5 private int outerValue = 7; 6 private String outerName = "outer"; 7 8 class InnerClass { 9 public void printOuterValue() {10 System.out.println(outerName + ": " + outerValue);11 }12 }13 14 }
然后看一下我们的主类:
1 package com.test.zj; 2 3 public class MainClass { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 OuterClass outerClass = new OuterClass(); 8 OuterClass.InnerClass innerClass = outerClass.new InnerClass(); 9 innerClass.printOuterValue();10 }11 12 }
这个例子相信经常写android 代码的人是不会陌生的。经常会写类似的代码。那这里有没有人思考过 Outer的那2个属性 不是private的吗,为什么内部类能直接用他们呢?
看一下字节码,首先我们看Outer的:
所以你看这里,大家一定很奇怪,我的outer 明明只有一个构造方法啊,这里怎么多了一个access0 access1 这是什么鬼。但是继续看 发现这2个方法 一个返回string 一个返回I 也就是int,似乎我们又明白了点什么
好继续看我们的内部类:
注意看内部类的print方法里的字节码,重要的地方我标红了,你看,原来outer里的那2个access方法是在这里被调用的。再看那个 this$0 看下冒号后面的内容 就能明白
这个 this$0就是指向外部类的指针啊! 所以 一个大家熟悉的概念 原理就在这了:内部类 持有外部类的引用。
然后有人又会说了,静态内部类不会持有外部类的引用啊。好,我们现在修改一下代码 看看是否是如此:
1 package com.test.zj; 2 3 public class OuterClass { 4 5 private static int outerValue = 7; 6 private static String outerName = "outer"; 7 8 static class InnerClass { 9 public void printOuterValue() {10 System.out.println(outerName + ": " + outerValue);11 }12 }13 14 }
1 package com.test.zj; 2 3 import com.test.zj.OuterClass.InnerClass; 4 5 public class MainClass { 6 7 public static void main(String[] args) { 8 // TODO Auto-generated method stub 9 InnerClass innerClass = new InnerClass();10 innerClass.printOuterValue();11 }12 13 }
然后看下 字节码:
然后看下内部类的字节码:
你看很明显的 我们就能看到 在内部类的printf方法里 再调用外部类的属性的时候 就看不到 this0 这个指向外部类的指针了。
回到我们的handler ,我们这个时候 就能清晰的分析出 为什么handler 有的时候会造成内存泄露了。
1 public class MainActivity extends MainActivity{ 2 3 private Handler mHandler=new Handler() 4 { 5 public void handleMessage(Message msg) 6 { 7 8 } 9 }10 11 protected void onCreate(Bundle saveInstance)12 {13 super.onCreate(saveInstance)14 15 mHandler.postDelayed(new Runnable() {16 @Override17 public void run() { /* ... */ }18 19 }, 1000 * 60 * 5);20 21 finish();22 23 }24 25 26 27 }
我们来看看 这段代码为什么会造成内存泄露。
首先 我们得明白一点,当一个app被启动的时候,android 会帮我们创建一个供ui线程使用的消息队列Looper。这个Looper就是用来处理ui线程上的事件的,
比如什么点击事件啊,或者是我们android里面生命周期的方法啊 之类的。Looper是一条一条处理的。可不是一次性处理多条哦。可以看一下大概的源码:
1 public static void loop() { 2 final Looper me = myLooper(); 3 if (me == null) { 4 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 5 } 6 final MessageQueue queue = me.mQueue; 7 8 // Make sure the identity of this thread is that of the local process, 9 // and keep track of what that identity token actually is.10 Binder.clearCallingIdentity();11 final long ident = Binder.clearCallingIdentity();12 13 for (;;) {14 Message msg = queue.next(); // might block15 if (msg == null) {16 // No message indicates that the message queue is quitting.17 return;18 }19 20 // This must be in a local variable, in case a UI event sets the logger21 Printer logging = me.mLogging;22 if (logging != null) {23 logging.println(">>>>> Dispatching to " + msg.target + " " +24 msg.callback + ": " + msg.what);25 }26 27 msg.target.dispatchMessage(msg);28 29 if (logging != null) {30 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);31 }32 33 // Make sure that during the course of dispatching the34 // identity of the thread wasn't corrupted.35 final long newIdent = Binder.clearCallingIdentity();36 if (ident != newIdent) {37 Log.wtf(TAG, "Thread identity changed from 0x"38 + Long.toHexString(ident) + " to 0x"39 + Long.toHexString(newIdent) + " while dispatching to "40 + msg.target.getClass().getName() + " "41 + msg.callback + " what=" + msg.what);42 }43 44 msg.recycleUnchecked();45 }46 }
一目了然 是一个循环,无限制的永远取messagequee对吧,取出来的是什么呢,废话当然是message。看27行。message对象里面有个target。
查看message源码得知:
1 /*package*/ Handler target;
所谓target就是一个handler对吧。那么问题就来了,我们上面的sample代码里面 我们这个handler对象是什么啊?是一个内部类构造的对象。
这个内部类构造的对象持有了外部类Activity的引用!所以导致 activity 无法被真正释放掉。同样的那个runnable对象实际上也是一个内部类对象,
他也会持有activity的引用了。
内存泄露就是在这里发生的。
当然了,更改的方法也很简单,那就是直接把这个内部类 改成静态的不就行了!
1 private static class MyHandler extends Handler2 {3 @Override4 public void handleMessage(Message msg) {5 super.handleMessage(msg);6 }7 }8 9 private MyHandler myHandler=new MyHandler();
那,又有人要问了,你这个静态类,不讲道理啊,我要引用activity的属性 比如textview 啥的,引用不了啊,总不能textview 还让我弄成static变量把,
那其实这边还是有解决方法的。
1 private TextView tv; 2 private static class MyHandler extends Handler 3 { 4 private final WeakReference<MainActivity> mActivity; 5 @Override 6 public void handleMessage(Message msg) { 7 8 MainActivity activity=mActivity.get(); 9 if (null!=activity)10 {11 activity.tv.setTag("123");12 super.handleMessage(msg);13 }14 }15 16 public MyHandler(MainActivity mainActivity)17 {18 this.mActivity=new WeakReference<MainActivity>(mainActivity);19 }20 }21 22 private MyHandler myHandler=new MyHandler(MainActivity.this);
为什么要用弱引用,我解释一下,当我们activity被弹出栈以后,此时就没有强引用去指向这个activity对象了。
如果发生gc,这个activity就会被回收,activity持有的那些资源 也自然而然就烟消云散了。对于dalivk虚拟机来说
第一次gc 的时候 是会把 没有任何引用的对象 和 只有弱引用的对象全部回收掉的,只有当发现这2种对象全部回收掉以后
所剩下的内存依然不够,那此时就会再进行一次gc,这时候gc 会把软引用指向的对象也回收掉。所以这里用弱引用
是最合适的。
你看 如果handler不做这种处理的话,我们gc的时候,一看,诶,怎么还有一个对象(handler的对象)持有activity的引用,恩
还是不销毁了。。。所以就内存泄露了。
- Android 从java字节码告诉你 为什么Handler会造成内存泄露
- Android中使用Handler造成内存泄露
- Android中使用Handler造成内存泄露
- Android handler 可能会造成内存泄露
- Android中Handler造成内存泄露解决方法
- Handler造成内存泄露解决办法
- Android使用Handler造成的内存泄露问题的解决
- Android Handler造成内存泄露的分析和解决
- Android使用Handler造成内存泄露的分析及解决方法
- Android使用Handler造成内存泄露的分析及解决方法
- Android中使用Handler造成内存泄露的分析总结
- Android使用Handler造成内存泄露的分析及解决方法
- Android使用Handler造成内存泄露的分析及解决方法
- 安卓Handler造成内存泄露问题
- android handler 内存泄露
- Android Handler内存泄露
- Android Handler 内存泄露
- android内存泄露:handler
- ExecuteThread: '33' for queue: 'weblogic.kernel.Default (self-tuning)'] ERROR
- Libev源码分析07:Linux下的eventfd简介
- Spring测试框架JUnit4.4
- LightOJ - 1301 Monitoring Processes(树状数组)
- Android Studio导入第三方类库的方法
- Android 从java字节码告诉你 为什么Handler会造成内存泄露
- Verilog之function使用说明
- 2、oracle专家编程---SQLPLUS设置
- 爬爬爬之路:OC语言(九) 内存管理(初级)
- 我第二次用SecureCRT时的糗事
- LightOJ - 1082 Array Queries(RMQ)
- 网络协议层学习笔记
- cocos2d-x设计模式发掘之六:观察者模式
- React Native Windows 安装方法