android Handler sendMessageDelayed实现原理

之前在看分析Handler实现原理的,有注意到sendMessageDelayed这个方法,最终都调用到了MessageQueue的enqueue方法,最终都到MessageQueue里面的next方法,至于具体怎么实现delay的,没有仔细研究,今天记录一下

主要包括2部分的内容:

  • Handler sendMessageDelayed实现原理
  • 延时任务实现的几种方式(针对sendMessageDelayed的一些扩展思考)

Handler的实现原理,可以参考我之前写的 android Handler消息机制实现原理 ,对整体流程有个了解,从消息是怎么从应用到framewokr再到native,以及怎么从native到framework再到应用的handleMessage方法处理的

本文主要分析sendMessageDelay的实现细节,也是穿插在Handler消息机制实现原理,主要区别在于Message.when参数的值不一样,默认是0,delay消息是传入的值+当前时间

    // frameworks/base/core/java/android/os/Handler.java

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
1.Handler sendMessageDelayed实现原理
1.1 Message.when

关于Message的其它几个参数,如what/obj/target等几个参数,平时用得比较多,when参数基本上没怎么用过。先看类里面的定义,不支持App调用

    /**
     * The targeted delivery time of this message. The time-base is
     * {@link SystemClock#uptimeMillis}.
     * @hide Only for use within the tests.
     */
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;

when属性的含义是:分发当前Message的具体时间

具体的使用是在MessageQueue方法里面的,sendMessageAtTime最终会调用到queue.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);
    }
1.2.MessageQueue.enqueueMessage

在enqueueMessage方法里面,会把传入的参数when赋值给Message.when

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            msg.markInUse();
            // when的值为SystemClock.uptimeMillis() + delayMillis
            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;
            }
        }
        return true;
    }

然后把消息加入到MessageQueue的消息队列里面,分2种情况处理

  • 队列为空,直接插入并返回
  • 队列非空,加入队列,然后在一个循环里面来判断是否有消息的时候到了需要唤醒阻塞在nativepollOnce的

mBlocked的定义如下:表示在next方法中是否blocked在pollOnce方法

    // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
    private boolean mBlocked;
1.3.MessageQueue.next

在Looper调用Looper.loop方法时,会阻塞在MessageQueue.next方里面的nativePollOnce方法上

这个方法,是整个Handler消息机制能循环获取消息的核心,利用epoll机制来实现,关于epoll的实现原理,可以在网上找到很多,在这里只是简单的说明一下 来源于linux man epoll_wait

epoll_wait方法的定义如下:

       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);

参数timeout是用来实现延时的核心,原理是当epoll_wait的timeout到期时,会唤醒epoll_wait

A call to epoll_wait() will block until either:

  • a file descriptor delivers an event; // 插入消息走的是这个唤醒
  • the call is interrupted by a signal handler; or
  • the timeout expires. // delay走的是这个唤醒

下面来分析一下,framework的Message.when是怎么传到入epoll_wait的timeout参数上的

    Message next() {
        int nextPollTimeoutMillis = 0;
        for (;;) {

            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) {
                    // 通过when来判断是否应该分发消息
                    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;
                }
            }

            // 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方法,会从消息队列里面取出消息,如果有取到消息,则会在Looper.loop方法里面,把消息分发到对应的Handler去处理

android Handler消息机制实现原理 里面有分析过,当插入一条消息后,会唤醒epoll_wait方法,从native层回到framework的next方法,走nativePollOnce之后的逻辑

从前面的分析可以知道,when的值为SystemClock.uptimeMillis() + delayMillis,以下判断为true

 if (now < msg.when) 

重新设置nextPollTimeoutMillis时间,为我们最初在sendMessageDelayed里面设置的delayMillis

当delay的时间到了后,就和正常的消息处理流程完全一样了

1.4.MessageQueue.nativePollOnce

nativePollOnce是一个native方法,实现是在jni层的android_os_MessageQueue.cpp

// android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

最终调用到system/core/libutils/Looper.cpp的pollInner

int Looper::pollInner(int timeoutMillis) {
    ...
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    ...
}

在epoll_wait方法里面,timeoutMillis设置为在sendMessageDelayed里面设置的delayMillis,然后利用epoll_wait的超时机制来实现延时处理

2. 延时任务实现的几种方式

除了上面的利用epoll_wait的超时机制,还可以使用ScheduledThreadPoolExecutor和Timer来实现

提到延时任务处理,正好想到了最近在看RxJava里面的实现,线程池基本上是利用ScheduledThreadPoolExecutor来实现的

类的定义如下:

/**
 * A {@link ThreadPoolExecutor} that can additionally schedule
 * commands to run after a given delay, or to execute
 * periodically. This class is preferable to {@link java.util.Timer}
 * when multiple worker threads are needed, or when the additional
 * flexibility or capabilities of {@link ThreadPoolExecutor} (which
 * this class extends) are required.
 * /

在ThreadPoolExecutor的基础中扩展了对周期性任务的支持,在多线程场景下,建议使用ScheduledThreadPoolExecutor来替换Timer

ScheduledThreadPoolExecutor由3部分组成:

  • 任务调度控制:ScheduledThreadPoolExecutor,负责任务调度控制,实现了ScheduledExecutorService接口;
  • 阻塞队列:DelayedWorkQueue,作为ScheduledThreadPoolExecutor的内部类,用于缓存线程任务的阻塞队列,仅能够存放RunnableScheduledFuture对象;该队列实现了延迟调度任务的逻辑,如果当前时间大于等于任务的延迟执行时间,任务才可以被调度。
  • 调度任务:ScheduledFutureTask,作为ScheduledFutureTask的内部类,实现了RunnableScheduledFuture,封装了调度任务的执行逻辑。其中的time字段存放下一次执行时间,DelayedWorkQueue会据此判断任务是否可以被执行。period字段存放执行周期,对于周期性执行任务,每次会根据period计算time。

ScheduledThreadPoolExecutor的任务执行分为单次执行和周期性执行

  • 单次执行:通过schedule方法执行的任务属于单次执行任务
public void execute(Runnable command) {
    schedule(command, 0, NANOSECONDS);
}
  • 周期性执行:通过scheduleAtFixedRate、scheduleWithFixedDelay方法执行的任务均为周期性执行任务
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }

DelayedWorkQueue的实现原理:

  1. 基于最小二叉堆实现的优先队列,根据ScheduledFutureTask.compareTo方法比较任务执行时间,使得最近要执行的任务位于队首;
  2. 任务出队时,通过轮询判断任务是否到达执行时间点,ScheduledFutureTask实现了Delayed接口,通过getDelay方法能够获取到任务还有多长时间执行;
  3. 当队列中所有任务都没有到达执行时间时,队列中会维持一个leader线程,用于轮询等待队首任务,其余线程均await()。
  4. ScheduledFutureTask增加heapIndex属性,用于标记任务在堆数组中的索引,从而便于任务的快速查找(是否存在)与取消(删除)

leader线程的实现,使用了**“Leader/Follower” 设计模式** , 一些解释可以参考 explain-leader-follower-pattern

核心思想:
leader线程相当于一个哨兵,其它线程都处于休眠状态,只有这一个线程在监听任务队列,当leader线程A开始工作前,先唤醒一个线程B来充当leader线程,然后线程A执行并完成任务后,进入休眠状态,等待下次被唤醒

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值