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

Handler 流程源码解析

程序员文章站 2022-07-14 16:46:28
...

用了三四年的 Handler,一直就没有真正理解过 Handler 到底是个什么东西,从开始认识它,就一直以为记住 Handler 是用来发送和处理消息的;Message 是消息,Handler 发送和处理的对象;Looper 是用来管理消息队列的;MessageQueue 是消息队列,仅此而已。

我曾经天真的以为我记住这些就能用好了,但是当我遇到了一个让我郁闷的一个 BUG,具体错误信息我忘记了,反正就是找不到 looper 之类的错误,当时我就懵逼了,不知道如何解决,没办法只能求助度娘。

在主席(任玉刚)的《开发艺术探索》中,关于 Handler 的解析还是分模块解析的,先解释 MessageQueue 的工作原理,然后是 Looper 的工作原理,最后是 Handler 的工作原理,瞬间人生三大拷问有木有,或许这样更适合他吧,但是我还是比较喜欢的是整体的理解,这也正是我为什么写这篇文章的原因,来让我们对 Handler 有一个整体的认识。

说一嘴不相关的,Handler 的使用我们经常这么写:

    var mHandler = object : Handler() {
        override fun handleMessage(msg: Message?) {
            super.handleMessage(msg)
            Log.i("message", "我是一个handler")
        }
    }
    
    fun sendMessage() {
        mHandler.sendMessage(null)
    }

但是这样会导致一个问题,那就是可能会内存泄漏,那么我么如何规避这个问题呢,我比较推荐使用弱引用来处理:

    fun sendMessage() {
        Handler.sendMessage(null)
    }

    val Handler = MyHandler()
    class MyHandler : Handler() {
        override fun handleMessage(msg: Message?) {
            super.handleMessage(msg)
            Log.i("message", "我是一个handler")
        }
    }

1. 关于创建

好了,我们现在进入正题,我们先从 Handler 的创建来一步一步的分析,我们来看看它的构造:

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

我们只需要关注这几行

 mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;

从这里我们能看出,在我们创建 Handler 的时候,会通过 myLooper 方法获取当前线程保存的 Looper 的实例,为什么这么说呢,而在新建 Looper 的时候,系统又为我们做了什么呢,我们来看下源码就明白了:

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

调用了 sThreadLocal.get(),那么既然出现了 get 方法,那么一定会有 set 方法了,但是这个方法又在哪里执行的呢,我们来看下在创建 Handler 的时候出现的一个关于 Looper 的报错信息:throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); 很显然,如果 Looper 是空的,是不能执行 Looper.prepare 方法的,我们来看下这个方法是干嘛的:

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

哦~原来这个 set 方法是在这里,是新建了一个 Looper,然后和一个叫 ThreadLocal 的线程绑定了,通过异常我们知道,在一个线程中,只能有一个 Looper。这时候你是不是会问了,我先执行的是 myLooper 方法,也就是 ThreadLocal 的 get 方法,然后才执行的 prepare 方法,也就是 set 方法,那么 get 获取的肯定是空的啊,那我创建的 Handler 中不照样是一个空的 Looper,为什么没有抛出异常呢,其实呢,这个通过源码也是可以找到答案的,我们来看下它的 get 和 set 方法:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

原来是进行了一次判断,如果不存在这个线程,那么会创建一个,如果存在,则进行其他的处理。

好,我们回到 Looper.prepare 的方法中,在执行 set 方法的时候,传入了一个 Looper 实例,我们来看下在 Looper 的构造中做了哪些处理:

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

哇!竟然和 MessageQueue 绑定了,这时候,我们或许明白了一部分了,我们来总结下:

(1)创建 Handler 的时候,我们通过 myLooper 方法获取当前线程保存的实例,然后通过 mQueue = mLooper.mQueue 获取 Looper 相关的 MessageQueue 队列。

(2)在创建 Looper 的时候,我们创建了一个 MessageQueue 的队列,然后知道了通过 set 和 get 方法来处理线程和 Looper 的关系。

2. 关于插入数据

我们继续往下走,来看下  sendMessage 为我们做了哪些事情:

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

跟随源码,我们找到这个方法 sendMessageAtTime,然后获取到了 MessageQueue 的实例,调用了 enqueueMessage 方法,那么 enqueueMessage 到底是干什么的呢,我们继续往下看:

   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

到这里,我们发现竟然多出了一个属性 meg.target 被赋值了,那么这个属性到底有啥用呢?我们继续往下看,这里调用了 MessageQueue 的 enqueueMessage 方法,源码如下:

    boolean enqueueMessage(Message msg, long when) {
        //消息的所属target不为空
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //如果该消息已经设置为inUse标签则不可再次加入,道理就是同一个Message不可用两次
        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;
            }
            
            //设置消息的inUse标签
            msg.markInUse();
            //赋值执行时间
            msg.when = when;
            //获取下一个将要执行的Message
            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 {
                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;
    }

原来,这个方法是把 Handler 发送的消息插入到 MessageQueue 队列中,这里我就不过多解释了,我们继续往下看。

3.关于取数据

关于怎么从 MessageQueue 中读取数据,然后把数据给 Handler,我们需要先来看另外一个方法,既然说了 Looper 是用来管理队列的,那么他就不可能创建一个 MessageQueue 队列就一遍歇着去了,哪能那么简单,我们来看下 Looper 的 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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

这时候我们通过 myLooper 方法得到一个 sThreadLocal 中保存的一个 Looper 对象,然后得到 Looper 中存在的 MessageQueue 队列,之后调用 MessageQueue 的 next 方法开启我们读取消息的旅程。

Message next() {
        //获取native的MessageQueue的实例引用
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //额外处理消息的个数
        int pendingIdleHandlerCount = -1;
        //阻塞时间:-1是一直阻塞不超时;0是不会阻塞,立即返回;大于0则nextPollTimeoutMillis是最长阻塞时间,期间有线程唤醒立即返回
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                //记录当前时间
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                //获取到消息头
                Message msg = mMessages;
                //如果该消息是屏障
                if (msg != null && msg.target == null) {
                    //忽略所有的同步消息找下一个异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    //如果当前时间还没到Message的执行时间
                    if (now < msg.when) {
                        //设置等待时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //执行时间到了,返回一个执行消息
                        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 {
                    //走到队尾
                    nextPollTimeoutMillis = -1;
                }

                //执行退出
                if (mQuitting) {
                    dispose();
                    return null;
                }

                //第一次进来,获取额外处理消息的条数
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                //额外消息个数为空,则进入下一次循环
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                //初始化mPendingIdleHandlers
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            //循环处理额外消息
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; //释放
                //根据返回结果来判断是否需要保留
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                //如果不需要保留则移除
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            //归零
            pendingIdleHandlerCount = 0;

            //阻塞时长归零
            nextPollTimeoutMillis = 0;
        }
    }

具体的也就不说了,注释已经说明,根据上面源码我们发现如果 msg.target 为空,那么就循环找出第一个 Message,那么它什么时候是空的呢?

    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++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

这个方法直接在MessageQueue中插入了一个Message,并且未设置target,它的作用是插入一个消息屏障,这个屏障之后的所有同步消息都不会被执行,即使时间已经到了也不会执行。

可以通过public void removeSyncBarrier(int token)来移除这个屏障,参数是post方法的返回值。

这些方法是隐藏的或者是私有的,具体应用场景可以查看ViewRootImpl中的void scheduleTraversals()方法,它在绘图之前会插入一个消息屏障,绘制之后移除。

回到之前的next方法,如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息),可以通过setAsynchronous(boolean async)设置为异步消息。

继续往下,如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis);阻塞;

否则把消息返回给调用者,并且设置mBlocked = false代表目前没有阻塞。

如果阻塞了有两种方式唤醒,一种是超时了,一种是被主动唤醒了。根据生产消费模式,生产者有产品的时候一般情况下会唤醒消费者。那么MessageQueue入队列的时候应该会去唤醒,下面看一下MessageQueue入队列的方法,截取了主要逻辑:

(以上内容来自:https://blog.csdn.net/kisty_yao/article/details/71191175  本来想自己总结一部分,但是感觉有用的比较多,就直接复制了。)

我们发现不管是插入数据或者是读取数据,都有一个属性 msg.target 一直伴随我们所有,从最初 Handler 执行 enqueueMessage 到 MessageQueue 执行 next 方法,它一直伴随我们左右,那么它到底是什么呢?其实它就是一个 Handler,Looper的loop方法会取出每个 msg 然后交给 msg.arget.dispatchMessage(msg) 去处理消息,而 msg.arget.dispatchMessage 正是调用了我们重写的 handleMessage 方法,这时候我们就把消息又重新交给 Handler 来处理了。