Handler介绍

925 阅读5分钟

Handler是什么

概述

Android Framework提供的一个基础组件,用于线程间通信。主要是子线程UI更细消息传递给主线程,从而主线程更新UI。

Android 主线程的UI,只能主线程更新。 如果多个线程都能更新,势必要「加锁」,还不如采用「单线程消息队列机制」

主线程内部维护一个循环。没有消息时候,这个循环是阻塞的。新来消息(或者阻塞timeout)时会唤醒,接着处理新到来消息。

Java层Handler

截屏2022-02-24 下午9.39.51.png 整个handler可以粗略的分为以下:

  • 等待读消息阶段: Prepare Looper与MessageQueue阻塞轮询
  • 写消息阶段: Message封装、入队列
  • 处理消息阶段: Message分发处理

1.Prepare Looper与MessageQueue阻塞轮询

使用HandlerThread构建Handler为例,

val handlerThread = HandlerThread("name-xx")
handlerThread.start()
var  threadHandler = Handler(handlerThread.looper)

分析HandlerThread内部实现,发现当调用HandlerThread.start()随即调用run(),内部几乎都是对Looper的使用。

public class HandlerThread extends Thread {
    Looper mLooper;
    
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Looper.loop();
    }

    public Looper getLooper() {
        synchronized (this) {
            while (mLooper == null) { wait(); }
        }
        return mLooper;
    }

1.1 Looper.prepare()

基于MessageQueue的 event loop,存储Looper到当前线程局部变量

public final class Looper {
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  
    final MessageQueue mQueue;
    
    public static @Nullable Looper myLooper() {return sThreadLocal.get();}

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
    }

    //初始化,Thread改造为Looper可用。
    //loop()方法前必须调用prepare()
    public static void prepare() {
        prepare(true);
    }
    
    //这里要注意,非主线程的Looper是允许退出的,而主线程的Looper是不允许退出的
    private static void prepare(boolean quitAllowed) {
    //不管怎样ThreadLocal的Looper对象只能初始化一次,所以prepare只能调用一次
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    //不允许退出,要不然主线程的事件得不到处理,就要ANR了
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
        sMainLooper = myLooper();
    }
}

1.2 Looper.loop()

消息的逻辑还是在MessageQueue中

public final class Looper {
    /**
     * 在线程中运行消息队列
     */
    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; 
        for (;;) { 
            // 1.阻塞,直到next有返回值。 
            Message msg = queue.next(); 
            //2.后面提到的消息分发处理 
            msg.target.dispatchMessage(msg); 
            msg.recycleUnchecked();
        }
    } 
}

题外话:主线程的Looper是在哪启动的?

//管理进程主线程的各种操作
public final class ActivityThread {
    //这个方法是应用进程的入口方法,应用进程创建后立即运行。
    public static void main(String[] args) {
        Looper.prepareMainLooper();
        Looper.loop();
    }
}

1.3 MessageQueue.next()

就堵塞在这了nativePollOnce(ptr, nextPollTimeoutMillis)。 此处没有用java中的wait/notify堵塞,而是通过Linux的epoll机制来堵塞,原因是需要处理 native侧 的事件。 没有消息时堵塞并进入休眠释放CPU资源,有消息时再唤醒线程。 epoll参考:epoll的本质

class MessageQueue {

    private long mPtr; // used by native code
    
    Message next() {
        /**等于0时,不堵塞,立即返回; 
        *大于0时,最长堵塞等待时间,期间有新消息进来,可能会了立即返回(立即执行);
        *等于-1时,无消息时,会一直堵塞;
        */
        int nextPollTimeoutMillis = 0;
        for (;;) {
            //阻塞在此,超时或者被唤醒时,执行接下来的代码
            //ptr是啥?实际指向NativeMessageQueue对象
            nativePollOnce(mPtr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null) {
                    if (now < msg.when) {
                    	//我们发现拿到的第一个消息,执行时间还没到,那我们计算下超时时间,用于下一次的nativePollOnce
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.提取这个消息,并以下一个消息重新作为消息头
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            }
        }
    }
}

2.Message封装、入队列

我们先来看发送消息,这是我们最简单的一种发送消息的方式。

//构建一个处理message的handler
val handler = object : Handler() {
     override fun dispatchMessage(msg: Message) {
          Log.d("alvin", "msg.what:${msg.what}")
      }
}
var msg = Message().also { it.what = 1 }
handler.sendMessageDelayed(msg,1000L)

稍稍追踪下代码我们发现,有很多sendMessage方式,都统一到一个方法中即

//Handler.java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;//这个分发用的,省略了一下其他代码
        return queue.enqueueMessage(msg, uptimeMillis);
}

接着调用到MessageQueue.enqueueMessage(Message msg, long when),代码在下面, 解释下,我们有一个单链表message,message有next指向下一个message,以及此message触发时间when。单链表是有顺序的,when越小,就越靠前。 根据when,新message插入合适位置,如果恰好插在队首,则需要唤醒nativeWake。所以归结下来两件事情:入队和唤醒。截屏2021-03-25 下午9.09.52.png

public final class MessageQueue {
    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            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 {
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when)  break;
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            if (needWake) nativeWake(mPtr);
        }
        return true;
    }
}

3. Message分发处理

在Looper.loop()中执行了msg.target.dispatchMessage(msg).

msg.target就是我们的Handler对象。早在Handler.enqueueMessage(msg,...)时,我们执行了msg.target = this; 这里要区分下 post(message)sendMessage的不同处理方式

class Handler{
    public void dispatchMessage(@NonNull Message msg) {
        //msg.callback跟踪下代码。哦,原来是post(Runnable),时候构建一个带callback的message
        if (msg.callback != null) {
            handleCallback(msg);//-->message.callback.run();
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //我们并没有给handler设置mCallback,所以执行handleMessage
            handleMessage(msg);
        }
    }
    
    //post(Runnable),时候构建一个带callback的message
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
}

4.Java层 其他

关于同步屏障SyncBarrier如下:

  • Message分为3种:同步消息、异步消息、同步屏障消息。
  • 同步屏障消息也是一种消息,可以插入到MessageQueue中,位置同样取决于Message.when。区别于不同消息,同步屏障消息没有target,因为不用处理回调。
  • 同步屏障消息作用为:
    • 对于when小于同步屏障消息的消息,处理方式不变

    • 对于when大于同步屏障消息的消息,优先选取异步消息处理,如果此异步消息时间未到,则继续轮询等这个异步消息。

虽然主流程没有了问题,但我们省略了很多代码,可以作为问题来探索

  • Looper.loop() 能退出么,如果一直没有消息的话,阻塞在那,岂不是会anr?
  • Message.obtain(),消息缓存池
  • Handler.removeCallbacksAndMessages()

Native层Handler

消息轮询机制是通过MessageQueue.nativePollOnce()阻塞和通过MessageQueue.nativeWake等方式唤醒。

MessageQueue通过mPtr变量保存NativeMessageQueue对象,从而使得MessageQueue成为Java层和Native层的枢纽,既能处理上层消息,也能处理native层消息;下面列举Java层与Native层的对应图。 要注意的是Java层和C++层分别实现了一套Handler机制代码。 image.png

Looper.prepare()

Java层:new Looper()-->new MessageQueue()-->mPtr = MessageQueue.nativeInit() -->native层:android_os_MessageQueue_nativeInit()-->new NativeMessageQueue()-->new Looper(false) 最终走到了Looper.cpp的构造方法

Looper::Looper(bool allowNonCallbacks)
    rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;//监听mWakeEventFd有写入数据。
    eventItem.data.fd = mWakeEventFd.get();
    //创建 eventpoll 对象,返回一个 epfd,即 eventpoll 句柄。
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
}

要说 epoll_ctl()干嘛用的,我也不太会了, 可以参考。

Android Handler 中的 epoll 机制

如果这篇文章说不清epoll的本质,那就过来掐死我吧!

Looper.loop()

从Java层分析,一顿操作进入到了Looper::pollInner()

Looper.loop()
    -->MessageQueue.nativePollOnce(mPtr,timeout)
进入native层:
android_os_MessageQueue_nativePollOnce()
    -->NativeMessageQueue::pollOnce()
        -->Looper::pollOnce()
            -->Looper::pollInner()
//platform/system/core/libutils/Looper.cpp
int Looper::pollInner(int timeoutMillis) {
    //1.timeoutMillis处理,省略...
    //2.开启等待,监听mWakeEventFd写入事件,省略...
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    //Looper::wake()后执行到这。
    //3.后面等唤醒后执行,先执行native层添加的message,逻辑与java层处理消息类似
     while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
           		sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                handler->handleMessage(message);
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }
    //4.走回java层,MessageQueue.nativePollOnce(),执行java层的消息提取。
}

Handler.sendMessage()

根据前面Java层分析,一顿操作进入Looper::wake()

MessageQueue.nativeWake()
进入native层:
android_os_MessageQueue_nativeWake()
    -->NativeMessageQueue::wake()
        -->Looper::wake() 
//platform/system/core/libutils/Looper.cpp
void Looper::wake() {
    uint64_t inc = 1;
    //往mWakeEventFd里面写了1,随后进入到Looper::pollInner(),唤起”epoll_wait“,继续执行。
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
}

Native层总结

与Java层类似,但同时我们省略了很多代码,带着问题继续看代码

  • Looper不仅仅用来处理Handler,还有其他作用,看看MessageQueue.addOnFileDescriptorEventListener代码
  • ALooper.cpp是啥

参考

换个姿势,带着问题看Handler

Android消息机制1-Handler(Java层)

Android Handler 中的 epoll 机制

如果这篇文章说不清epoll的本质,那就过来掐死我吧!