Android 消息机制原理分析-Handler Message MessageQueue Looper

安卓消息机制

以下源码皆基于安卓SDK版本:android-28

本文纯属本人基于安卓源码对安卓消息机制的总结。

Message篇

对于Message类,我们只需要关注以下部分即可:

public final class Message implements Parcelable{
	long when;
    Message next;
    // 以下省略其他的成员变量和方法
}

Handler篇

对于Handler,我们主要讲解两个方面:

  • Handler是如何处理消息的
  • Handler发送消息的常用方式以及内部原理

Handler是如何处理消息的

首先,针对消息的处理,Handler内部提供了三种处理方式,具体可以参照Handler内部dispatchMessage的源码,如下:

1	public void dispatchMessage(Message msg) {
2        if (msg.callback != null) {
3            handleCallback(msg);
4        } else {
5            if (mCallback != null) {
6                if (mCallback.handleMessage(msg)) {
7                    return;
8                }
9            }
10            handleMessage(msg);
11       }
12   }

首先,我们可以看到针对消息msg来说,如果它自己被指定默认的消息处理函数,则调用该函数进行处理,具体体现在上述代码第三行,那么该函数具体做了什么事,那么我们看一下安卓Handler类对于该方法的实现,如下:

private static void handleCallback(Message message) {
	message.callback.run();
}

先不着急,我们在看一下Message类中的callback是个什么,继续查找源码,如下:

public final class Message implements Parcelable {
    // 以上省略其他的成员变量和成员函数
    
    /*package*/ Runnable callback;
    
    // 以下省略其他的成员变量和成员函数
}

众所周知,Runnable是一个内部只定义了一个run方法的接口。那么结合dispatchMessagehandleCallback、Message类来看,我们可以知道Handler处理消息的整个逻辑:

  1. 对于Handler对象,如果其处理消息时,消息自己被设置了某一消息处理函数,那么就会优先调用该函数对消息进行处理;
  2. 如果没有为要处理的消息对象指定消息处理函数,且Handler对象自己被指定了消息处理函数,即mCallback的值不为空,则优先调用mCallback的消息处理函数,即mCallback.handleMessage函数;
  3. 若Handler对象自己没有被指定消息处理函数,则调用Handler对象自己内部的成员函数来处理消息,即handleMessage函数

针对以上第2点、第3点结论的推出,我们可以结合dispatchMessage函数的处理逻辑和以下代码块分析推理得出

mCallback是Handler类内部的一个成员变量,其相关的声明如下:

final Callback mCallback;

// Callback 是Handler类内部定义的接口
public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

	// 这个是Handler内部自己的成员函数,我们可以很清楚的知道,
    // 如果调用dispatchMessage的对象是Handler声明出来的,那么handleMessage实际上什么也没有做
    // 如果调用dispatchMessage的对象是Handler的子类声明出来的,并且这个子类重新实现了handleMessage
    // 函数,那么就会使用子类实现的处理逻辑对消息进行处理,当然这个前提是前面提到过的第1点、第2点都不会执行	  // 的前提下
	/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

那么问题就来了,mCallback是什么时候指定的?熟悉Spring的同学肯定都知道,里面有一个“依赖注入”的概念,那么针对mCallback成员变量的设置,我们猜想要么可以通过“构造器注入”或者“set注入”的方式来为mCallback变量进行赋值,对于我们的Handler来说,采取的是“构造器注入”的方式,具体可以查看Handler的构造函数。下面简单的贴一下与mCallback相关的Handler构造函数的实现,其他的Handler的构造函数的实现不予展示,感兴趣的同学可以自行查阅Handler的源码。

1  public Handler(Callback callback, boolean async) {
2          if (FIND_POTENTIAL_LEAKS) {
3              final Class<? extends Handler> klass = getClass();
4              if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
5                      (klass.getModifiers() & Modifier.STATIC) == 0) {
6                  Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
7                      klass.getCanonicalName());
8              }
9         }
10
11         mLooper = Looper.myLooper();
12         if (mLooper == null) {
13              throw new RuntimeException(
14                  "Can't create handler inside thread " + Thread.currentThread()
15                          + " that has not called Looper.prepare()");
16         }
17         mQueue = mLooper.mQueue;
18         mCallback = callback;
19         mAsynchronous = async;
20 }
21
22   public Handler(Looper looper, Callback callback, boolean async) {
23        mLooper = looper;
24        mQueue = looper.mQueue;
25        mCallback = callback;
26        mAsynchronous = async;
27    }

如上源码所示,第18行和第25行为Handler的mCallback指定了值。

Handler发送消息的常用方式以及内部原理

Handler对于消息的发送方式有以下几种函数可供调用:

public final boolean post(Runnable r);
public final boolean postAtTime(Runnable r, long uptimeMillis);
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis);
public final boolean postDelayed(Runnable r, long delayMillis);
public final boolean postDelayed(Runnable r, Object token, long delayMillis);
public final boolean postAtFrontOfQueue(Runnable r);
public final boolean sendMessage(Message msg);
public final boolean sendEmptyMessage(int what);
public final boolean sendEmptyMessageDelayed(int what, long delayMillis);
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis);
public final boolean sendMessageDelayed(Message msg, long delayMillis);
public boolean sendMessageAtTime(Message msg, long uptimeMillis);
public final boolean sendMessageAtFrontOfQueue(Message msg);
public final boolean executeOrSendMessage(Message msg);

查看以上函数的源码,其最终都是调用到Handler类中的下面这个函数,注意是私有方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis){
    msg.target = this;
    if (mAsynchronous) {
       msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

**其中,这个queue对象是Handler类中的mQueue成员变量。**那么问题来了,这个mQueue变量是哪里设置的,是“构造器注入”还是“set注入”?通过查看Handler类的源码,我们发现只有两个地方对这个mQueue变量进行了赋值。代码如下:

public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;  // 在这里被赋值
        mCallback = callback;
        mAsynchronous = 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());
            }
        }

        mLooper = Looper.myLooper(); // 取到mLooper对象
        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对象被构造的时候就可以为成员变量mQueue指定相应的值。第一段代码指定是外来传进来的looper对象中的mQueue,第二段代码取的则是Looper.myLooper().mQueue。这里,我们重点关于第二段代码。这时候,我们的第一个疑问就是:Looper.myLooper()是个什么玩意儿?它有什么用?是干什么的? 强行三连,最为致命!我们先把这个疑惑放在一边,只需要知道,这个mQueue是从某个Looper对象中来的,不管它是外部的Looper对象设进来还是Looper.myLooper()设进来的。我们先看看那个return queue.enqueueMessage(msg, uptimeMillis);这行代码是做什么的。那么就需要到MessageQueue这个类中去查看了,那么接下来进入我们的MessageQueue篇。

MessageQueue

在Handler篇,章节"Handler发送消息的常用方式以及内部原理"的末尾部分,我们提到了enqueueMessage这个函数,那么我们就详细看看它的源码实现。

1  boolean enqueueMessage(Message msg, long when) {
2          if (msg.target == null) {
3              throw new IllegalArgumentException("Message must have a target.");
4          }
5          if (msg.isInUse()) {
6              throw new IllegalStateException(msg + " This message is already in use.");
7          }
8
9          synchronized (this) {
10              if (mQuitting) {
11                  IllegalStateException e = new IllegalStateException(
12                          msg.target + " sending message to a Handler on a dead thread");
13                  Log.w(TAG, e.getMessage(), e);
14                  msg.recycle();
15                  return false;
16              }
17
18              msg.markInUse();
19              msg.when = when;
20              Message p = mMessages;
21              boolean needWake;
22              if (p == null || when == 0 || when < p.when) {
23                  // New head, wake up the event queue if blocked.
24                  msg.next = p;
25                  mMessages = msg;
26                  needWake = mBlocked;
27              } else {
28                  // Inserted within the middle of the queue.  Usually we don't have to wake
29                  // up the event queue unless there is a barrier at the head of the queue
30                  // and the message is the earliest asynchronous message in the queue.
31                  needWake = mBlocked && p.target == null && msg.isAsynchronous();
32                  Message prev;
33                  for (;;) {
34                      prev = p;
35                      p = p.next;
36                      if (p == null || when < p.when) {
37                          break;
38                      }
39                      if (needWake && p.isAsynchronous()) {
40                          needWake = false;
41                      }
42                  }
43                  msg.next = p; // invariant: p == prev.next
44                  prev.next = msg;
45              }
46  
47              // We can assume mPtr != 0 because mQuitting is false.
48              if (needWake) {
49                  nativeWake(mPtr);
50              }
51          }
52          return true;
53}

逐块来看代码功能:

  • line2到line7, 做一些检查:检查消息msg是否有对应的Handler对象和msg当前是否在被使用。
  • line9告诉我们:以下的过程是只允许一个线程进行处理,是需要做线程安全处理的。
  • line10到line16:告诉我们如果mQuitting为真,那么对消息msg进行回收处理。
  • line18:将消息标记为正在使用
  • line19: 设置消息的处理时间(主要用来排消息在消息队列中的位置),有以下几种方式:
    • 系统启动后,当前调用postXXX或者sendXXX这类方法的时间作为时间标记,这个时间标记仅用来安排消息在消息队列中的位置,而不是消息的执行时间
    • 在上面的时间标记的基础上,再往后延一定的时间
    • 人为指定一个时间标记,很灵活
  • line20: mMessages其实是由各个Message链接起来的链表结构,关于链表可自行查阅《数据结构与算法》中的链表章节。mMessages可能为空,表示一个消息也没有;也可能不为空,表示至少已经有一个Message在这个链上。
  • line22到line27:链表为空或者新进来的消息的when值比链表头部节点的when值小,则将新进来的消息加入链表的头部
  • line32到line44: 链表不为空且新加入的消息的when值比链表头部节点的when值大,则与链表中的其他节点的when值进行比较,一直找到比链表中某个节点的when值小为止,然后将新的消息插入找到的那个节点的前面。(其现实意义就是:链表中的消息是按照进入队列中那个时间点的值进行排列的,早进入队列的消息的when值小,所以需要排在靠近链表头部的位置,晚进入队列的消息的when值大,所以需要排在靠近链表尾部的位置,这样就以链表的形式完成了一个消息的队列结构)。具体的图像化的分析过程可以参照 我的博客

Looper篇

先看看Looper内部几个重要的成员变量

public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;
}

再看看prepare函数

public static void prepare() {
        prepare(true);
}

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));
}

代码比较简单,但是sThreadLocal.get()干的事情可不少,以下贴一下源码:

// 类ThreadLocal的成员函数    
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

以下贴一下getMap的代码:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

以下,贴一下threadLocals是个什么东西:

public class Thread implements Runnable{
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // 省略其他的成员变量和成员函数
}

经过在Thread的源码中查找,Thread并没有提供为threadLocals设置值的方法,所以不需要考虑使用Thread的成员函数为其设置值这条路径(这点很重要)。还有另外重要的一点,threadLocals只在声明的时候指定了默认的初始值null和执行exit函数时设置为null。

然后我们接着返回去看我们上面贴出来的get方法:

  • 对于一开始,当前线程(此时记做为Ti)调用get方法是获取的map肯定是null,所以会调用:

    private T setInitialValue() {
    	T value = initialValue();
    	Thread t = Thread.currentThread();
    	ThreadLocalMap map = getMap(t);
    	if (map != null)
    		map.set(this, value);
    	else
    		createMap(t, value);
    	return value;
    }
    

    由于map是空,所以调到createMap为当前的线程对象t的threadLocals进行创建操作,具体以假代码的方式描述为: **t.threadLocals的table数组中存储了【Looper类的sThreadLocal,null】这样形式的节点元素。**table是ThreadLocalMap的成员变量,存储Entry元素。其中null元素是由于调用initialValue()函数而来的。那么这样我们再回到开头看prepare函数,其实if语句内的判断是返回null,所以执行sThreadLocal.set(new Looper(quitAllowed));将set函数的代码贴出来,如下:

    public void set(T value) {
    	Thread t = Thread.currentThread();
    	ThreadLocalMap map = getMap(t);
    	if (map != null)
    		map.set(this, value);
    	else
    		createMap(t, value);
    }
    

    由于我们在get的时候已经创建过关于当前调用prepare函数的线程对象的map了,所以这里只是将那个新new出来的Looper对象加到这个线程对象Ti的threadLocals中去。

    综上所述:

    1. 当前哪个线程调用Looper.prepare(),其本质上是将Entry元素,即【Looper类的静态成员变量sThreadLocal,new出来的Looper对象】增加到当前线程的threadLocals中去,threadLocals本质上是个map,其key=Entry中的key,value=Entry的value。
    2. 当前线程不能调用Looper.prepare()两次及两次以上,通过prepare的函数内容我们知道,调用多次会抛出RuntimeException异常对象。

然后我们看看如何使用Looper进行消息的处理

其本质上是当前线程显示调用Looper.loop()函数来进行消息的处理,那么我们则看看loop函数内部的实现,如下:为了方便,我们将分析过程写在代码内部。

    public static void loop() {
        // myLooper其实是sThreadLocal.get(),那么我们当前线程调用Looper.loop()时,获取的就是我们当前线程之前在调用时
        // new出来的Looper对象。
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 这个new出来的Looper对象的mQueue的内容我们暂时是不知道,所以先着重作为一个重点,后续需要去看这个Looper对象me中的
        // mQueue的值是怎么来的 !!!!!!!!!!!
0        final MessageQueue queue = me.mQueue;

        
        // !!!!!!! 以下关于Binder、slowDeliveryDetected、traceTag、slowDispatchThresholdMs、
        // slowDeliveryThresholdMs、和与之相关的代码都先不关注
        // 只关注我下面标注数字的代码行即可
        
        // 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();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

1        for (;;) {
          	// 在queue中沒有消息的時候,這個loop()函數會阻塞在下面這行代碼處
            // 因為Android的底層內核是Linux系統,Linux內核會監聽文件描述符,
            // 這裡的文件描述符對象從抽象上看就是Android的Message,當沒有Message
            // 加到當前線程的MessageQueue中去的時候,通過Looper的loop()函數去調用next
            // 的時候會發生阻塞
2            Message msg = queue.next(); // might block
3            if (msg == null) {
4                // No message indicates that the message queue is quitting.
5                return;
6            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
7            try {
8                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
9            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
10            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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);
            }

11            msg.recycleUnchecked();
        }
    }

根据上面标注的数字,可以看出来,其实代码逻辑很简单,就是从me这个Looper对象mQueue中取消息出来,然后执行标注7那一行的代码即可,其中取消息的过程可能发生阻塞(即标注2那一行的代码)。其中标注7的消息具体处理逻辑请详看“Handler篇—Handler是如何处理消息的”中的“Handler处理消息的整个逻辑”即可。

接下来那么关于loop()函数,我们就只剩下一个疑问了,我们已经知道了me是怎么来的了,那么代码中我们标注0的地方me.mQueue这个是怎么来的。通过在整个Looper类中查找mQueue,我们只在两个地方发现可能会设置mQueue的地方:

  • Looper类的构造函数:所以我们结合prepare函数可以知道,mQueue的创建是这个时候创建出来的

  • public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }
    
  • 同一个包(package)下的其他类可能为mQueue进行增删(因为Looper类中的变量mQueue是包访问权限)

所以我们的结论是:后两种方式都极有可能改变Looper中的mQueue,但必须都是同一个包下的类才有权修改其内容。

重看Handler类

我们在“Handler篇—Handler发送消息的常用方式以及内部原理”章节中提到过Handler对象不管是postXXX还是sendXXX方式的发送消息方式,最终都是调用enqueueMessage函数,其作用就是将postXXX或者sendXXX中的参数Message对象加到其mQueue成员变量中。通过查阅Handler类的源码知道,这个mQueue就是在Handler对象初始化(即调用构造函数)的时候设进来的,其来源只有以下两种:

  • 将Handler的构造函数中通过参数传进来的Looper对象的mQueue设置给他
  • 取当前线程的Looper对象的mQueue

💡 结论: 诶! 这不是将Handler和Looper对象联系起来了吗?这样我们就知道了,Looper对象在执行loop()函数时,其内部的队列mQueue中的消息正是Handler对象通过postXXX或者sendXXX方法添加进去的呀!❤️

再回过去看Looper类的loop()函数

这次我们看数字标注的2-6,看代码我们会产生以下的疑问:如果队列中没有消息不是在标注5的那一行return了吗,loop函数不是直接结束了吗,如果在loop()函数调用前,Handler对象根本就没有调过postXXX或者sendXXX这类的方法,那不是loop函数的调用就是个摆设,没起一点作用。

我们在源码中注意到有这么一行英文注释(在我们标注的数字4那一行): “// No message indicates that the message queue is quitting.” 那我们就想,消息队列退出是什么意思,所以我们去MessageQueue中查找有没有与之对应的标记,正好找到这么一个成员变量 “mQuitting”,这个值的赋值只有在调用MessageQueue类的void quit(boolean safe)时才发生,既然MessageQueue和Handler和Looper类相关性如此密切,我们则去这两个类中去查找是否调用了这个quit函数,然后只在Looper类中找到了quit的调用,在Handler中没有找到,代码如下:

    public void quit() {
        mQueue.quit(false);
    }

💡 原来这句英文注释是这个意思,并不是队列中没有消息了就直接结束loop()函数,而是这个Looper对象自己调用了自己的quit()函数才会终结这个loop()函数内部的循环,最终终结这个消息的处理过程。

最终总结

至此,Android对于消息的处理的整个逻辑就比较清晰了,我们综合以上对于“Message篇”、“Handler篇”、“MessageQueue篇”、“Looper篇”的分析,得出简要的结论如下:

  1. 首先,为了处理当前线程中的消息,Andorid为当前线程创建一个Looper对象来负责消息的处理,这个创建过程就是Looper类的prepare函数,该函数内部的if判断+异常抛出保证了,每一个线程只能创建一个Looper对象
  2. 为了进行消息的处理,Android的Looper类提供了loop()函数来无限地对消息进行处理,除非显式地调用了quit函数终结消息处理
  3. 消息是Handler对象通过postXXX或者sendXXX这类方法将其加到调用这些方法的当前线程的Looper对象中的(即第1点提到的Looper对象)
  4. 与Handler对象绑定的Looper对象要么是当前线程的Looper对象,要么是其他传进来的Looper对象(构造函数传Looper对象:这个就有可能是其他线程的Looper对象)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值