Handler 流程源码解析

用了三四年的 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 来处理了。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值