Android线程通信机制-Handler(Java层)
一、概述
Android的单线程UI模型,决定了在UI线程中不能进行耗时任务,在开发过程中,需要将网络、io等耗时任务放在工作线程中执行,工作线程中执行完成后需要在UI线程中进行刷新,因此就有了Handler进程内线程通信机制,当然Handler并不是只能用在UI线程与工作线程间的切换,Android中任何线程间通信都可以使用Handler机制。
Android的Handler机制应该说是有两套实现,Java层与native层分别实现了Handler机制,也就是说在Java层与native层各自维护了自己的消息队列,native层消息优先于Java层消息处理,在MessageQueue的源码中可以看到很多的native代码。这里只对Java层做个分析。
二、使用Handler实现线程间通信
1、在UI线程中使用Handler
UI线程中使用Handler非常简单,因为框架已经帮我们初始化好了Looper,只需要创建一个Handler对象即可,之后便可直接使用这个Handler实例向UI线程发送消息。
private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // do something } };
注意:这种做法会导致内存泄漏。
我们通过Handler发送消息,在Message对象中会持有当前Handler对象的引用,在Java中非静态成员类、内部类、匿名类会持有外部对象的引用(这里在源码中有提到),而Looper是线程局部变量,其生命周期与UI线程相同,Looper持有MessageQueue的引用,MessageQueue持有Message的引用,当通过Handler发送一个延时消息未处理之前用户已经离开当前Activity,会导致Activity不能及时释放而内存泄漏。
解决思路:
既然知道是因为Handler持有Activity的引用而导致内存泄漏,那便让Activity在结束的时候不再有对象持有当前Activity的引用,或者不再有对象持有该Handler的引用,总之便是将这条引用链切断。
2、在非UI线程中使用Handler
在非UI线程中使用Handler一定要注意必须在创建Handler之前调用Looper.prepare()
方法来初始化Looper,否则会报异常。如果不调用Looper.loop()
方法,线程会在执行完毕后退出,也无法接收到消息。
private Handler handler; Thread threadA = new Thread(new Runnable() { @Override public void run() { // 这一步其实是创建threadA的线程本地变量Looper Looper.prepare(); // 创建Handler handler = new Handler() { @Override public void handleMessage(Message msg) { Bundle bundle = msg.getData(); System.out.println(bundle.getString("msg")); } }; // 让threadA进入Looper循环中,不断的获取消息 Looper.loop(); } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { System.out.println("<<<<<<<<<<<"); // 创建消息 Message msg = Message.obtain(); Bundle bundle = new Bundle(); bundle.putString("msg", "Hello World!"); msg.setData(bundle); // 发送消息 handler.sendMessage(msg); System.out.println(">>>>>>>>>>>"); } });
这里只是为了简单写的示例代码,在实际开发中当然不会这么做,异步任务都会管理起来,不然离开了当前界面还有任务在执行(比如请求数据),那便没有意义了。
三、Handler机制实现原理
1、UML类图
Handler机制主要由Handler、Looper、MessageQueue、Message四个类组成,从下面的UML中可以看到Handler持有一个Lopper实例,这个Looper实例与线程相关,而Looper中管理着一个MessageQueue消息队列,MessageQueue本质上是一个链表。从UML类图大致能看到Handler的一个整体结构。
Handler、Looper、MessageQueue、Message类图2、Handler工作流程
2.1、基本流程
Handler工作流程主要分为两条支线,工作线程(也就是要发送消息的线程,后同)中发送消息实际上是将消息插入到消息队列MessageQueue中,初始化Handler的线程(即接受消息的线程,后同)则通过Looper.loop()
方法进入无限循坏,不断的从消息队列MessageQueue中取出消息,通过Message本身持有的Handler去分发消息。
2.2、线程切换的关键
1、在Looper初始化的时候,其实是在当前线程的本地变量(ThreadLocal)中存储了一个Looper,而Looper.loop()
在进入循环时,便是通过当前线程拿到Looper对象,从而拿到当前线程维持的MessageQueue消息队列,不断的读取消息。至于在另一个线程中通过Handler发送消息简单的说,读消息一直都是在初始化Handler的线程中进行,之后的分发消息与处理消息当然也是了。
2、在工作线程中发送消息,Handler本身持有了一个初始化线程的引用,当发送消息时,其实都是将消息放入初始化线程的消息队列中。
3、源码解析
3.1 Handler源码
从创建Handler开始看,Handler的构造方法有多个重载,最终都会走Handler(Callback callback, boolean async)
或者Handler(Looper looper, Callback callback, boolean async)
这两个构造方法。
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.myLooper()是从线程本地存储中拿到与当前线程相关的Looper mLooper = Looper.myLooper(); // 这里抛出的异常就是我们经常看见的,未再非UI线程中使用Handler却没有调用Looperprepare()初始化Looper 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; }
Handler创建好了,接下来便是使用Handler,也就是通过Handler来发送消息。发送消息的方法主要有两大类,post****
系列发送一个Runnable
,作为处理消息的callback,或者send****
系列方法发送一个Message
,本质上都是构造一个Message
对象,加上消息分发的时间点,最终都会调用sendMessageAtTime(Message msg, long uptimeMillis)
这个方法。
有一个例外的方法,sendMessageAtFrontOfQueue(Message msg)
,默认将消息的执行时间点置为0,也就是立即分发,在入消息队列时会放置在队列头。
/** * @param msg 要发送的消息 * @param uptimeMillis 消息的投递时间 * @return true 消息放入队列成功, false则是失败 */public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; // 这个方法很简单,在这里检查了下MessageQueue是否准备好了 if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } // 从命名可以看出是将消息加入队列 return enqueueMessage(queue, msg, uptimeMillis); }
这个方法只有几行代码,真正入队列还是在MessageQueue中做的,却在入队列之前做了一件很重要的事情。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { // 重要的事情在这,设置了Message的target,这也是后面分发消息的关键 msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } // 调用MessageQueue的成员方法将消息入队列 return queue.enqueueMessage(msg, uptimeMillis); }
Handler
的另一个任务是分发消息并交给合适的方法去处理消息,由Handler.dispatchMessage(Message msg)
这个方法完成。
public void dispatchMessage(Message msg) { if (msg.callback != null) { // 这里的callback就是个Runable对象,会执行其run()方法 handleCallback(msg); } else { if (mCallback != null) { // mCallback是在创建Handler对象时设置的监听 if (mCallback.handleMessage(msg)) { return; } } // 这个方法很熟悉了,就是创建Handler时复写的,也是最常用的 handleMessage(msg); } }
3.2 Looper源码
Looper
的代码非常少,先来看一下初始化方法,有两个重载Looper. prepare()
与Looper. prepare(boolean quitAllowed)
。
public static void prepare() { // 调用的有参的那个构造方法 prepare(true); }
/** * @param quitAllowed 是否允许退出循环 */ private static void prepare(boolean quitAllowed) { // 检查是否已经初始化过了,从里可以看到`Looper.prepare()`在一个线程中只能调用一次 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } // 这里可以看到创建了一个Looper对象,并存入TLS sThreadLocal.set(new Looper(quitAllowed)); }
接下来时Looper
的构造方法,这个构造方法是私有的,也就是说我们在初始化Looper时只能通过Looper.prepare()
这个方法。
private Looper(boolean quitAllowed) { // 创建了消息队列,每个线程只有一个MessageQueue对象 mQueue = new MessageQueue(quitAllowed); // 存储当前线程的引用 mThread = Thread.currentThread(); }
还有个prepareMainLooper()
方法,也是用来初始化Looper
的,这个方法只是为了UI线程使用,UI线程Looper
的初始化是在ActivityThread
的main()
方法中进行的。我们可以通过Looper.getMainLooper().getThread()
来获取UI线程。
接下来是Looper的消息循环方法Looper.loop()
,这个方法去掉一些安全性验证与Log,核心代码也很短。
public static void loop() { // 获取当前线程绑定的Looper final Looper me = myLooper(); // 未初始化Looper if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // 当前线程的消息队列 final MessageQueue queue = me.mQueue; ... for (;;) { // 从消息队列中取出下一个消息,没有则阻塞 Message msg = queue.next(); // might block // 取到null,这是因为MessageQueue已经退出消息循坏 if (msg == null) { return; } // 分发消息,这里的target就是Handler对象,在Handler中消息入队列时设置的 msg.target.dispatchMessage(msg); ... // 将Message放入消息池 msg.recycleUnchecked(); } }
Looper
的任务就是创建一个循环器,不断的从消息队列取消息,交给Handler去分发。
3.3 MessageQueue源码
前面说Handler机制在native层与Java层都有实现,而Handler
与Looper
中都未出现native层的代码,其实是在MessageQueue
中将Java层与native层联系了起来,这里只分析Java层实现,在做应用开发的时候也往往只使用Java层的Handler机制。
首先来看一下MessageQueue
的构造方法,这个构造方法是包级权限,也就是说我们是无法在自定义的类中创建MessageQueue
这个类的实例的。
MessageQueue(boolean quitAllowed) { // 是否允许退出消息循环 mQuitAllowed = quitAllowed; // mPtr是native层消息队列的头指针 mPtr = nativeInit();}
在分析Handler发送消息的时候,可以看到最终都是将消息Message
放入消息队列MessageQueue
中,MessageQueue称为消息队列,其实现的数据结构却并不是真正的队列,而是一个单链表,在插入消息节点Message
时按照时间点来确定位置。看下一下将消息入队列的MessageQueue.enqueueMessage(Message msg, long when)
方法。
boolean enqueueMessage(Message msg, long when) { // 如果没有target,消息无法被处理 if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } // 消息如果已经被使用,那无法再次入队列 if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } // 设置消息使用的标志位 msg.markInUse(); // 设置消息被处理的时间点 msg.when = when; Message p = mMessages; boolean needWake; // 消息队列中无消息,或消息的时间为立即执行,或消息的时间小于队列中第一个消息的时间,则将消息加入到队列头 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { // 根据消息的时间将消息放入队列的合适位置,就是单链表的插入 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { // 唤醒线程 nativeWake(mPtr); } } return true; }
接下来看一下取出消息MessageQueue.next()
方法,这个方法挺长的,而且与native层交互很多。
简单说下大概的逻辑:
- 1.消息循环已经退出,则返回null,在
Looper.loop()
方法结束循环 - 2.阻塞操作,等待nextPollTimeoutMillis到达,或者线程被唤醒未到下一次唤醒时间,则阻塞线程,等待唤醒,在
MessageQueue.enqueueMessage(Message msg, long when)
方法中我们看到了唤醒的操作。 - 3.将取到的消息的时间与当前时间做比较,若还未到处理时间,则设置下一次轮询的超时时间
- 4.取出一条消息返回
- 5.消息队列为空,设置下一次超时时间为-1,会使线程一直阻塞,等待唤醒
Message next() { // step 1 final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // step 2 nativePollOnce(ptr, nextPollTimeoutMillis); // 查找下一条消息,找到则返回 synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // 忽略所有的同步消息 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // step 3 // 下一条消息执行时间还未到,设置一个超时时间 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // step 4 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; // 设置消息使用的标志位 msg.markInUse(); return msg; } } else { // step 5 nextPollTimeoutMillis = -1; } // 执行退出消息,返回null if (mQuitting) { dispose(); return null; } // 消息队列为空或者消息未到执行时间,线程空闲,可以执行IdleHandlers if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // 没有IdleHandlers执行,直接进入下一次循环继续等待消息 mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // 重置IdleHandler,不会被再次执行 pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0; } }
3.4 Message源码
Message
就是消息的实体类,也是消息队列MessageQueue
的节点,除了具有一些消息的基本属性,还持有下一条消息的指针。
Message
的构造方法是空的,其所有属性都是通过set方法来设置。在开发过程中,尽量使用obtain
系列的方法来获取一个消息实例,内部通过消息池来实现,减少因为创建对象而造成的开销,以达到复用的效果。
obtain
方法有许多重载,本质上都是调用无参的Message.obtain()
方法从消息池中取出一个消息实例,设置不同的属性。
public static Message obtain() { // 通过同步sPoolSync对象,将sPool加锁,保证线程安全 synchronized (sPoolSync) { if (sPool != null) { // 消息池不为空,则从消息池中取出一个消息实例 Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // 清楚使用标志位 sPoolSize--; // 消息池数量减1 return m; } } // 如果消息池为空,则创建一个新的消息对象 return new Message(); }
既然有消息池,那么消息池中的消息是哪来的呢,Message
中有个Message.recycle()
方法,这个方法便是将消息放入消息池中。
public void recycle() { if (isInUse()) { // 正在使用的消息无法回收 if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } // 真正的实现回收消息 recycleUnchecked(); }
Message.recycleUnchecked()
多了个uncheck,是真的不检查是否在使用,强行回收。
void recycleUnchecked() { // 设置使用标志位 flags = FLAG_IN_USE; // 清楚Message的所有属性 what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; // 同步 synchronized (sPoolSync) { // 如果消息池未满,将消息放入消息池中 if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
四、结束
大概记录了Handler机制在Java层的一个实现流程,从Handler发送消息,将消息加入到MessageQueue中,Looper不断的循环从MessageQueue中取出消息,将消息交给Handler处理。
更多相关文章
- [原]Android应用程序线程消息循环模型分析
- android系统中的多线程(一): 关于在android中启动线程以及线程间
- Android中消息系统模型和Handler Looper
- Android开发实践:基于命令模式的异步任务线程
- 学习Android线程模型解析(包括UI的更新)