Handler源码详解及导致内存泄漏的分析
[TOC]
简介
android的消息处理有三个核心类:Looper,Handler和Message,
主要接受子线程发送的数据, 并用此数据配合主线程更新UI。
部分图片来至CodingMyWorld博客,3Q
使用方法
public class LooperThread extends Thread { @Override public void run() { // 将当前线程初始化为Looper线程 Looper.prepare(); // ...其他处理,如实例化handler // 开始循环处理消息队列 Looper.loop(); }}
通过上面两行核心代码,你的线程就升级为Looper线程了,就具备消息处理的功能!
Looper.prepare()
通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,以下源码使用到ThreadLoacal,可以想象成一个线程的属性/变量,想了解更多请点击链接
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //获取当前线程对应线程变量:Looper,重复执行此方法会有如下报错提示 // //"Only one Looper may be created per thread" if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //将当前初始化的Looper对象保存到当前线程变量中 sThreadLocal.set(new Looper(quitAllowed)); }
Looper.loop()
调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { //获得当前线程的Looper对象 final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //获得消息队列 final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); //循环消息体 for (;;) { //去出单个消息体 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } //向目标对象中分发当前循环到的消息体,下面将会具体讲解 msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } //使用完消息之后需要对它进行回收 msg.recycleUnchecked(); } }
对msg.target.dispatchMessage(msg)
解释
去Message
类中查找可以发现
/*package*/ Handler target;
其实target就是handler对象,那handler是如何和一个Message发生联系的,稍等?下面移步Handler源码分析
Handler的创建就已经获取了当前线程的Looper和消息队列
public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } //获取同一线程的Looper mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //获取到同一线程的消息队列 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
真正Message和Handler关联的地方来了
在我们sendMessage()
的时候都是先用obtainMessage
来获取一个Message
public final Message obtainMessage() { return Message.obtain(this); }
移步Message的方法看详细
public static Message obtain(Handler h) { Message m = obtain(); //是不是so easy,真正的联系在这里 m.target = h; return m; }
如果你是使用post(Runnable r)
来发送消息的,那应该构造一个Message来发出去,不信可以看源码
Handler类中:
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } /** *构造一个Message */ private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); //与PostMessage不同的是这个runable是给了callback属性 m.callback = r; return m; }
具体消息处理:Handler处理消息
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { //处理Runable消息 handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //处理具体Message,这个是你在消息处理的时候重写的方法实现 handleMessage(msg); } }
总结
- Lopper–消息的集合和消息的循环
- Handler–消息的管理接口和消息的处理
- 主线程已经持有Looper,所以不需要Looper.prepare()
Activity源码查看:
/*package*/ ActivityThread mMainThread;
ActivityThread源码中查看:
final Looper mLooper = Looper.myLooper();
导致内存泄漏的分析
内存泄漏场景
- 在一个activity中post已个message
- 关闭这个activity
- 由于某些原因这个message开始执行或者正在执行(如上一个message比较耗时/当前message比较耗时),
更严重的是你发送一个延时消息前把activity关闭
参考代码
private Handler mLeakHandler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Logger.d(msg.toString()); } }; @Override protected void onResume() { super.onResume(); //延迟10s来模拟场景 mLeakHandler.sendEmptyMessageDelayed(0x1,10000); finish(); } //省略其他代码
分析及修改方法
由于这个Handler作为内部类声明在Activity内部,普通的内部类对象隐式地保存了一个指向外部类对象的引用,所以这个Handler对象保存了一个指向Activity对象的引用。而这个Handler对象的生命周期可能比Activity生命周期长,比如当有一个后台线程持有该Handler,且该线程在执行一个长时间任务。Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity不会被回收。但是注意这个泄漏时临时的!当这个消息处理完引用关系也就不存在了,下次GC的时候也就能回收啦
修改方法:
private static class MyHandler extends Handler { private WeakReference reference; public MyHandler(Activity activity) { reference = new WeakReference(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); LeakActivity activity = (LeakActivity) reference.get(); if (activity != null) { Logger.d("activity != null"+activity.toString()); } else { Logger.d("activity = null"); } } } private final Handler mHandler = new MyHandler(this);
同时你需要在调用一下方法,避免不必要的回调(虽然不会报错了)
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
参考文献:
- http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html
- http://blog.csdn.net/qjyong/article/details/2158097
- http://m.blog.csdn.net/article/details?id=51493352 (代码来源博客)
更多相关文章
- Android主线程、子线程通信(Thread+handler)
- 【翻译】Android多线程下安全访问数据库
- Android子线程真的不能更新UI么
- [Android]Thread线程入门1
- Android保证首次获取到的location对象不为空的解决方案