欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Handler延时消息是如何实现的?

程序员文章站 2022-07-14 19:02:42
...

首先,需要明确一点,Handler 延时消息机制不是延时发送消息,而是延时去处理消息;举个例子,如下:

handler.postDelayed(() ->{
    Log.e("zjt", "delay runnable");
}, 3_000);

上面的 Handler 不是延时3秒后再发送消息,而是将消息插入消息队列后等3秒后再去处理。

postDelayed 的方法如下:

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

其中的 getPostMessage 就是将 post 的 runnable 包装成 Message,如下:

private static Message getPostMessage(Runnable r) {
    // 使用 Message.obtain() 避免重复创建实例对象,达到节约内存的目的
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

sendMessageDelayed 方法如下:

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    // 延时的时间是手机的开机时间(不包括手机休眠时间)+ 需要延时的时间
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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 的 target 为Handler
    msg.workSourceUid = ThreadLocalWorkSource.getUid();
    // 异步消息,这个需要配合同步屏障来使用,可以看我之前的文章,这里不赘述
    if (mAsynchronous) { 
        msg.setAsynchronous(true);
    }
    // 插入到 MessageQueue 中
    return queue.enqueueMessage(msg, uptimeMillis);
}

MessageQueueenqueueMessage 的方法如下:

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;
            // 表示要唤醒 Hander 对应的线程,这个后面解释
            needWake = mBlocked;
        } else {
            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;
        }
        // 唤醒Handler对应的线程
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

举个例子,假设我们消息队列是空的,然后我发送一个延时10s的延时消息,那么会直接把消息存入消息队列。

从消息队列中获取消息是 通过 Looper.loop() 来调用 MessageQueuenext()方法,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.
     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();
         }
         // 表示要休眠多长时间,功能类似于wait(time)
         // -1表示一直休眠,
         // 等于0时,不堵塞
         // 当有新的消息来时,如果handler对应的线程是阻塞的,那么会唤醒
         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) {
                     // 计算延时消息的剩余时间
                     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;
             }
           
             .......
             // 判断是否有 idle 任务,即主线程空闲时需要执行的任务,这个下面说
     
             if (pendingIdleHandlerCount <= 0) {
                 // 这里表示所有到时间的消息都执行完了,剩下的如果有消息一定是延时且时间还没到的消息; 
                 // 刚上面的 enqueueMessage 就是根据这个变量来判断是否要唤醒handler对应的线程
                 mBlocked = true; 
                 continue;
             }       
         
        ......
     }
 }

其实,从这里就可以看出来,Handler 的延时消息是如何实现的了。

比方说 发送一个延时10s的消息,那么在 next()方法是,会阻塞 (10s + 发送消息时的系统开机时间 - 执行next()方法是系统的开机时间),到达阻塞时间时会唤醒。或者这时候有新的消息来了也会 根据 mBlocked = true来唤醒。

IdleHandler是什么?

MessageQueue 类中有一个 static 的接口 IdleHanlder

public static interface IdleHandler {
    boolean queueIdle();
}

MessageQueue中无可处理的Message时回调; 作用:UI线程处理完所有事务后,回调一些额外的操作,且不会堵塞主进程;

接口中只有一个 queueIdle() 函数,线程进入堵塞时执行的额外操作可以写这里, 返回值是true的话,执行完此方法后还会保留这个IdleHandler,否则删除。