为了账号安全,请及时绑定邮箱和手机立即绑定

Handler话剧第一场-从发送消息到接收消息中间发生了什么?

标签:
Android

1.角色

陆续出场 Handler、Message、MessageQueue、Looper

2.开场白(Handler是什么)

其实只要弄明白了上面第二个问题,自然就会知道handler从发送消息到接收消息更新UI这个过程中发生了什么。不过为了理解更彻底一些,最好是先了解一下handler是什么?用来解决什么问题。
看一下官网对于Handler的描述:

A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue. Each Handler instance is associated with a single thread and that thread’s message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper’s message queue and execute them on that Looper’s thread.
There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

这段话的大概意思是,handler是一个与线程和消息队列绑定的消息处理程序,可以通过它来发送和处理消息。有两种主要用途:(1)调度消息和运行项,以便在未来的某个时间点执行;(2)将要在不同线程上执行的操作加入消息队列。

上面提到的两个主要用途,可能理解起来比较困难,我们来看一个简单的例子:
比如现在有一个下载操作,要求下载完成之后Toast提示一下 下载成功。这里有两个动作,一个是下载,一个是Toast提示,下载是耗时操作,而Toast是更新UI。一般情况下,Android更新UI的操作是在主线程进行的,子线程是不能直接更新UI的,但是耗时操作如果放到主线程的话是会引发ANR阻塞的,所以可以把下载放到子线程,Toast提示放到主线程,那么主线程的Toast如何知道下载操作已经完成了呢?换句话说,子线程和主线程如何通信呢?使用Handler处理类似情况。

3.先发一条消息(Handler、Message出场)

刚刚提到的下载并Toast的例子用handler实现:

Handler handler = new Handler(Looper.myLooper(), msg -> {
            Toast.makeText(MainActivity.this,msg.obj.toString(),Toast.LENGTH_LONG).show();
            return false;
 });
 btn_click_b.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
               new Thread(() -> {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Message message = Message.obtain();
                    message.obj = "下载完成";
                    handler.sendMessage(message);
                }).start();

        }
});

在上面例子中,我们先让子线程阻塞了3s,然后构建了一条消息,调用handler.sendMessage()把构建的消息发送出去,发送到什么地方去了呢?看一下sendMessage(msg)方法的内部是怎么做的。

//handler.sendMessage(msg)的调用链
//handler.sendMessage->sendMessageDelayed->sendMessageAtTime->enqueueMessage->queue.enqueueMessage
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);
}
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);
 }
 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);
  }

4.消息发送到哪里去了(MessageQueue出场)

跟踪整个调用过程发现sendMessage方法经过各种曲折调用之后,最后调用到了MessageQueue类的enqueueMessage方法,其他send方法,包括post方法最后也都会调用到这个enqueueMessage方法。流程进行到这一步之后,核心处理逻辑就已经不在Handler.java这个类中了,而是转移到了MessageQueue类中。接下来看一下MessageQueue.enqueueMessage类的内部:

Message mMessages;
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;
                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;
    }

Message 内部有一个其自身类型的变量next,所以Message可以理解成一个专门存放消息的链表结构。 上面代码的核心逻辑就是,通过一个死循环将收到的信息不断添加到这个链表中去。

5. 只进不出?(Looper出场)

整个过程走到这里,Handler处理机制中的两个关键类Handler、MessageQueue已经陆续登场了。Handler、MessageQueue完成了发送消息和消息入队的过程,我们的消息总不能只发送不接收吧,那么谁来负责取出消息呢?取出消息之后又是怎么处理的呢?轮到Looper上场了!
我们都知道Handler机制中Looper.loope()方法的作用就是从消息列表中不断获取消息,具体是怎么获取呢?看一下loop()方法的内部:

for (;;) {
            Message msg = queue.next(); // 调用MessageQueue.next从Message队列中取出消息
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ......
            msg.target.dispatchMessage(msg);//取出消息之后向下分发
}

整个方法的全部代码比较长,没必要理解全部的逻辑,只需关注for循环就行,这个死循环里其实就做了两件事情,1 不断从Message队列取消息 2 每取出一条消息都将其分发出去。取消息的操作是在MessageQueue.next方法中进行的,核心代码如下:

for (;;) {
           ......
           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) {
                        // 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;
                }
              
               ......
            }
            ......
           }

取消息的核心操作都是在一个死循环中进行的,逐条从Message队列中轮询获取每个Message对象然后将其返回给Looper。

6.兜兜转转回到原点

到这一步Looper就已经取出来了,那么接下来Looper又是怎么处理取到的消息对象的呢?它把消息又分发出去了!。在前面Looper.loope()方法的源码中有代码是msg.target.dispatchMessage(msg),通过字面意思就可以知道,这是一个分发消息的操作,消息分发到哪里去了呢?继续看dispatchMessage(msg)的内部:

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

是不是很熟悉?!dispatchMessage这个方法是Handler内部的一个方法,也就是说,我们通过Handler发出的消息兜兜转转又回到了Handler!接下来的事情大家都很清楚了,我们在Activity中调用了handleMessage回调,拿到了当初发送出去的消息,然后就开始处理其他逻辑了。至此,演出结束,众演员退场。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消