美文网首页
Handler 源码解析

Handler 源码解析

作者: 我也不知道aq | 来源:发表于2021-09-23 00:28 被阅读0次

Handler 相关

[TOC]

介绍

Android 主要是靠事件驱动的,而Handler在其中扮演重要的角色

Handler 是Android 中的一种消息的发送、处理机制.是Android中开发中常用的机制,在这简单说明下.

与之密切相关的有Looper,Message,MessageQueue.

整个机制的流程:

大致为如下

--> 发送消息-->添加到消息队列-->消息队列取出消息--->处理消息

整体的结构类似于生活中常见的传送带结构

可以带着以下几点问题

  1. 什么是消息,如何处理消息的;
  2. 如何发送消息,消息如何添加到消息队列中,
  3. 消息队列中如何取出消息,按什么样的规则
  4. 一个线程有几个Looper,怎么实现的不同线程间的通信.
  5. 异步消息是什么?
  6. 主线程中的Lopper中死循环,为何不会造成ANR

术语说明

消息(msg) : Message

Message

  即Handler 发送与处理的对象。内部存在一个target 保存发送处理此msg的handler

节选部分关键源码

public int what;
@UnsupportedAppUsage
/*package*/ Handler target;
// sometimes we store linked lists of these things
@UnsupportedAppUsage
/*package*/ Message next;
private static Message sPool;
public long when;

从源码中我们可以很明显的看出,Message是一种链式的结构,next 指向下一个Message,

其中target 是Handler 类型,也正是我们处理消息的对象。

一种很典型的命令模式

Handler

  首先看下Handler,发送处理消息都是通过handler进行的,所以可以看下handler的构造方法,

一般情况最好自己传入声明的Looper,Handler 与 传入的Looper绑定

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

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

Looper

Looper 负责管理MessageQueue,在内部通过不断地取出msg,分发给handler 处理。

Looper 构造方法中创建了MessageQueue 的成员变量,

  1. 我们使用Looper前 需要调用 Looper.prepare()方法,且只允许prepare一次。 可以看下源码
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

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

明显可以看到.此静态方法创建了一个Looper对象,放入sThreadLocal中,且多次调用的话会直接抛出异常

即问题4的答案,通过ThradLocal 进行保证每个线程内只存在一个looper

  1. 通过Looper.loop() 函数来启动 MessageQueue,进行事件处理,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;

        //进入死循环
        for (;;) {
           //1. 不断的取出数据,核心
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // 可以设置一个logPrinter 进行 调试
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
               //2. 将msg 分发给 msg的target(Handler)
                msg.target.dispatchMessage(msg);
                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);
                }
            }

            msg.recycleUnchecked();
        }
    }

可以看到loop 循环中,是不断地从MessageQueue中取出msg,然后进行分发的
包装了一些监听分发的处理
核心点即是 Message msg = queue.next()
接着看 MessageQueue

MessageQueue

  MessageQueue 是整个机制的核心点,那它是怎么创建的呢?
前面说到每个线程只会存在一个Looper,在Looper创建的时候会new一个MessageQueue对象,与当前线程绑定

   //是否支持退出,默认是true支持、主线程不支持
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

逻辑流程

如上的几个类的大致说明,看下实际使用中的业务逻辑,在源码中的实现

1. 插入消息

前面查看handler的源码我们知道发送消息最后都调用到了MessageQueue的enqueueMessage方法
下面为源码看下具体是怎么插入到队列中的

    //Handler 发送消息,实际还是通过持有的 MessageQueue 进行插入逻辑
    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }   
    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);
    }
    //MessageQueue
    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;//是否需要唤醒
            //1.头msg为空,或者要插入的msg时间戳小于队列头的.直接放在队列头
            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 {
                //2. 1 不满足的话,则很明显是要插入到队列中,
                // 如果当前是同步屏障且该消息是异步消息则默认是需唤醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //遍历找出当前应该插入的位置,从前往后
                for (;;) {
                    prev = p;
                    p = p.next;
                    //到达链尾或者找到了比该msg更后的
                    if (p == null || when < p.when) {
                        break;
                    }
                    //如果是插入到队列中的其他的异步msg的后面,则不需要唤醒,因为前面的唤醒了
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //3.插入到 prev 和 p 之间
                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;
    }

PS: 同步屏障

频繁的提到了同步屏障这个东西,这个是什么呢,怎么插入?
整体讲同步屏障是一个特殊的Msg,设置之后会阻止读取同步的msg,只允许分发异步msg,
通过postSyncBarrier()设置,removeSyncBarrier移除,@hide方法,
此方法慎用,如果 同步屏障未移除那么就无法处理业务事件了,相当于无反应了,且不会ANR

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;//屏障当前的值,每次+1,移除时需要对应的token
            //1.构建同步屏障的msg
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            //2.设置同步屏障的位置,prev 和 p之间
            // 依据when 进行插入
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            //3.插入
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

为什么需要设置呢?
同步屏障最常的使用地点就在ViewRootimpl里,在scheduleTraversals方法中设置,此方法熟悉的娃肯定知道是更新UI时会被调用到的.很简单的可以联想到奥 设置同步屏障是为了保证UI的更新
例如: 当前消息队列已经充满了很多msg,等待处理,此时 vsync信号到来需要更新界面,总不可能让UI更新的消息排队吧,那样界面就卡主了.
看下scheduleTraversals的源码流程也很容易发现此点。Choreographer.postCallback发送的是异步的msg

2. 取出消息

通过接下来查看关键的next方法,调用代码查看之前的Looper.loop()

    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; //下一次取消息的时间间隔

        //1.que的死循环
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //2.native 方法,会阻塞,底层通过epoll进行阻塞,在用msg输入时唤醒,或者等待时间到达时
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {

                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //3.如果当前 message 是同步栅栏
                // 遍历寻找下一个异步的 message,赋值给 msg
                if (msg != null && msg.target == null) {
                    do {
                       //prevMsg 为 异步msg前的一个同步message
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                //4.当前的msg不为空(将要被处理的msg)
                if (msg != null) {
                    //4.1 当前 message 的时间,还未到,计算需要等待的时间
                    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);
                    //4.2 当前 message的 时间到了,返回此msg给looper分发
                    } else {
                        mBlocked = false;
                        //说明msg是异步消息,取出msg
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            //当前msg的下一个msg,mMessages
                            mMessages = msg.next;
                        }
                        //断链
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //5.找不到msg,消息队列空了或者这个栅栏后,没有异步msg了
                    nextPollTimeoutMillis = -1;
                }
                // 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;
                }
                //线程空闲时,交给IdleHandler去处理。
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 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方法也是一个死循环方法,整体作用是不断地循环取出msg(交给Looper分发出去)

关键点1
取出msg的时候会通过native方法进行阻塞调用
nativePollOnce 通过Linux的底层 epoll,监听文件描述符的IO事件,msg插入时会通过nativeWake也会操作文件的描述符
epoll
其中通过传入的参数进行不同的等待

MessageQueue阻塞nextPollTimeoutMillis毫秒的时间。
1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回
3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。

关键点2
成员变量mMessages指代队列中第一个msg(代表将要被处理的msg)

相关文章

网友评论

      本文标题:Handler 源码解析

      本文链接:https://www.haomeiwen.com/subject/bkibnhtx.html