深入理解 Handler 消息机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011371324/article/details/88630383

上一篇 - 消息机制 Handler 使用 文章讲了 Handler 的一个概述和基本的使用方法,这里还有一点需要强调一下:对于初学者一定要将你创建的子线程区分开,Handler 是你在子线程执行完,准备进行线程切换执行其他操作时才开始使用 Handler

本篇主要对 Handler 的工作原理进行分析,即 Handler、Looper、MessageQueue 三者是如何工作的,从源码层面来分析下,本篇的主要内容如下:

handler_detail

Looper

首先来说 Looper,实际上 Handler 最开始是和 Looper 关联起来的,上一篇中对 Handler 的构造函数进行了介绍,无论有参构造还是无参构造,其实都是需要有 Looper的。


public Handler(Looper looper) {
        this(looper, null, false);
    }

public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

这两个好理解,构造中直接需要有 Looper,可以使主线程的 Looper,也可以是你自己创建的线程所属的 Looper。

主线程 Looper 获取方法:

public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
}

如果是你自己创建的子线程,那么如果创建子线程的 Looper 呢?

// 在当前线程中创建 Looper,但是只能创建一次,重复创建(即重复调用这个方法,或报错)
public static void prepare() {
        prepare(true);
}

// 创建之后,还需要将 Looper运转起来,才能执行消息队列中的消息
public static void loop() {
    ...
}

就像下面这个小例子,就是在子线程中使用 Handler,其他线程就可以使用这个 Handler,发送消息后,消息会在这个 new Thread 线程中执行。


private Handler handler;

new Thread()
		{
			public void run()
			{
 
				Looper.prepare();
				
				handler = new Handler()
				{
					public void handleMessage(android.os.Message msg)
					{
						Log.e("TAG",Thread.currentThread().getName());
					};
				};
                 Looper.loop();
			    
			}
		    
		}

好,回到 Looper 分析中来,那么无参数构造的 Handler 呢?最终是调用这个构造方法。

    public Handler(Callback callback, boolean async) {
    
        ...

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

这个方法中有一个 Looper.myLooper() 方法,通过这个方法就是获取到了 Looper,具体是怎么获取?分下这个方法。


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

看到是通过 ThreadLocal 来获取的,ThreadLocal 是线程内部维护数据的一个变量,能够存储当前线程的一些数据,只有在当前线程才能够获取当前线程中的数据, Looper 属于线程的数据,所以可以通过 ThreadLocal 可以获取,主要的过程就是通过 ThreadLocal 的 put 和 get 方法,以 key-value 的形式来完成的,key 就是当前线程。那么什么时候将线程的 Looper 存储到 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));
}

看到这段代码是不是就理解了。

好,接着往下走,获取到 Looper 的目的是什么呢?mQueue = mLooper.mQueue, 答案是为了获取到 MessageQueue。


权限是包内权限,可以知道 Handler 和 Looper 类处于同一个包下 .../android/os 
final MessageQueue mQueue;

获取当前线程的 MessageQueue,有这几种方法,都是通过 Looper 来获取的


// (1)同一个包内,直接使用
mQueue = mLooper.mQueue;

// (2)已经获取了 Looper
public @NonNull MessageQueue getQueue() {
    return mQueue;
}

// (3)没有获取looper ,不在同一个保内,当时在当前线程
public static @NonNull MessageQueue myQueue() {
    return myLooper().mQueue;
}

Looper 持有 MessageQueue,MessageQueue 是在什么时候创建的呢?看下 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();
}

// MessageQueue.java
MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
}

代码比较清晰,就是在创建 Looper 时创建 MessageQueue,既然 一个线程的 Looper 只能有一个,同理,MessageQueue 也是只能有一个。本文最开始的的问题,如何切换到主线程?,Looper 和 MessageQueue 是在主线程,那么通过 Handler 发送的消息自然就在主线程中了。

对于 Looper 来讲,剩下就是一个最重要的方法,loop()


    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        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();
        ...

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            ...

            msg.recycleUnchecked();
        }
}
    

首先是获取 MessageQueue,然后 Looper 中通过一个无限循环,不断从消息队列中取出消息,然后通过调用 msg.target.dispatchMessage(msg); 来执行消息,虽然我们可能不知道 msg 中的 target 是啥?但是可以猜想一下,是不是应该是 Handler 呢?因为我们通过 Handler 来执行我们在主线程(准确说是 Handler 线程,这里只是以主线程为例)想干事的事情,即 handleMessage 或者是 Runnable 的 run 方法,下面再去验证想法。

到这里会有一些疑问?

  • 为什么是无限循环,这样的话,CPU 满载,手机岂不是一直满负荷在跑,变得烫手。。。
  • 什么时候 msg 为 null,这时候 return 之后会怎么样?
  • Message msg = queue.next(); // might block 可能会阻塞,含义是什么?

这几个问题中,到这里暂时只能解释第二个,如果 msg 为空,返回之后 loop() 方法结束。这里以 应用程序的主线程为例,在主线程,主要是更新 UI,也就是在 Looper 中取出来的消息是用来更新 UI 的操作,既然都不能更新 UI,那么也只有应用程序死掉了,主线程退出的情况。

对于其他的子线程,也是消息队列中没有消息了,消息队列退出,当前子线程也就结束了。

现在我们知道 Handler 获取了 Loopper,Looper 中有消息队列,也能够获取到,即 Handler 持有 Looper 和 MessageQueue,对于 Handler 来说,有了这些,能够做些什么,下面就来看看 Handler。

Handler

Handler 的主要原理,可以看一下下图:

Handler

实际上,Handler 做的主要就是两件事,发送消息,然后等到消息处理时进行执行。先来看发送消息,在上一篇文章中提到,发消息有两种方式,send 和 post 方式,最终都是调用 sendMessageAtTime 方法。

public boolean sendMessageAtTime(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;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    
}

sendMessageAtTime 方法中 uptimeMillis 参数指定消息执行的绝对时间,接着将消息加入到消息队列中,MessageQueue 就是上面提到的,通过 Looper 获取的消息队列。

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

(1)msg.target = this 这一句验证了我们上面的猜想,target 就是 Handler 本身,在 Looper 中 loop() 方法中执行的 msg.target.dispatchMessage(msg),就是回调 Handler 的 dispatchMessage () 方法。

(2)中间部分代码,如果设定的是异步执行,需要将消息打上异步的标记。默认情况下,消息队列中都是同步执行的,但是会根据设定的时间来区分消息执行的先后顺序。

将消息加入到消息队列中的具体操作在下文介绍 MessageQueue 时再详细分析。接下来看一下,消息的执行,即 dispatchMessage。

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    // 通过 post 方法发送的消息
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        // 通过 send 方法发送的消息
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        } 
        // handler 的默认方法,空方法
        handleMessage(msg);
    }
}

这个方法就比较简单,分为 3 种情况:

(1) 第 1 种,通过 post 方法发送的消息,post(Runnable r),是一个 Runnable 对象,然后赋值到 Message 的 callback 变量,执行时也就是调用 callback 的 run 方法,注意这里虽然是 Runnable 对象,但是跟 Thread 中的 Runnable 是不一样的,这里仅仅是为了执行 run 方法而已。

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

(2) 第 2 种, 通过 send 方法发送的消息,这个就是我们构造 public Handler(Callback callback) 这样一个 Handler,执行时就会通过 Callback 对象来执行消息。Callback 是一个接口,方法就是我们熟悉的 handleMessage 方法,所以 mCallback.handleMessage(msg) 就是调用这个接口的方法了。

public interface Callback {
        public boolean handleMessage(Message msg);
}

(3) 第 3 种,在 Handler 中 handleMessage 是一个空方法

    public void handleMessage(Message msg) {
    }

也可以进行重写,但是一般情况下,我们使用前两种方式较多。

MessageQueue

Message

介绍消息队列之前,先介绍下 Message。主要看下 Message 中的参数


    public int what;

    public int arg1;

    public int arg2;

    public Object obj;
    
    /*package*/ long when;

    /*package*/ Bundle data;

    /*package*/ Handler target;

    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    /*package*/ Message next;

以上是 Message 中能够携带的参数:

  • 一般我们使用 what 来作为标识,表明是哪种消息
  • arg1 和 arg2 是 int 类型参数
  • obj 是对象类型参数,
  • 当然也可以使用 Bundle
  • when 是指定的消息执行的时刻
  • target 就是我们上面猜测得 Handler 对象
  • callback 使用 post 方法发送消息的 Runnable 对象
  • next 指向下一个消息,可以得知,消息队列中是单链表形式的

MessageQueue

先来看一下消息队列的构造,还记得上面在创建 Looper 时就是创建 MessageQueue 的时刻。

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
}

mQuitAllowed 为 true 时表明消息队列可以退出,意味着为 false 是不能退出,具体这个有什么影响,这里不去探究了。

mPtr = nativeInit() 是一个 native 方法,下面一段话是引自老罗Android应用程序消息处理机制(Looper、Handler)分析这篇文章

(1)在 Java 层,创建了一个 Looper 对象,这个 Looper 对象是用来进入消息循环的,它的内部有一个消息队列 MessageQueue 对象 mQueue;

(2)在 JNI 层,创建了一个 NativeMessageQueue 对象,这个 NativeMessageQueue 对象保存在 Java 层的消息队列对象 mQueue 的成员变量 mPtr 中;

(3)在 C++ 层,创建了一个 Looper 对象,保存在 JNI 层的 NativeMessageQueue 对象的成员变量 mLooper 中,这个对象的作用是,当 Java 层的消息队列中没有消息时,就使 Android 应用程序主线程进入等待状态,而当 Java 层的消息队列中来了新的消息后,就唤醒 Android 应用程序的主线程来处理这个消息。

实际上,java 层 Looper 在底层都能对应到底层,因为是涉及到线程操作,包括等待、唤醒,这些操作需要在底层来完成。

添加消息

消息队列的构造分析之后,接下来看一下如何把消息加入到消息队列当中的。

boolean enqueueMessage(Message msg, long when) {
        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) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                
                // 情况二:根据时间点比较将消息插入到链表中
                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;
    }

分析:将消息加入到消息队列中,实际上就是操作单链表的过程,分为两种情况:

(1)情况一:直接将消息加入到消息队列头部(单链表头部)这种情况的条件是下列条件中的一个:

  • p == null 消息队列为空
  • when == 0 当前需要加入的消息的时间点为0,意味着需要立即执行的消息,所以需要加入到队列头部
  • when < p.when 当前需要加入的消息的时间点比消息队列头部的消息的时间小早,也就是说需要先执行的消息,但不一定立即执行

(2) 情况二:根据时间点比较将消息插入到链表中

典型的单链表插入操作,首先找到根据消息的时间点来找到插入的位置,然后将消息插入到链表中。

取出消息

取出消息是通过 next() 取出消息的,该方法在 Looper 的 loop() 方法中被调用,用来获取需要执行的消息

    Message next() {
    
        ...
            
        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();
            }
            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) {
                    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;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                
            }

            ... 
            
            // 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;
        }
    }

将代码简化了一下,for 循环中主要执行取出消息的操作,首先是找到一个消息,这个消息是最先执行的。找到之后,先看这个消息的时间点,如果到了执行的时间就返回消息,并从链表中删除,返回给 Looper;如果时间还没到就等待,nativePollOnce(ptr, nextPollTimeoutMillis);

什么情况下线程会进入等待状态?两种情况,一是当消息队列中没有消息时,它会使线程进入等待状态;二是消息队列中有消息,但是消息指定了执行的时间,而现在还没有到这个时间,线程也会进入等待状态。消息队列中的消息是按时间先后来排序的。

下面回答下上面的未给出答案的两个问题:

(1)为什么是无限循环,手机不烫手?

(2)Message msg = queue.next(); // might block 可能会阻塞,含义是什么?

线程的等待唤醒底层采用的是管道机制,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。

所以阻塞时,线程处于等待状态,这时候 Linux 系统中的 epoll 机制,并不会占用很高的 CPU 使用率,因此即使是无限循环,也不会导致手机发烫啦…

总结

Handler 中持有 Looper,并通过 Looer 获取 MessageQueue,然后发送消息就是将消息加入到 MessageQueue 中,并且在 Message 中将 Handler 本身携带过去,在 Looper 的 loop 循环中 通过 MessageQueue 的 next 方法 得到消息后,在 Message 中取出 target,即 Handler,然后回调 dispatchMessage,执行我们自定义的方法。

本篇对 Handler 的分析就到这里,如果文中有错误的地方,希望在评论区给出指正!另外如果对 Handler 的底层想进行研究的话,建议看看老罗的文章。

参考

《安卓开发艺术探索》

Android应用程序消息处理机制(Looper、Handler)分析

Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

猜你喜欢

转载自blog.csdn.net/u011371324/article/details/88630383