Android Handler消息机制核心代码

Posted 涂程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Handler消息机制核心代码相关的知识,希望对你有一定的参考价值。

前言

该文主要是分析Handler消息机制的关键源码,阅读前需要对handler有一些基本的认识。这里先简要回顾一下:

基本组成

完整的消息处理机制包含四个要素:

  1. Message(消息):信息的载体
  2. MessageQueue(消息队列):用来存储消息的队列
  3. Looper(消息循环):负责检查消息队列中是否有消息,并负责取出消息
  4. Handler(发送和处理消息):把消息加入消息队列中,并负责分发和处理消息

基本使用方法

Handler的简单用法如下:

Handler handler = new Handler()
    @Override
    public void handleMessage(@NonNull Message msg)                      
        super.handleMessage(msg);
    
;
Message message = new Message();
handler.sendMessage(message);

注意在非主线程中的要调用Looper.prepare()Looper.loop()方法

工作流程

其工作流程如下图所示:

从发送消息到接收消息的流程概括如下:

  1. 发送消息
  2. 消息进入消息队列
  3. 从消息队列里取出消息
  4. 消息的处理

下面就一折四个步骤分析一下相关源码:

发送消息

handle有两类发送消息的方法,它们在本质上并没有什么区别

  • sendXxxx()
    • boolean sendMessage(Message msg)
    • boolean sendEmptyMessage(int what)
    • boolean sendEmptyMessageDelayed(int what, long delayMillis)
    • boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
    • boolean sendMessageDelayed(Message msg, long delayMillis)
    • boolean sendMessageAtTime(Message msg, long uptimeMillis)
    • boolean sendMessageAtFrontOfQueue(Message msg)
  • postXxxx()
    • boolean post(Runnable r)
    • boolean postAtFrontOfQueue(Runnable r)
    • boolean postAtTime(Runnable r, long uptimeMillis)
    • boolean postAtTime(Runnable r, Object token, long uptimeMillis)
    • boolean postDelayed(Runnable r, long delayMillis)
    • boolean postDelayed(Runnable r, Object token, long delayMillis)

这里不分析具体的方法特性,它们最终都是通过调用sendMessageAtTime()或者sendMessageAtFrontOfQueue实现消息入队的操作,唯一的区别就是post系列方法在消息发送前调用了getPostMessage方法:

private static Message getPostMessage(Runnable r) 
    Message m = Message.obtain();
    m.callback = r;
    return m;

需要注意的是:sendMessageAtTime()再被其他sendXxx调用时,典型用法为:

sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

若调用者没有指定延迟时间,则消息的执行时间即为当前时间,也就是立即执行。Handler所暴露的方法都遵循这种操作,除非特别指定,msg消息执行时间就为:当前时间加上延迟时间,本质上是个时间戳。当然,你也可以任意指定时间,这个时间稍后的消息插入中会用到。 代码很简单,就是讲调用者传递过来的Runnable回调赋值给message(用处在消息处理中讲)。 sendMessageAtTime()sendMessageAtFrontOfQueue方法都会通过enqueueMessage方法实现消息的入栈:

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) 
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) 
            msg.setAsynchronous(true);
        
        return queue.enqueueMessage(msg, uptimeMillis);
    

代码很简单,主要有以下操作:

  • 让message持有发送它的Handler的引用(这也是处理消息时能找到对应handler的关键)
  • 设置消息是否为异步消息(异步消息无须排队,通过同步屏障,插队执行)
  • 调用MessageQueueenqueueMessage方法将消息加入队列

消息进入消息队列

入队前的准备工作

enqueueMessage方法是消息加入到MessageQueue的关键,下面分段来分析一下:

boolean enqueueMessage(Message msg, long when) 
    if (msg.target == null) 
	    throw new IllegalArgumentException("Message must have a target.");
    
    //...省略下文代码
    

这端代码很简单:判断message的target是否为空,为空则抛出异常。其中,target就是上文Handler.enqueueMessage里提到到Handler引用。 接下来下来开始判断和处理消息

boolean enqueueMessage(Message msg, long when) 
    //...省略上文代码
    synchronized (this) 
        if (msg.isInUse()) 
            throw new IllegalStateException(msg + " This message is already in use.");
        
        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;
        //...省略下文代码
    
    //...省略下文代码

首先加一个同步锁,接下来所有的操作都在synchronized代码块里运行 然后两个if语句用来处理两个异常情况:

  1. 判断当前msg是否已经被使用,若被使用,则排除异常;
  2. 判断消息队列(MessageQueue)是否正在关闭,如果是,则回收消息,返回入队失败(false)给调用者,并打印相关日志

若一切正常,通过markInUse标记消息正在使用(对应第一个if的异常),然后设置消息发送的时间(机器系统时间)。 接下来开始执行插入的相关操作

将消息加入队列

继续看enqueueMessage的代码实现

boolean enqueueMessage(Message msg, long when) 
    //...省略上文代码
    synchronized (this) 
            //...省略上文代码
            //步骤1
            Message p = mMessages;
            boolean needWake;
            //步骤2
            if (p == null || when == 0 || when < p.when) 
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
             else 
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                //步骤3
                Message prev;
                for (;;) 
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) 
                        break;
                    
                    if (needWake && p.isAsynchronous()) 
                        needWake = false;
                    
                
                msg.next = p;
                prev.next = msg;
            

            if (needWake) 
                nativeWake(mPtr);
            
    
    //步骤4
    return true;

首先说明MessageQueue使用一个单向链表维持着消息队列的,遵循先进先出的软解。 分析上面这端代码:

  • 第一步:mMessages就是表头,首先取出链表头部。

  • 第二步:一个判断语句,满足三种条件则直接将msg作为表头:

    1. 若表头为空,说明队列内没有任何消息,msg直接作为链表头部;
    2. when == 0 说明消息要立即执行(例如 sendMessageAtFrontOfQueue方法,但一般的发送的消息除非特别指定都是发送时的时间加上延迟时间),msg插入作为链表头部;
    3. when < p.when,说明要插入的消息执行时间早于表头,msg插入作为链表头部。
  • 第三步:通过循环不断的比对队列中消息的执行时间和插入消息的执行时间,遵循时间戳小的在前原则,将消息插入和合适的位置。

  • 第四步:返回给调用者消息插入完成。

需要注意代码中的needWakenativeWake,它们是用来唤醒当前线程的。因为在消息取出端,当前线程会根据消息队列的状态进入阻塞状态,在插入时也要根据情况判断是否需要唤醒。

接下来就是从消息队列中取出消息了

从消息队列里取出消息

依旧是先看看准备准备工作

准备工作

在非主线程中使用Handler,必须要做两件事

  1. Looper.prepare():创建一个Loop
  2. Looper.loop():开启循环

我们先不管它的创建,直接分段看啊循环开始的代码:首先是一些检查和判断工作,具体细节在代码中已注释

 public static void loop() 
 		//获取loop对象
        final Looper me = myLooper();
        if (me == null) 
        	//若loop为空,则抛出异常终止操作
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        
        if (me.mInLoop) 
        	//loop循环重复开启
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        
        //标记当前loop已经开启
        me.mInLoop = true;
        //获取消息队列
        final MessageQueue queue = me.mQueue;
        //确保权限检查基于本地进程,
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);
        boolean slowDeliveryDetected = false;
        //...省略下文代码
        

loop中的操作

接下来就是循环的正式开启,精简关键代码:

 public static void loop() 
 		//...省略上文代码
 		for (;;) 
 			//步骤一
            Message msg = queue.next();
            if (msg == null) 
                //步骤二
                return;
            
           //...省略非核心代码
            try 
            	//步骤三
                msg.target.dispatchMessage(msg);
                //...
             catch (Exception exception) 
                //...省略非核心代码
             finally 
                //...省略非核心代码
            
            //步骤四
            msg.recycleUnchecked();
        
        

分步骤分析上述代码:

  • 步骤一:从消息队列MessageQueue中取出消息(queue.next()可能会造成阻塞,下文会讲到)
  • 步骤二:如果消息为null,则结束循环(消息队列中没有消息并不会返回null,而是在队列关闭才会返回null,下文会讲到)
  • 步骤三:拿到消息后开始消息的分发
  • 步骤四:回收已经分发了的消息,然后开始新一轮的循环取数据
MessageQueue的next方法

我们先只看第一步消息的取出,其他的在稍后小节再看,queue.next()代码较多,依旧分段来看

Message next() 
	//步骤一
	final long ptr = mPtr;
    if (ptr == 0) 
		return null;
	
	//步骤二
	int pendingIdleHandlerCount = -1; 
	//步骤三
	int nextPollTimeoutMillis = 0;
	//...省略下文代码
	
  • 第一步:如果消息循环已经退出并且已经disposed之后,直接返回null,对应上文中Loop通过queue.next()取消息拿到null后退出循环
  • 第二部:初始化IdleHandler计数器
  • 第三部:初始化native需要用的判断条件,初始值为0,大于0表示还有消息等待处理(延时消息未到执行时间),-1则表示没有消息了。

继续分析代码:

Message next() 
	//...省略上文代码
	for(;;)
		if (nextPollTimeoutMillis != 0) 
			Binder.flushPendingCommands();
		
		nativePollOnce(ptr, nextPollTimeoutMillis);
		//...省略下文代码		
	
	

这一段比较简单:

  • 开启一个无限循环
  • nextPollTimeoutMillis != 0表示消息队列里没有消息或者所有消息都没到执行时间,调用nativeBinder.flushPendingCommands()方法,在进入阻塞之前跟内核线程发送消息,以便内核合理调度分配资源
  • 再次调用native方法,根据nextPollTimeoutMillis判断,当为-1时,阻塞当前线程(在新消息入队时会重新进入可运行状态),当大于0时,说明有延时消息,nextPollTimeoutMillis会作为一个阻塞时间,也就是消息在多就后要执行。

继续看代码:

Message next() 
	//...省略上文代码
	for(;;)
		//...省略上文代码
       //开启同步锁
		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) 
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                     else 
                        //步骤五
                        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 
                    //步骤六
                    nextPollTimeoutMillis = -1;
                
			
		//...省略下文IdleHandler相关代码		
	
	

分析一下代码:

  • 第一步:获取到队列头部
  • 第二步:判断当前消息是否为同步消息(异步消息的target为null),开启循环直到发现同步消息为止
  • 第三步:判断消息是否为null,不为空执行第四步,为空执行第六步;
  • 第四步:判断消息执行的时间,如果大于当前时间,给前文提到的nextPollTimeoutMillis赋新值(当前时间和消息执行时间的时间差),在这一步基本完成了本次循环所有的取消息操作,如果当前消息没有到达执行时间,本次循环结束,新循环开始,就会使用上文中提到的nativePollOnce(ptr, nextPollTimeoutMillis);方法进入阻塞状态
  • 第五步:从消息队列中取出需要立即执行的消息,结束整个循环并返回。
  • 第六部:消息队列中没有消息,标记nextPollTimeoutMillis,以便下一循环进入阻塞状态

剩下的代码就基本上是IdleHandler的处理和执行了,在IdleHandler小节里进行讲解,这里就不展开说明了。

消息的处理

还记得上文中loop方法中的msg.target.dispatchMessage(msg);吗? 消息就是通过dispatchMessage方法进行分发的。其中target是msg所持有的发送它的handler的引用,它在发送消息时被赋值。 dispatchMessage的源码如下:

public void dispatchMessage(@NonNull Message msg) 
        if (msg.callback != null) 
            handleCallback(msg);
         else 
            if (mCallback != null) 
                if (mCallback.handleMessage(msg)) 
                    return;
                
            
            handleMessage(msg);
        
    

代码很简单,通过判断Message是否有Runable来决定是调用callback还是调用handleMessage方法,交给你定义的Handler去处理。需要注意的是,callback虽然是一个Runable,但是它并没有调用run方法,而是直接执行。这说明它并没有开启新的线程,就是作为一个方法使用(如果一开始Handler使用kotlin写的话,此处或许就是一个高阶函数了)。

其他关键点

上面讲完了消息处理的主流程,接下来讲一下主流程之外的关键点源码

Loop的创建

还记得上文中的说到的在非主线程中的要调用Looper.prepare()Looper.loop()方法吗?这两个方法可以理解为初始化Loop和开启loop循环,而主线程中无需这么做是因为在app启动的main方法中,framework层已经帮我们做了。我们分别来看这两个方法:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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));
    
private Looper(boolean quitAllowed) 
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
        

这里首先使用了一个静态的ThreadLocal确保Loop的唯一性,同时做到线程隔离,使得一个线程有且只有一个Loop实例。接着初始化Loop,同时创建MessageQueue (quitAllowed设置是否允许退出)。在这一步实现了Loop和消息队列的关联。

需要注意的是,Loop的构造方式是私有的,我们只能通过prepare 区创建,然后通过myLooper方法去获取。

public static @Nullable Looper myLooper() 
        return sThreadLocal.get();
    

ThreadLocal.get源码:

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

ThreadLocalMap getMap(Thread t) 
        return t.threadLocals;
        

可以看到,每个Thread都持有一个ThreadLocalMap ,它和HashMap使用相同的数据结构,使用ThreadLocal作为key值,value就是Loop实例。不难发现:我们只能获取到当前线程的Loop实例。

Loop也提供了主线程中初始化的办法prepareMainLooper ,但是这个方法明确说明不允许调用,只能由系统自己调用。 这基本上就是Loop创建的关键了,也是在这里完成了Loop和消息队列以及线成之间的关联。

Handler的创建

Handler的构造函数有以下几个:

  1. public Handler()
  2. public Handler(Callback callback)
  3. public Handler(Looper looper)
  4. public Handler(Looper looper, Callback callback)
  5. public Handler(boolean async)
  6. public Handler(Callback callback, boolean async)
  7. public Handler(Looper looper, Callback callback, boolean async)

其中第一个和第二个已经被废弃了,实际上第1~5个构造方法都是通过调用public Handler(Callback callback, boolean async)public Handler(Looper looper, Callback callback, boolean async)实现的,它们的源码如下:

 public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) 
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    
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());
            
        

        mLooper = Looper.myLooper();
        if (mLooper == null) 
        	//注意这个异常,loop不能为空的,首先要Looper.prepare();
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    

以上是关于Android Handler消息机制核心代码的主要内容,如果未能解决你的问题,请参考以下文章

[Android源代码分析]Android消息机制,Handler,Message,Looper,MessageQueue

从Handler+Message+Looper源代码带你分析Android系统的消息处理机制

Android中的Handler机制

Android消息机制——Handler

Android Handler机制剖析

android的消息处理机制(图+源码分析)——Looper,Handler,Message