Handler 笔记总结

本次涉及到的源码为 Source for Android 27.

补充:聊一聊Android的消息机制(里面对于 Message Barrier 和 Looper 的阻塞讲得比较清楚)


首先,需要明确的就是,Handler 的主要作用就是将一个任务切换到某个指定的线程中去执行。

切换到指定的线程 ,说白了就是在线程中调用某方法,即可切换到指定线程,例如一个方法 m1(),在线程 A 中被调用执行,那么就是切换到了 A,在线程 B 中被调用执行,那就是切换到了 B。


对于 Handler 的实现,主要涉及到的有 ThreadLocalMessageQueueLooper

其中,有关于 ThreadLocal 可以参阅:ThreadLocal 实现原理总结


1、MessageQueue

MessageQueue 是一个消息队列(虽然内部是基于单链表的数据结构实现的),用于存储 Handler 发送的 Message。主要涉及到的操作就是 enqueueMessage(),往消息队列中插入一条消息next(),从消息队列中读取一条消息并从消息队列中移除该消息

源码部分的注释参阅自:Android MessageQueue源码分析
该文章会介绍 MessageQueue 消息的插入 (enqueueMessage) 和读取 (next),native 层的消息机制,以及 IdleHandler 和 SyncBarrier 的逻辑原理。

1.1、boolean enqueueMessage(Message msg, long when)
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;// mMessages 指向链表的第一个 Message 节点
        boolean needWake;
        // 如果队列为空,或者当前处理的时间点为0(when 的数值,when 表示 Message 将要执行的时间点),
        // 或者当前 Message 需要处理的时间点先于队列中的首节点,那么就将 Message 放入队列首部
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;//将 msg 插入到首部
            mMessages = msg;//将当前 msg 复制给成员变量 mMessages,也就是将首节点赋值给 mMessages
            needWake = mBlocked;
        } else {
            // 否则遍历队列中 Message,找到 when 比当前 Message1 的 when 大的 Message2,
            // 将 Message1 插入到该 Message2 之前,如果没找到则将 Message1 插入到队列最后。
            // 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) {
                    //最终还是会退出该 for 循环
                    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.
        // 判断是否需要唤醒,一般是当前队列为空的情况下,next 那边会进入睡眠,需要enqueue这边唤醒 next 函数
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

enqueueMessage()总的来说就是将 msg 插入到队列中,涉及到两个参数,第一个好理解,而第二个参数则与使用 handler.sendMessageAtTime(msg,1000); 等时设置的延迟时间有关,在将 msg 插入消息链表的时候,会赋值给 message.when,会根据设置的 when 的大小来选择插入的节点,总的就是 when 越小越在前。

注意,msg 在队列中的排序是以该 msg 的 SystemClock.uptimeMillis()+delayMillis (即上面所说的 when)时间作为依据的。其中 SystemClock.uptimeMillis() 是自系统启动以来(不包括 deep sleep 的时间),delayMillis 则是发送一个 msg 时设置的延迟时间。

其中,与 System.currentTimeMillis() 的区别是后者是从 1970 年 1 月 1 日 UTC 到现在的毫秒数。

1.2、Message next()
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    /* 第一步:初始化操作,如果 mPtr 为 null,则直接返回 null,设置 nextPollTimeoutMillis 为 0,进入下一步。 */
    final long ptr = mPtr;// mPrt 是 native 层的 MessageQueue 的指针
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    
    //无限循环的
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        
		/* 第二步:调用 nativePollOnce。
		nativePollOnce 有两个参数,第一个为 mPtr 表示 native 层 MessageQueue 的指针,
		第二个参数 nextPollTimeoutMillis 表示超时返回时间(-1 表示一直等待,0 立刻返回)。
		调用这个 nativePollOnce 会等待 wake,如果超过 nextPollTimeoutMillis 时间,
		则不管有没有被唤醒都会返回。*/
        nativePollOnce(ptr, nextPollTimeoutMillis); // jni 函数
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            /* 第三步:获取队列的头 Message(msg),如果头 Message 的 target 为 null,
            	则查找一个异步 Message 来进行下一步处理。
               当队列中添加了同步 Barrier 的时候 target 会为 null */
            Message prevMsg = null;
            Message msg = mMessages;//先赋值为消息链表的首节点
            
            // target 正常情况下都不会为 null,在 postBarrier 会出现 target 
            // 为 null 的 Message
            // 如果设置了 postBarrier() 则就进入到该段逻辑
            // 在屏幕刷新机制中,就会触发该段逻辑,因为与屏幕刷新有关的 msg 是异步的
            // 且在将异步 msg 添加到主线程的 messageQueue 之前会触发 postBarrier()
            // 之后就会通过这段逻辑取出与屏幕刷新有关的异步 msg 并进行处理
            if (msg != null && msg.target == null) {// msg.target 即发送该 msg 的 Handler 的引用,一般情况下不为 null
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
                // 取到异步消息则会退出循环
            }
            /* 第四步:判断上一步获取的 msg 是否为 null,为 null 说明当前队列中没有 msg,
               设置等待时间 nextPollTimeoutMillis 为 -1。
               (当 nextPollTimeoutMillis == -1 就会导致下一次轮询时在
               第二步因 jni 函数导致 sleep)
               实际上会等待 enqueueMessage 的 nativeWake 来唤醒。
               (可参阅 1.1 enqueueMessage() 最后那一部分的注释)
               如果非 null,则下一步。 */
            if (msg != null) {
                /* 第五步:判断 msg 的执行时间 (when) 是否比当前时间 (now) 的大,如果小,则将 msg 从队列中移除,并且返回 msg,结束。
                   如果大则设置等待时间 nextPollTimeoutMillis为(int) Math.min(msg.when - now, Integer.MAX_VALUE),
                   执行时间与当前时间的差与 MAX_VALUE 的较小值。执行下一步 */
                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;
            }
            /* 第六部:判断是否 MessageQueue 是否已被设置为退出状态,
            // true 则返回 null,否则下一步 */
            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }
            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            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 (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        /* 第七步:运行 idle Handler,idle 表示当前有空闲时间的时候执行,而运行到这一步的时候,
           表示消息队列处理已经是出于空闲时间了(队列中没有符合条件的 Message,
           ,因为有 barrier msg 时如果遍历了整个队列也没有找到异步 msg,而此时可能
           有同步 msg,或者头部 Message 的执行时间(when)在当前时间之后)。
           如果没有 idle,则继续第二步,如果有则执行 idle Handler 的 queueIdle 方法,
           我们可以自己添加 Idle Handler 到 MessageQueue 里面(addIdleHandler 方法),执行完后,回到第二步。 */
        // 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);
                }
            }
        }
        // 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() 方法内部有一个 for(,,) 无限循环的方法,在正常情况下,如果消息队列里面没有消息,则会因为该循环阻塞住(这里会利用底层技术实现 sleep,参阅详细的注释部分,而不是简单粗暴的一直循环,猜测是为了避免性能的浪费),而如果能够正常的取到 message 则会返回该 message,并将其从消息队列中移除。

如果没有要处理的 msg,则会在下一次循环前处理 IdleHandler,即使得队列能在即将进入阻塞状态(在下一轮循环时会调用 nativePollOnce() 方法 )之前做一些动作,这些动作可以称为 Idle 动作。

这个函数里的for循环并不是起循环摘取消息节点的作用,而是为了连贯“当前时间点”和“处理下一条消息的时间点”。简单地说,当“定时机制”触发“摘取一条消息”的动作时,会判断事件队列的首条消息是否真的到时了,如果已经到时了,就直接返回这个msg,而如果尚未到时,则会努力计算一个较精确的等待时间(nextPollTimeoutMillis),计算完后,那个for循环会掉过头再次调用到nativePollOnce(mPtr,
nextPollTimeoutMillis),进入阻塞状态,从而等待合适的时长。
摘抄自:聊一聊Android的消息机制

在循环中,会计算 nextPollTimeoutMillis 的值,即根据实际来计算其值,具体有几种情况:
(1)当前时间小于取到的 msg 的触发时间,则设置为 (int) Math.min(msg.when - now, Integer.MAX_VALUE)
(2)队列中没有 msg,则设置为 -1
(3)如果有要处理的 idle handler,则设置为 0
然后在下一轮循环中就会根据该值调用 nativePollOnce() 方法进行相应时间的休眠。


2、Looper

Looper 则是扮演消息轮询的角色,内部维护着一个 MessageQueue 实例,它会不停的利用 messageQueue.next() 从消息队列中取 Message,如果有取到则会立即进行处理,否则就会因为 messageQueue.next() 内部的实现机制而被阻塞在那里。

Handler 在使用的时候,需要在对应的线程里面有实例化的 Looper,否则就会报错。

new Thread("SubThread") {
    @Override
    public void run() {
    	// 在目标线程中创建 Looper 实例(因为是在目标线程中调用的该方法)
    	// 然后会把该实例保存在 Looper 的静态成员变量 sThreadLocal 中
        Looper.prepare(); 
        // 在目标线程中创建 Handler 实例,
        // 该构造函数内部会把当前线程的 Looper 借助 Looper.sThreadLocal 传递给该 Handler 实例
        Handler handler = new Handler();
        Looper.loop();
		
		// 需要等 Looper.loop() 执行完后才能执行
		Log.d("TAG", "After Looper.loop()");
    }
}.start();
2.1、Looper.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");
    }
    // 实例话一个 Looper 实例并通过 sThreadLocal 保存
    sThreadLocal.set(new Looper(quitAllowed));
}
2.2、Looper.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();
    for (;;) {
        //正常情况下,queue.next() 如果不能返回 msg,则会因内部的无限循环以及具体的实现逻辑堵塞在该处
        Message msg = queue.next(); // might block
        //queue.next() 返回 null 会使得 loop() 方法结束
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            //利用 msg 持有的 target(也就是发送该 msg 的 Handler)对 msg 进行处理
            //正常情况下,因为 loop() 方法是在目标线程中被调用
            //进一步的这里就会在目标线程中调用
            //因此就实现了线程的切换
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        msg.recycleUnchecked();
    }
}

loop() 比较容易理解,其内部也有一个无限循环,目的是为了不断的从消息队列中去取 Message 进行处理。需要注意的是,loop() 内部的无限循环的阻塞是因为执行到

Message msg = queue.next();

时,无法从 queue.next() 内部退出而发生。

如果通过 messageQueue.next() 取到了 message,则会通过 message 持有的 target(即发送该 messagehandler 实例的引用) 调用该 handler 实例的 dispatchMessage(msg) 方法,从而实现了线程的切换。

回顾前面说的 切换到指定的线程 的本质,这里因为 loop() 方法是在 SubThread 中调用的,因为 handler.dispatchMessage(msg) 也就在 SubThread 线程中被调用。

2.2、Looper 的退出

一个 Looper 实例可以通过 quit() / quitSafely() 退出。如:

// 直接退出
handler.getLooper().quit();
// 设定一个标记,等把消息队列设置标志之前已有的消息处理后才退出
handler.getLooper().quitSafely();`在这里插入代码片`
// Looper.java
public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
    mQueue.quit(true);
}
// MessageQueue.java
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

可以看到,调用 Looper 的退出方法,实际上就是调用内部的 MessageQueuequit() 来通知消息队列退出。

MessageQueue#quit() 中,会将 mQuitting 标记为 true,然后根据 safe 的值来移除对应的 msg。

safe 为 true,则是通过 removeAllFutureMessagesLocked() 来移除在当前时刻之后触发的那些 msg,因此当前时刻之前的 msg 还是会被处理完。(因为存在一种情况,假设当此时此刻为 t1,然后线程对应的 handler 正在处理 msg1,处理过程有点耗时,之后还有 msg2、msg3 等待处理,且实际其触发时刻应该应该是小于 t1,只不过由于 msg1 的耗时而一直被耽误,所以此时如果调用 mQueue.quit(true),msg2、msg3 是不会被移除,而是会等到被处理完。)

而当 safe 为 false 时,会直接移除所有的队列中所有的 msg。

另外当消息队列被标记为退出状态时,如 next() 方法中,如果遍历了消息列表之后,已经没有 msg 要处理了,此时就会返回 null,而不是阻塞在该方法中,因此会进一导致 Looper#loop() 方法 return,因此线程就会继续执行调用 Looper#loop() 之后的逻辑。

Looper 退出后,调用 Handlersendpost 方法会返回 false

在子线程中,如果手动创建了一个 Looper,则应在不需要用了之后主动调用退出的方法来终止消息循环,否则这个子线程就会一直处于等待状态,而如果 Looper 退出了,则在线程内部才能正常执行 Looper.loop() 之后的逻辑。

而且需要注意,在 MessageQueue#quit() 中会判断是不是退出主线程,是的话则会抛出异常。


3、Handler

3.1、发送 Message

Handler 可以通过一些列的 send 或者 post 方法来发送 Message

对于 post 方法,实际上还是会将传递的 runnable 封装成一个 message 实例,并将 runnable 赋值给 message.callback

然后,send 或者 post 方法都会走到:

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

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

很简单,将 message 插入到内部维护的 messageQueue 中。

3.2、处理 Message

在 2.2 中有说,Looper.loop() 方法内部会将从 messageQueue 中取到的 message 交由对应的 handler 进行处理。

msg.target.dispatchMessage(msg);

HandlerdispatchMessage() 也很简单。

// Handler.java
public void dispatchMessage(Message msg) {
	//	通过 handler.post() 传递的 callback
    if (msg.callback != null) { 	
        handleCallback(msg);
    } else {
   		// handler 实例内部的 mCallback 成员变量
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                // 如果 mCallback.handleMessage() 返回 true 则 return
                return;
            }
        }
        // 最后才轮到重写的 handleMessage()
        handleMessage(msg);
    }
}

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

Callback 的意义在于可以用创建一个 Handler 的实例但不需要派生 Handler 的子类。而直接通过构造方法来传递一个 Callback 来实现对 msg 的处理。

下面就是派生子类的实现实例。

// 需要实现一个 Handler 的子类,或者直接实例化一个匿名类。
class SunHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
}

而传递 Callback 示例如下:

// 在 Handler 的构造方法中传递 Callback 对象
new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        return false;
    }
});

另外,通过 Callback 来处理还有一个好处,就是如果对于多个 Handler 的处理 msg 的逻辑是一样的话,则可以传递同一个 Callback 对象,减少代码量。

不过要注意,前面说过的,如果 Callback#handleMessage() 返回 false,则会继续调用 Handler 自身的 handleMessage() 方法。

3.3、为 Handler 的构造函数

1、通过 Handler(looper) 可以在实例化时指定 Looper

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

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

2、使用默认构造函数 Handler() 时如果目标线程没有 Looper 会出错,而平时在 UI 线程中直接实例化时正常,是因为 UI 线程早就有来一个 MainLooper

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

public Handler(Callback callback, boolean async) {
    ...
    // 先获取对应线程的 Looper
    mLooper = Looper.myLooper();
    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;
}

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

在这里插入图片描述
根据前面的你内容,可以了解到,一个线程之后对应唯一的一个 Looper,从而对应唯一的一个消息队列,而对于一个线程来说,可以对应多个 Handler,这数个 Handler 共用唯一的消息队列。


部分内容参阅自《Android 开发艺术探索》

扩展阅读:你知道android的MessageQueue.IdleHandler吗?


补充

2019.06.23

主线程的死循环一直运行是不是特别消耗 CPU 资源呢?

并不是,这里就涉及到 Linux pipe/epoll 机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种 IO 多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质是同步 I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。

摘抄自:https://github.com/JsonChao/Awesome-Android-Interview/blob/master/Android%E7%9B%B8%E5%85%B3/Android%E5%9F%BA%E7%A1%80%E9%9D%A2%E8%AF%95%E9%A2%98.md#%E4%B8%BB%E7%BA%BF%E7%A8%8B%E7%9A%84%E6%AD%BB%E5%BE%AA%E7%8E%AF%E4%B8%80%E7%9B%B4%E8%BF%90%E8%A1%8C%E6%98%AF%E4%B8%8D%E6%98%AF%E7%89%B9%E5%88%AB%E6%B6%88%E8%80%97cpu%E8%B5%84%E6%BA%90%E5%91%A2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值