从源码读懂 Handler 的原理

一、Handler 的工作原理

Handler 的正常运作依赖于三个组件:MessageQueue,Handler,Looper。

MessageQueue 是一个消息队列,用于存放消息(Message),当我们创建 Handler 时,Handler 会持有一个 Looper,Looper 中有一个死循环 loop ,loop 会不断的从消息队列中取出消息并进行处理。

我们通过 Handler 的 sendMessage(),removeMessages() 等方法向消息队列添加或移除消息,通过 handleMessage() 对消息进行具体的处理。

Handler 构造函数:

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

    mLooper = Looper.myLooper();  //Looper对象
    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 的 handleMessage 方法:

/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}

Looper 中的 loop 方法:

public static void loop() {
    ...

    for (;;) {
        //loopOnce 会从消息队列中取出消息并处理
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

二、Handler 中 postDelay 方法的原理

首先查看 Handler 中 postDelay() 方法的源码:

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

接着看 sendMessageDelayed 方法中:

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
  • 注意这里用的是 SystemClock.uptimeMillis(),这个值记录了系统启动到当前时刻经过的时间。但是系统深度睡眠(CPU睡眠,黑屏,系统等待唤醒)之中的时间不算在内。

继续看 sendMessageAtTime 方法:

public boolean sendMessageAtTime(@NonNull 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);
}

接着看 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);
}

到这里可以看到,Handler 中并没有做什么延时操作,而是直接将消息放进了消息队列中,接下来看一下 MessageQueue 中 enqueueMessage 的具体实现:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    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;
        Message p = mMessages;
        boolean needWake;     //注意此处的 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;
}

可以看到 MessageQueue 中 enqueueMessage 也没有延时操作,只是按时间顺序将消息插入到消息队列中;由此我们可以得出一个结论,postDelay 并不是采用延时发送消息的方式实现的。

既然不是在消息的发送上做延时,那接下来就看看处理消息的位置,Looper 中的 loopOnce 函数:

private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }

    ...
}

可以看到 loopOnce 就是从消息队列取出消息并处理该消息,但是我们注意到 MessageQueue 的 next 方法这里有个可能阻塞的注释,看看next 方法做了什么:

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

            ...
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;   //不需要处理消息的时候设置为true
                continue;
            }
        }

        ...
    }
}

在这段代码中,我们主要关注 nativePollOnce 方法和 nextPollTimeoutMillis 变量,nativePollOnce 方法的作用类似于 object.wait(),作用就是阻塞:当队列中的消息需要处理时,设置 nextPollTimeoutMillis 值为 0,正常进行消息处理;当队列中的消息还没到达处理时间时,对 nextPollTimeoutMillis 进行赋值,设置定时器并阻塞;当队列中没有消息时,设置 nextPollTimeoutMillis 值为 -1 并阻塞。

到这里 postDelay 的具体实现方式我们就已经了解了,但是这个消息处理机制还缺少一个唤醒步骤,刚才说到当队列中没有消息或者消息还没到达处理时间的时候线程会进入阻塞状态,有阻塞就会有唤醒,不然新消息到来时就没法继续处理了,根据这个推断我们重新看回消息的入队代码:

boolean enqueueMessage(Message msg, long when) {
    ...
    synchronized (this) {
        ...
        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;
}

可以看到在将消息插入消息队列的队头时会将 mBlocked 赋值给 needWake,如果此时消息队列的 next 正在阻塞线程(即 mBlocked 为 true),就会通过 nativeWake 去唤醒线程。

为什么不采用延时发送消息的方式实现 postDelay 方法

因为如果采用延时发送消息的方式,需要对每一条延时消息都设置一个定时器,而采用这种设计方式最多只需要一个定时器,方便统一管理。

三、在线程中新建 Handler 之前为什么要先调用 Looper.prepare()

我们知道 Handler 的工具原理就是从通过一个死循环 loop 从消息队列中不断的取出消息并处理,而在默认情况下线程中是不存在 loop 的,因此如果我们想要在线程中使用 Handler,就必须先调用 Looper.prepare() 为线程创建一个循环,我们看一下 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");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

可以看到 prepare 方法的内容非常简单,就是为 sThreadLocal 设置了一个 Looper,即为线程创建一个循环,接下来我们看看创建 Handler 的方法:

public Handler(@Nullable 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;
}

可以看到在创建 Handler 的时候调用 Looper.myLooper() 去获取一个 Looper 对象,如果为空则抛出异常,我们看看这个 Looper.myLooper() 方法中做了什么:

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

可以看到 myLooper 方法就是返回在 Looper.prepare() 方法中创建并设置给 sThreadLocal 对象的 Looper,在 sThreadLocal 的定义处我们也可以看到如果没有调用 prepare 方法为线程设置 Looper,则会返回一个 null 对象,此时 Handler 会抛出异常。因此在线程中使用 Handler 必须先调用 prepare 方法。

四、为什么在主线程新建 Handler 不需要先调用 Looper.prepare()

从上文的分析可以知道创建 Handler 之前是一定需要创建循环的,但在主线程中创建 Handler 时却没有这一步操作,那么只能是有人替我们完成了这一步操作。考虑 Handler 对于一个应用的重要性,我们推测 Looper 的初始化时机应该很早,所以我们从应用的入口:ActivityThread.java 的 main 方法看起:

public static void main(String[] args) {
    ...

    Looper.prepareMainLooper();

    ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                                            LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到在 main 方法中调用了 Looper.prepareMainLooper() 方法,看看这个方法做了什么:

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

可以看到 prepareMainLooper 方法中替我们调用了 prepare 方法初始化了一个 Looper 对象,并且将这个 Looper 设置为了应用的 sMainLooper。因此我们可以用 Looper.getMainLooper() == Looper.myLooper() 来判断当前线程是否是主线程。

五、主线程的 loop 是一个死循环,为什么不会发生 anr

回答此问题首先要明确 anr 的定义:anr 是指应用在一段时间内对用户的操作无响应,对于前台界面来说,5s 内没有响应用户的输入事件就会发生 anr。从定义可以知道,anr 的原因是对用户的操作无响应,而主线程中的 loop 并不会导致对用户操作无响应,相反,正是因为这个循环的存在,应用才能不断的处理事件,当循环退出时,应用就关闭了。

六、一个线程有两个 Handler 的时候,其中一个 Handler 调用 removeCallbacksAndEqualMessages() 是否会影响另一个 Handler

首先看到 Handler 中的 removeCallbacksAndEqualMessages 方法:

public final void removeCallbacksAndEqualMessages(@Nullable Object token) {
    mQueue.removeCallbacksAndEqualMessages(this, token);
}

可以看到该方法直接调用了 MessageQueue 的 removeCallbacksAndEqualMessages 方法,我们接着看 MessageQueue 中的 removeCallbacksAndEqualMessages 方法:

void removeCallbacksAndEqualMessages(Handler h, Object object) {
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.
        while (p != null && p.target == h //此处判断了消息的目标是哪一个Handler
               && (object == null || object.equals(p.obj))) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && (object == null || object.equals(n.obj))) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

可以看到在移除消息的时候会判断消息的目标,因此两个 Handler 使用同一个消息队列的时候并不会互相影响。

七、一个线程有两个 Handler 的时候,Looper 在处理消息的时候如何进行分配

我们看到实际处理消息的 loopOnce 函数:

private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
    ...
    try {
        msg.target.dispatchMessage(msg); //调用消息中储存的 Handler对象
        if (observer != null) {
            observer.messageDispatched(token, msg);
        }
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
    } catch (Exception exception) {
        if (observer != null) {
            observer.dispatchingThrewException(token, msg, exception);
        }
        throw exception;
    } finally {
        ThreadLocalWorkSource.restore(origWorkSource);
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }
    ...

    return true;
}

再看到 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);
    }
}

public void handleMessage(@NonNull Message msg) {
}

可以看到在处理消息时通过消息中储存的 Handler 对象将消息直接分配给该 Handler 对象处理:

八、为什么创建 Message 对象的时候推荐优先使用 obtain() 获取

我们知道线程中有线程池的概念,在 message 中也有类似的概念,叫做消息池。在 Message 中定义了以下类变量和成员变量:

public final class Message implements Parcelable {
    ...
    // sometimes we store linked lists of these things
    Message next;
    //消息池的链头
    private static Message sPool;
    //消息池的大小
    private static int sPoolSize = 0;
    //消息池的最大上限
    private static final int MAX_POOL_SIZE = 50;
    ...
}

可以看到消息池实际上是一条由消息组成的链表,接下来我们看到 obtain 方法的实现:

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

可以看到当消息池不为空的时候,会返回消息池中已经创建好的消息,当消息池为空时才创建新消息,因此相对于直接创建消息可以提高对象的利用率。

有从消息池取消息的操作,那当然就存在着往消息池放消息的操作,我们看到 recycleUnchecked 方法:

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

可以看到此方法会清楚 message 附带的信息,并将消息放进消息池中。我们调用 Handler 的 removeMessage 方法,或者 Looper 中处理过的消息都会通过 recycleUnchecked 回收到消息池中,从而提高对象的利用率。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值