从源码的角度分析Handler
对于Java开发人员来说多线程和线程间通信是非常重要也是必须要掌握的不可或缺的知识。Android是基于Java语言进行开发的,所以对于Android开发者而言多线程和线程间通信也同样显得极其重要,但是作为Android开发者在开发过程中好像并没有经常的深层次的接触多线程和线程池。这是因为在Android中线程间通信这一重要的知识点已经被Google开发者封装到了Handler中,Handler已经替我们实现了线程间的通信。开发者只需要调用handler的sendMessage就可以轻松的实现线程间通信。由此可看的出,掌握Handler的使用方式以及其实现原理就显得尤为重要。因此这篇文章就诞生了。
在Android开发过程中,我们经常将一些耗时的操作(例如:网络请求,数据库查询等)放入到子线程中去实现,然后将执行的结果传递给主线程,再由主线程进行更新UI的操作。这一过程就涉及到了线程间的通信,我们常用的方式就行使用handler.sendMessage将子线程的执行结果通知到主线程。其实Handler不仅仅可以在子线程与主线程之间进行通信,它还能实现任意两个线程之间的数据传递。至于Handler在子线程与主线程之间消息传递的实现方式大家用的很多,基本上是由handler调用sendMessage方法将要传递的数据Message传递到主线程。此处不再详细介绍。
现在从handler.sendMessage方法开始,分析Handler的源码。代码如下:
public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0);}
我们知道,实现线程间通信除了可以调用sendMessage方法外,还可以调用post方法,我们不妨看一下post方法源码实现:
public final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);}
我们发现,不论是sendMessage方法还是post方法,其中都时调用了sendMessageDelayed方法。所以无论哪种用法在源码层面调用的方法都是一样的。所以,我们可以直接分析sendMessageDelayed方法的原码。代码如下:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}
我们看到,sendMessageDelayed方法中返回了sendMessageAtTime方法。这一方法的含义是在指定的时间将所有之前未发送的消息添加到消息队列中。我们接着看一下sendMessageAtTime方法中做了什么操作?代码如下:
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } // 调用enqueueMessage将消息入队到消息队列中 return enqueueMessage(queue, msg, uptimeMillis);}
sendMessageAtTime方法最后调用了enqueueMessage方法。熟悉队列这一数据结构的同学可能知道,enqueue是用来入队的。所以从字面上来看,enqueueMessage方法可能跟将消息msg入队列有关。我们不妨抱着这一猜想继续阅读源码,看这一猜想是否能得到证实。现在就来看一下这个enqueueMessage方法中的实现。代码如下:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } // 调用了MessageQueue的enqueueMessage方法 return queue.enqueueMessage(msg, uptimeMillis);}
返回了MessageQueue.enqueueMessage,这样逻辑就由Handler切换到了MessageQueue。现在我们不着急继续往下分析源码。上面我们提到,Handler可以应用于任意两个线程间的通信,现在我们写代码实现一下两个子线程之间使用Handler进行通信。代码如下:
public void handlerTestMethod() { Thread childThread1 = new Thread() { @Override public void run() {// Looper.prepare(); handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Log.e("TAG", "childThread handleMessage received:" + msg.obj); Toast.makeText(MainActivity.this, ((String) msg.obj), Toast.LENGTH_SHORT).show(); } };// Looper.loop(); } }; Thread childThread2 = new Thread() { @Override public void run() { try { // 模拟耗时操作 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 线程2给线程1发消息 Message message = handler.obtainMessage(); message.obj = "Hello thread1"; handler.sendMessage(message); } }; childThread1.setName("线程1"); childThread1.start(); childThread2.setName("线程2"); childThread2.start(); }
代码比较简单,我不再做任何解释。需要注意的就是有两行被注释掉的代码。现在我们先不管他,将此段代码运行起来(注意最好使用模拟器),发现会有如下报错:
图中画线的部分就是这个出错的原因了。(这一块我用华为手机调试卡了我一个多小时,发现不会报错,我一直都在怀疑是自己的代码有问题,检查了好几遍觉得代码没错才想到可能是测试机的原因,换了模拟器之后,就能出现报错信息,真是坑爹啊!)报错信息中提到
Can't create handler inside thread Thread[线程1,5,main] that has not called Looper.prepare()
它的意思好像是说,无法在未调用Looper.prepare()的线程内创建Handler。现在我们寻根溯源的去找一下原因。因为这是在创建Handler时出现的错误,所以我们跟进new Handler()方法,最终会调用到的方法代码如下:
public Handler(@Nullable 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()); } } // 关键代码1。。。 mLooper = Looper.myLooper(); if (mLooper == null) { // 报错信息的源头在此处 throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
从Handler的构造方法中我们看到了Looper.myLooper方法得到了调用,紧接着就对此方法的返回值mLooper进行了空判断,为空时,抛出了异常。异常信息正是我们上边贴出来的,由此可知上图中的异常信息是因为Looper类型的mLooper对象为空导致的。现在看一下Looper.myLooper方法的代码。
public static @Nullable Looper myLooper() { return sThreadLocal.get();}
该方法返回的是ThreadLocal.get()得到的对象。关于ThreadLocal我不做详细的说明,这里我推荐一篇文章讲述的特别详细《ThreadLocal源码解读》。在Looper的myLooper方法中调用了ThreadLocal.get()方法,ThreadLocal内部维护了一个ThreadLocalMap,可以简单的将它视为key为ThreadLocal,value为代码中放入的值的一个Map集合。上面的报错中提到了Looper.prepare()方法,现在我们来看一下该方法中的实现。代码如下:
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
在Looper.prepare()的方法中,首先对ThreadLocal的get方法的返回值做了一个非空的判断,ThreadLocal.get()返回的是当前线程中该线程局部变量的副本中的value值。如果这个值不为空抛出了一个异常,这个值为空时才向ThreadLocal中set了一个Looper对象。所以,看到这里我们就能理解为什么在子线程中new一个Handler之前(new Handler会调用到Looper.myLooper方法),如果没有调用Looper.prepare()方法(此方法中向ThreadLocal中set了一个Looper对象)就会报错的原因。
我们提到Looper.myLooper方法中调用了ThreadLocal的get方法,Looper.prepare方法中调用了ThreadLocal的set方法。(关于ThreadLocal的set和get方法的分析,我在上面推荐的文章中讲的很清楚)。ThreadLocal.get方法保证了一个Thread只能返回与它唯一绑定的Looper。这就意味着Looper.myLooper方法保证了Looper的唯一,Looper.myLooper在Handler的构造方法中被调用了,所以,这也就意味着Looper和Handler是一一对应的。
现在我们接着分析Looper.prepare方法,调用sThreadLocal.set()方法时,new了一个Looper对象,现在我们看一下Looper的构造方法。代码如下:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread();}
Looper的构造方法,为我们创建了一个MessageQueue对象,可以全局看一下,关于MessageQueue的构造函数只有Looper中调用了。上面我们分析了Looper的唯一性,而Looper的构造函数中调用了MessageQueue的构造方法,这样一来也就保证了MessageQueue的唯一性。
至此,我们知道了,Handler、Looper和MessageQueue是一一对应的关系。
子线程中创建Handler如果不事先调用Looper.prepare方法会抛异常,经过上面的分析相信大家已经清楚了。But,那么问题来了,我们在主线程中使用Handler时也未曾调用Looper.prepare()和Looper.loop()方法,为什么没有抛出异常而是很出色的完成了子线程向主线程传递消息的功能?其实啊,这个问题是这样子的。我们知道,App在初始化时都会执行ActivityThread的main方法,而这个main方法已经替我们处理了Looper.prepare和Looper.loop。我们可以简单看一下源码:
public static void main(String[] args) { //。。。。 // 调用了Looper的prepareMainLooper方法 Looper.prepareMainLooper(); // 。。。。 ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); // 调用了Looper.loop方法 Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
源码中调用了Looper.prepareMainLooper()方法,也调用了Looper.loop()方法。所以我们在主线程中使用Handler不需要调用Looper.prepare()方法。
上面子线程使用Handler时两行注释掉的代码,我们只是解释了Looper.prepare(),对于Looper.loop方法还只字未提。现在终于有机会来将它单独拎出来简单分析一波了。首先上面的代码如果只调用Looper.prepare而不调用Looper.loop方法,运行结果是程序不会报错,但是却什么效果都没有。此时,把Looper.loop()的注释放开,再次运行结果是,Toast可以正常弹出,Log也会打印。因此,我们可以猜测,Looper.loop()方法可能跟Message有关系。现在我们带着这一猜想去看一下源码。代码如下:
public static void loop() { // 关键代码1 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; for (;;) { // 关键代码2 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // 代码省略。。。 try { // 关键代码3 msg.target.dispatchMessage(msg); 。。。。 } catch (Exception exception) { if (observer != null) { observer.dispatchingThrewException(token, msg, exception); } throw exception; } finally { ThreadLocalWorkSource.restore(origWorkSource); if (traceTag != 0) { Trace.traceEnd(traceTag); } } // 关键代码4。。。 msg.recycleUnchecked(); } }
loop方法的代码很长,我只标出了其中的关键几处。
关键代码1:上边已经分析过,myLooper方法调用了ThreadLocal.get方法,保证了Looper的唯一性。
关键代码2:queue.next()方法的返回值是一个Message类型的msg对象。所以MessageQueue.next方法一定是消息出队列的方法。
关键代码3:这是设计上的一个亮点,我们放到后面去分析。
关键代码4:这个也放到后面去分析。
至此我们已经把Looper要注意的大体分析完了,现在我们来看Handler使用中另外一个比较重要的角色MessageQueue(消息队列)。在MessageQueue的enqueueMessage(添加消息)和next(读取消息)这两个方法中,对消息Message的操作部分都是线程安全的。这些代码都被Synchronized关键字修饰了。MessageQueue对消息的操作其实是一个生产者-消费者模型,对于生产者消费者模型它的阻塞有两种情况。
第一:如果当前队列已满,此时再执行enqueueMessage(入队)的操作。此时会发生阻塞。
第二:如果当前队列已经空了,此时再执行next(出队)的操作。此时也会发生阻塞。
在分析MessageQueue的enqueueMessage和next方法之前,我们先来一个小插曲。在Message中有一个when字段,见名知意,这个字段肯定和时间有关系。我们看一下源码中对这个字段的解释:
/** * The targeted delivery time of this message. The time-base is * {@link SystemClock#uptimeMillis}. * @hide Only for use within the tests. */ @UnsupportedAppUsage @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public long when;
翻译一下,大概意思就是:此消息的目标传递(发送)时间。我们知道,调用sendMessage方法最终都会调用到sendMessageAtTime方法,而这个方法的第二个参数long uptimeMills就是代表我们希望此消息延迟多长时间被发送出去。这个参数会通过enqueueMessage方法将此消息延迟uptimeMills时间后加入到MessageQueue队列中。通过下面的图可以更直观的理解这一过程。
经过这一过程最终我们可以得到一个按时间顺序进行排序的优先级队列,越早执行的消息放在队头。
了解了msg.when这一概念之后,现在我们先来分析一下MessageQueue.next()方法。主要代码如下:
Message next() { // 关键变量1 int pendingIdleHandlerCount = -1; // -1 only during first iteration // 关键变量2:下次执行需要等待的时间 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // 等待nextPollTimeoutMillis时间 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { // 当前还没到msg的执行时刻 if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. // 将要等待的时间赋值给变量nextPollTimeoutMills nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); // 返回msg return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // 。。。 // 注释1:关键代码 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } // 注释2:关键代码 if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. // 注释3:将mBlocked赋值为true mBlocked = true; continue; } // 。。。 } //。。。 // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
next方法即便是省略掉一部分它的代码也不少,MessageQueue有一个阻塞唤醒机制,这将是我们要分析的重点。
nextPollTimeoutMills变量表示下一条消息执行需要等待的时间,在next方法最后我们看到,这一变量被赋值给了0。既然这样那是不是说明nativePollOnce(ptr,nextPollTimeoutMills)没有意义呢?现在我们带着这一疑问去源码中找答案。
第一、首次进入遍历,pendingIdleHandlerCount = -1,而nextPollTimeoutMills变量有值则说明此时Message不为null,MessageQueue有数据,故此时代码:
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue;}
会执行第二个if判断,首先将mBlocked赋值为true,然后执行continue当前for循环就会跳出,这样一来方法结束时的代码nextPollTimeoutMills = 0的赋值就得不到执行。
第二、加入此时MessageQueue消息队列为空代码:
if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; }} else { // No more messages. nextPollTimeoutMillis = -1;}
只能执行else了,将nextPollTimeoutMills变量赋值为-1。继续分析,同样上面提到的两个if判断:
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue;}
第一个if就会执行,将MidleHandlers.size()赋值给了pendingIdleHandlerCount变量。继续追踪ArrayList
public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); }}
MessageQueue的addIdleHandler方法只有ActivityThread中得到了调用,我们自己不会调用,所以在大多数情况下mIdleHandlers.size()是为0的,因此,变量pendingIdleHandlerCount的值也就是0了,所以这个时候代码:
if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue;}
依旧会得到执行,所以代码仍然是会跳出当前for循环的,并且将变量mBlocked赋值为true。
这一种情况是消息队列MessageQueue为空,nextPollTimeoutMills值为-1。而方法nativePollOnce(ptr,nextPollTimeoutMills)传入的时间是-1,此方法会让主线程一直处于空闲状态(至于何时唤醒先留下一个悬念)。所以此时主线程的Looper.loop方法中for循环内Message msg = queue.next();会一直阻塞在这里,此时主线程可以处理其他工作,msg得不到消息就会一直阻塞等待,这也就是为什么主线程从来不需要创建Handler但是一直可以执行,Looper一直在工作,所以主线程一直是挂起并且永远不会走完。
阻塞完的唤醒
分析完next方法紧接着我们来看一下enqueueMessage方法,代码如下:
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } //。。。 synchronized (this) { //。。。 msg.when = when; Message p = mMessages; boolean needWake; // 队列的队头为null时,将mBlocked的值赋给了needWake变量 if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // 。。。 } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { // 唤醒。。。 nativeWake(mPtr); } } return true; }
enqueueMessage方法代码没有太多,如果想要将主线程唤醒就需要调用最后的nativeWake方法。此方法调用条件就是needWake为true,所以,只有执行if而不是执行else时,才会把我们上面分析的已经为true的mBlocked赋给needWake。此种情况的条件就是p(消息队列的队头)为空时,或者时间还没到队头消息的执行时刻。
if条件成立执行的代码就是首先将当前的队头变成下一次要执行的消息,把我们要入队的消息msg赋给队头,然后把mBlocked赋给needWake。
此时因为MessageQueue的入队和出队而导致的线程的阻塞与唤醒就分析完了。
上面在分析Looper.loop方法的时侯,有两个小尾巴还没有介绍。现在我们来分析一下。
首先就是msg.target.dispatchMessage方法,msg.target其实就是Handler,故这里描述的也就是Handler.dispatchMessage方法。
代码如下:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
它的意思是:
第一:首先判断当前消息有没有定义callback,如果有就调用自己的callBack;
第二:msg没有定义callBack,再去判断当前Handler有没有定义callBack,如果有就调用当前Handler的callBack的handlerMessage方法。
第三:如果前面两个callBack都没有定义,那么就会调用Handler的handleMessage方法来处理消息。
Handler对Message的处理非常类似于事件分发过程中对touch事件的处理原则。实际上这都是用了责任链模式,也可以说是链式调用原则。它的应用很广泛,RxJava中用到的很多。它的优点就是很大程度上实现了消息处理的自由性。
最后再来看一下msg.recycleUnChecked方法。代码如下:
void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; 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++; } } }
recycleUnChecked方法的作用是:回收可能正在使用的消息。它的做法是将Message的字段置为空。并且在当前sPool(Message缓存池)的大小小于允许缓存的最大数量时,将要回收的Message的next字段指向sPool,将sPool指向了将要回收的Message对象,这样就将Message放入到了sPool缓存池的头部。
那sPool是做什么用的呢?
它是用户缓存Message对象的,我们在使用Handler时,通常是通过Handler.obtainMessage来获取Message对象的,而其内部调用了Message.obtain方法,这里为什么没有使用new Message的方式来新建一个Message对象呢?现在看一下obtain方法的代码:
/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
可以看到,当需要一个Message对象时,如果sPool不为null则返回当前的sPool(Message对象),而将sPool指向了之前sPool的next节点,然后将返回的Message的next节点指向空(断开链表),sPoolSize记录了当前缓存的Message对象的数量,如果sPool为空则说明没有缓存的Message,此时则需要创建一个新的Message对象(new Message)。
获取Message对象的设计使用的是享元模式。这样做的好处就是,加入了缓存池不用每次都去创建新的Message对象,减少了内存碎片,可以有效的降低内存泄漏的风险。
至此,Handler的原码我们就分析完了。里边涉及到的逻辑我倒是懂得,但是讲起来就有点逻辑不清,并且也没有一个清晰的目录,总觉得清晰度不够,真的是差些什么。希望各位看官多多担待,有什么好的意见可以留言。
在此我向享学课堂的老师表示感谢,关于Handler讲解的很深入,很到位。
更多相关文章
- Android内核开发:理解和掌握repo工具(含被墙后的下载方法)
- webapp打包为Android的apk包的一种方法
- Android智能指针使用方法介绍
- 编写高效的Android代码
- Android 测试代码编写小技巧 - UI 和 单元测试间共享代码