写在前面:本文并不是专业的技术文章,只是本菜鸡在千辛万苦找到实习后觉得平时在牛客上面取了那么多经,现在终于有了阶段性的成绩,是时候回馈一下了,所以就把平时自己的学习笔记整理了一下发出来,希望能对看这篇文章的你有那么些许帮助,如果有大佬发现了什么错误 也请轻喷
贴一下之前的面经,里面也有我的一些基本情况 : https://www.nowcoder.com/discuss/659075?source_id=profile_create_nctrack&channel=-1
正文:
Handler是Android当中线程之间进行通信的重要手段之一,整个体系由四大部分组成:Message,Handler,MessageQueue,Looper。最常见的应用场景便是在子线程中进行网络请求,成功后发送消息到主线程来更新UI,本文将以sendMessage方法为切入点,将Handler,MessageQueue,Looper串联起来,对整个Handler体系进行说明
一:Handler
Handler是Handler体系当中最重要的一块,发送消息与处理消息都与Handler有关,而当中最重要的方法,便是sendMessage(post方法底层也是调用了sendMessage,将Runnable包装成了一个Message)
public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); }上面这个方法便是我们调用的sendMessage方法,可以看到,它并没有进行过多的处理,也仅仅是调用了sendMessageDelayed方法,并将第二个参数设置为了0,那么第二个参数是什么意思呢?让我们点进去看一下
Enqueue a message into the message queue after all pending messages before (current time + delayMillis). You will receive it in {@link #handleMessage}, in the thread attached to this handler. public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
从注释和变量名不难理解,第二个参数代表延迟时间,即你希望多久之后在把这个消息发送出去,而上一个方法将第二个参数设置为0,便是希望立即将Message发送出去
可以看见,这个方法底层又调用了sendMessageAtTime这个方法,我们同样点进去看一下
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);
可以看到,这里先是将mQueue变量赋值给了一个MessageQueue类型的变量,然后进行了判空操作,如果不为空才能继续往下执行,至于这个mQueue是什么,之后讲到MessageQueue的时候再来说明,我们现在着重理清调用sendMessage方法后的流程。
接着,这个方法又调用了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); }
这里将msg的target属性设置为了this,也就是将当前msg的target属性指向了当前的handler,而这便是之后Looper分发消息时,能找到对应handler的关键所在,之后讲到Looper时我们便会看到Looper是如何将消息分发给对应的handler进行处理
而这个方法最后又调用了MessageQueue的enqueueMessage方法,我们先不讲MessageQueue具体是什么,只是先看一下enqueueMessage里面的逻辑
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; if (p == null || when == 0 || when < p.when) { 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; } if (needWake) { nativeWake(mPtr); } } return true; }
如果你看不懂这些代码也没关系,只需要大概知道,enqueueMessage的逻辑就是将我们发送的消息插入到消息队列中就行了
好了,到这里我们便将发送消息后的大概流程弄清楚了:当我们调用sendMessage方法后,底层会对这个Message进行一些必要的设置,最终插入到Handler对应的MessageQueue中等待被分发,那么这个MessageQueue到底是什么呢?我们接下来便着手对它进行分析。
二:MessageQueue
MessageQueue,也叫做消息队列,用来存放待分发的消息,它的底层是一个单链表数据结构,如之前我们所讲,当我们调用sendMessage方法后,便会将我们发送的消息插入到链表之中,而之前讲我们所提到的
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);
这个方法中的mQueue变量,就是当前Handler所对应的MessageQueue,总结来说,就是Handler与MessageQueue具有一对一的关系(Looper也一样,后面讲到Looper的时候我们再细讲)
而MessageQueue中最重要的方法,便是next方法
Message 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; } ...... }
我修剪了一些代码,保留了核心的部分,可以看到,next方法当中有一个死循环用来不断返回待分发的消息,大体的逻辑是:首先获得一个消息,如果这个消息找不到对应的handler来处理(即:msg.target == null),就尝试获得下一个消息,直到找到一个可以分发的消息。
找到一个可以分发的消息的消息后,首先判断这个消息有没有准备好被分发,如果没有准备好(即:now < msg.when),就设置一下该消息还需要多久才能被分发,然后就跳出;如果准备好了,便将这个消息从链表中取出并返回这个消息。
那么是谁获得了这个返回的消息呢?这就要提到我们接下来要讲的Looper了。
三:Looper
Looper字面意思为“循环”,在消息机制中,它的作用就类似于一个中转站,从MessageQueue中获取消息,并将这个消息转发给对应的handler来处理,而这些工作都是在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."); } me.mInLoop = true; final MessageQueue queue = me.mQueue; ...... for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ...... try { msg.target.dispatchMessage(msg); ...... } msg.recycleUnchecked(); }
同样,我也对loop当中的代码进行了裁剪,保留了最最核心的部分:我们可以发现,loop当中同样有一个死循环,用来进行消息的分发:首先通过MessageQueue的next方法尝试获得一个消息,如果没有获得新的消息(即:msg == null),那么便会退出loop方法,而这会导致应用程序的退出,从这里也可以看出,为什么next方法中需要用一个死循环来进行阻塞,直到有新的消息到来。
成功获得消息过后,便会将消息分发给对应的handler进行处理,对应的代码便是 msg.target.dispatchMessage(msg),这个target,便是我们之前调用sendMessage方法后,在调用链上的
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); }方法来设置的
public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
而在handler的dispatchMessage方法中,首先会判断message的callback是否为空,不为空则交给message的callback来处理消息(这个message的callback便是我们post的Runnable);为空则判断handler的callback是否为空,不为空则交给handler的callback进行处理,为空则再交给我们重写的 handleMessage方法来处理,也就是说,我们平时重写的 handleMessage方法的优先级其实是排在最后的。
好了,到这里我们便将调用sendMessage方法之后,消息如何几经周转,最后交给handler进行处理大体的流程理清楚了,还有一些常见的问题,如:一个线程中能不能有多个looper? 子线程中能不能直接使用handler?handler造成的内存泄漏该如何处理? 等诸如此类的问题,便放到下一篇文章来进行说明。
第二篇文章地址:https://www.nowcoder.com/discuss/666640?source_id=profile_create_nctrack&channel=-1
全部评论
(1) 回帖