【Android】Handler 深度解析

481 阅读18分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

Handler 可以说贯穿了整个 Android 系统,在 Android 中扮演着相当重要的角色。我们开发中接触到的四大组件如 Activity、Service、BroadcastReceive、ContentProvider,他们的生命周期的管理都是由handler机制进行的,甚至毫不夸张的说,Android几乎所有的操作 ,都与Handler有关。

因此Handler对于Android来说,非常非常重要。所以本篇文章将带大家深入研究Handler的机制。

而我们在开发的过程中,用得比较多的场景是:在子线程中处理完耗时任务后,使用handler发送消息,然后在主线程中创建的handler中处理消息,即切换到主线程将任务的结果更新到UI界面上。

那么,handler是怎么做到子线程中发送的消息,放在主线程中处理的呢?

换个角度,handler在主线程中发送的消息,能不能在子线程中处理呢?

handler的涉及的问题其实还有很多,我们本篇文章带大家一个一个探测。

首先我们需要对Handler的有个初步的认知,Handler机制中涉及到的几个重要的类:

  • Handler:发送消息和接收消息
  • Message:消息实体类,本质是个单向链表
  • MessageQueue:用于管理Message
  • Looper:用于轮询消息队列,不断取出消息回调到Handler处理

Handler

Handler的初始化

Handler的初始化方式有很多种,比较常用的有:

  • 通过匿名内部类构造Handler,并重写Handler的handleMessage方法
  • 传入Handler.Callback对象,构造Handler,并实现Callback的handleMessage方法
  • 无参构造,仅通过post方法传Runnable对象实现run方法即可处理消息

我们将Handler的所有构造方法都列出来看看:

1、Handler()
2、Handler(Callback callback)
3、Handler(Looper looper)
4、Handler(Looper looper, Callback callback)
5、Handler(boolean async)
6、Handler(Callback callback, boolean async)
7、Handler(Looper looper, Callback callback, boolean async)

其实,第1/2/5个构造方法内部会调用第6个构造方法,第3/4个构造方法内部会调用第7个构造方法。

我们就先从第6个构造方法看起:

public Handler(@Nullable Callback callback, boolean async) {
   ........................................................
   //返回与当前线程关联的Looper对象
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

它通过Looper.myLooper()方法来获取与当前线程关联的Looper对象mLooper,如果mLooper为null就抛出异常,否则就通过mLooper.mQueue获取MessageQueue对象mQueue,然后就是对成员变量mCallback和mAsynchronous进行赋值了。Looper和MessageQueue这两个类也是非常重要的,后面我们会详细讲到。

我们再看第7个构造方法:

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

这个构造方法更简单,只是将传进来的参数进行赋值,同时,通过传参looper获取到MessageQueue对象。

初始化流程总结

由此可见,Handler的构造函数其实只是做了以下几件事:

1、通过Looper.myLooper()方法拿到handler所在线程的Looper对象

2、通过mLooper.mQueue拿到MessageQueue对象

3、对成员变量mCallbackmAsynchronous进行赋值(如果有)

Handler发送消息

handler发送消息的方法有非常多,常用的有handler.post(Runnable r)handler.sendMessage(Message msg)等,下面把他们列出来。

post相关方法

1、post(Runnable r)
2、postDelayed(Runnable r, long delayMillis)
3、postAtTime(Runnable r, long uptimeMillis)
......

post相关的方法我们主要关注这三个即可,post方法和postDelayed方法其实内部都会调用到sendMessageDelayed方法,而postAtTime方法内部会调用sendMessageAtTime方法。

这里我们需要注意到一个东西:post方法的第一个参数Runnable,我们拿postDelayed方法来分析一下:

Handler.java:
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

可以看到,Runnable参数被传入了getPostMessage方法:

Handler.java:
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

getPostMessage方法中,将Runnable对象赋值给了Message对象的callback成员变量具体用途是什么,后面消息的处理时会说明,大家先对此有个印象,然后把Message对象返回。返回Message对象后,就是执行sendMessage系列的方法了。

sendMessage相关方法

1、sendMessage(Message msg)
2、sendMessageDelayed(Message msg, long delayMillis)
3、sendMessageAtTime(Message msg, long uptimeMillis)
4、sendEmptyMessage(int what)
5、sendEmptyMessageDelayed(int what, long delayMillis)
6、sendMessageDelayed(Message msg, long delayMillis)
7、sendEmptyMessageAtTime(int what, long uptimeMillis)

以上是sendMessage系列的所有方法,一共7个,它们互需调用的关系如下:

从关系图中可以看出,无论调用哪个sendMessage相关的方法,最终都会调用sendMessageAtTime方法,最后执行enqueueMessage方法把消息发送到消息队列。所以我们直接看到sendMessageAtTime方法:

Handler.java:
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)是否为null,不为null则直接执行enqueueMessage方法:

Handler.java:
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赋值为当前的Handler对象,明确的指定了消息的target为当前的Handler,以便于在后面Looper分发消息时用到。最后调用了queue.enqueueMessage方法:

MessageQueue.java:
boolean enqueueMessage(Message msg, long when) {
    //如果消息所属的handler为null,则抛出异常
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    synchronized (this) {
    ..........................................................................	   
        msg.markInUse();
        //对msg的when对象赋值
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //如果队列为null,或等待时间为null,或者比前面那位的等待时间更短,就插队
        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;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

先对当前消息所属的handler进行判断,如果为null,则抛出异常,然后加锁处理后续流程,后续流程主要是:

1、对当前消息的when变量赋值,when就是消息可以开始被处理的时间点

2、将当前的消息队列mMessages赋值给p(此时的消息队列还未插入最新消息)

3、如果p为null,或当前消息等待时间为0,或者当前消息等待的时间比队列中最前面的消息的等待时间更短,走if分支的代码,让当前消息插队到最队列最前面

4、如果不满足第3点的条件,走else分支代码,进入循环,如果当前消息的when小于当前消息队列中某消息的when,就退出循环,然后将当前消息插到该消息的前面

以上流程我用三个简单的例子方便大家理解一下:

一、假设当前消息队列mMessages为:a -> b -> c -> null,

此时我要插入一个消息msg为:d -> null,它的when在比a小,

那么它将走进if的分支,

执行,msg.next = p; mMessages = msg;

那么此时mMessages将为:d -> a -> b -> c -> null

二、假设当前消息队列mMessages为:a -> b -> c -> null,

此时我要插入一个消息msg为:d -> null,它的when在a和b的when之间,

进入循环,

先对前指针prev赋值为p,所以prev此时为:a -> b -> c -> null,

再将p链表的指针指向下一结点,即p此时为: b -> c -> null,

判断b和d的when,因为d的when小于b,所以会直接退出当前循环,

执行msg.next = p,此时msg为:d -> b -> c -> null,

执行prev.next = msg,此时prev为:a -> d -> b -> c -> null,

三、假设当前消息队列mMessages为:a -> b -> c -> null,

此时我要插入一个消息msg为:d -> null,它的when在b和c的when之间,

进入第一次循环,

先对prev赋值为p,所以prev此时为:a -> b -> c -> null,

再将p链表指针指向下一结点,即p此时为: b -> c -> null,

判断b和d的when,因为d的when大于b,不会退出循环,

进入第二次循环,

先对prev赋值为p,所以prev此时为:b -> c -> null,

再将p链表指针指向下一结点,即p此时为: c -> null,

先判断c和d的when,因为d的when小于b,所以会直接退出当前循环。

执行msg.next = p,此时msg为:d -> c -> null,

执行prev.next = msg,此时prev为:b -> d -> c -> null,

发送消息流程总结

我们来理一下handler的发送消息的流程:

1、调用发送消息系列方法(post或sendXxx),将消息msg发出

2、执行到HandlerenqueueMessage方法,对msg的target变量赋值

3、执行到MessageQueueenqueueMessage方法,对msg消息的when变量赋值,将msg放在消息队列中合适的位置

Handler处理消息

我们知道,Looper的作用就是不断的从消息队列中取出消息,分发消息,给到handler来处理。具体的过程是怎么的呢?

下面我们就进入到Looper的源码来一探究竟,看到Looper的loop()方法:

Looper.java:
public  static void loop() {
        //获得当前的Looper
        final Looper me = myLooper();   
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //获取当前Looper的消息队列
        final MessageQueue queue = me.mQueue;  
        .................................................................
        for (;;) {
            //取出队头的消息
            Message msg = queue.next();  // might block
            if (msg == null) {
                // 如果消息为空,则跳过,继续执行下一个message
                return;
            }
          ...............................................................
            try {
                msg.target.dispatchMessage(msg);
            ..............................................................
            } finally {
             .............................................................
            }
            .............................................................
            //回收可能正在使用的消息
            msg.recycleUnchecked();  
}

在Looper的loop()方法中,先通过myLooper()方法获取当前的Looper对象,然后me.mQueue获取当前Looper对象的消息队列MessageQueue对象,然后loop方法就会进入循环,通过queue.next()方法不断从消息队列中取出头部消息msg

注意,queue.next()方法后面有个注释“might block”,也就是说,在执行queue.next()方法时,线程可能会被阻塞。为什么会有可能被阻塞呢?这个我们等会在讲解MessageQueue时再详细说明,这里先略过。

取出消息后调用msg.target.dispatchMessage(msg)方法将消息回调到handlerdispatchMessage方法中,

在发送消息的流程中,我们已经知道了,msg的target变量其实就是handler对象,因此消息dispatchMessage方法是在handler中执行的,那么我们进入Handler的dispatchMessage方法中看看:

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

首先对msg的callback变量进行判断,callback还记得不?如果我们通过post(Runnable r)系列方法来发送消息,则这个Runnable对象将赋值给了Message对象的callback成员变量,

因此,当使用post(Runnable r)方法发送消息,msg.callback不会为null,所以就会执行handleCallback(msg)方法,而该方法最终会执行callback的run方法

也就是说,如果handler通过post方法发送消息,将在callback的run方法中处理。 如下:

handler?.post(object : Runnable{
    override fun run() {
        //在这对返回的消息进行处理
    }
})

继续回到dispatchMessage方法,如果msg.callback为null时,对mCallback进行判断,如果mCallback不为null,再判断mCallback.handleMessage(msg)方法的返回值,如果返回值为true,则dispatchMessagere方法结束,返回false,则继续往下执行handleMessage(msg)方法。

那么,mCallback又是在什么呢?它是在构建handler实例时可选择传入的参数,

例如,我们通过传入Callback参数这种形式来构建Handler,消息将在Callback的handleMessage方法中处理,如下:

handler = Handler(Looper.myLooper()!!, object : Handler.Callback {
    override fun handleMessage(msg: Message): Boolean {
        //在这对返回的消息进行处理
        return true
    }
})

然后我们注意Callback的handleMessage方法的返回值,如果返回true,则dispatchMessagere方法结束,如果返回false或者说Callback为null,将继续往下执行Handler类的handleMessage(msg)方法,这个方法是Handler的public方法,供外部重写的,比如:

handler = object : Handler(Looper.myLooper()!!) {
    override fun handleMessage(msg: Message) {
        //在这对返回的消息进行处理
    }
}

因此,如果我们构建handler时重写了Handler的handleMessage方法,且msg.callback为null,且mCallback为null或mCallback的handleMessage方法返回false,消息可在Handler的handleMessage方法中处理

处理消息流程总结

按照惯例,我们来理一下handler对消息处理的流程:

1、在Looper.loop()方法中的循环,取出消息队列头部消息msg

2、调用msg.target.dispatchMessage(msg)方法将消息回调到handlerdispatchMessage方法

3、如果handler通过post方法发送消息,将在callback的run方法中处理

4、如果通过传入Callback参数这种形式来构建Handler,消息将在Callback的handleMessage方法中处理

5、如果构建handler时重写了Handler的handleMessage方法,且msg.callback为null,且mCallback为null或mCallback的handleMessage方法返回false,消息在Handler的handleMessage方法中处理。

.....................................分割线..........................................

源码分析至此,相信大家已经对Handler的发送消息和处理消息的流程有了大致的认知。但过程中涉及到的一些细节还是需要去深入探讨一下的。比如,

MessageQueue的next是实现是怎样的?

Looper在哪创建?

Looper循环能不能退出?

Looper和线程的关系是什么?

......好吧,问题确实挺多。

那么我们就继续往下分析。

Message

分析Handler的发送消息和处理消息的流程中,我们多次看到了Message这个类。它其实就是我们消息实体类。

它内部有两个成员变量,我们需要提前知道:

Message next;
private static Message sPool;

首先第一个是next,我们发现,它是个Message类型的变量,对数据结构比较熟悉的大佬可能已经能联想到链表了,没错, Message的确是个单向链表的数据结构,next即为当前Message对象的后继结点。

另一个变量是sPool,它是Message对象的复用池,我们先知道有这个东西,等会再细说。

然后,我们看Message的构造方法,它有且仅有一个构造方法,并且是个无参构造方法:

public Message() {}

除了构造方法可以创建实例对象外,还可以通过内部的静态方法来创建:

static Message obtain()
static Message obtain(Message orig)
static Message obtain(Handler h)
static Message obtain(Handler h, Runnable callback)
static Message obtain(Handler h, int what)
static Message obtain(Handler h, int what, Object obj)
static Message obtain(Handler h, int what, int arg1, int arg2)
static Message obtain(Handler h, int what, int arg1, int arg2, Object obj)

以上几个静态方法内部都会调用第一个方法来创建一个Message对象。

我们来看下Message的obtain方法:

/**
 * 如果消息池不为null,说明有消息,
 **/
public static Message obtain() {
    synchronized (sPoolSync) {
        //如果sPool不为null
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next; 
            m.next = null;
            m.flags = 0; 
            sPoolSize--;
            return m;
        }
    }
    //如果消息池没有消息对象,就创建一个
    return new Message();
}

如果消息池sPool不为null,就说明消息池中有Message对象,而且Message是个单向链表

假设这时消息池sPool的消息如下:

sPool的链式结构:a -> b -> c -> null

当调用Message.obtain()方法时,首先会判断消息池是否为null,如果不为null就说明消息池中有Message对象,然后就将消息池头结点赋值给m,然后消息池大小减1。如下:

sPool的链式结构: b -> c -> null

m的链式结构:a -> null

最后,将m消息返回出来。如果消息池为null,说明消息池没有消息对象可以复用,那就创建一个。

所以,大多数情况下,使用obtain()来获得一个Message对象,可以避免消耗更多的内存资源

那么,sPool是什么呢?我们看看它的定义:

private static Message sPool;

从obtain方法的流程中,我们已经大致可以知道,它是用来复用消息的。那么它是在什么时候被赋值的呢?那就应该是在message对象将被回收的时候,它就有可能被赋值,所以我们来看看message是怎么进行回收的,我们看到它的recycleUnchecked方法:

Message.java:
private static final int MAX_POOL_SIZE = 50;
void recycleUnchecked() {
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

可以看到,这个方法中,只是将message的变量进行了一些默认值的赋值,并没有正在的回收掉它,然后我们看到synchronized块的代码,判断sPool的大小是否小于MAX_POOL_SIZE(默认值为50),如果小于,则将这个消息放到sPool的头部,以便复用。这种不断的复用同一块内存地址的设计模式,也叫享元模式

MessageQueue

文章开头我们已经大致的说明,MessageQueue是用于管理Message的。具体是怎么管理的,在分析Handler发送消息的过程中,已经分析了。无非就是将Handler发送的msg对象,放到单向链表中的一个合适的位置,以便Lopper取用,怎么个合适的位置呢,就是按照时间的先后来排序,执行时间靠前的就放到链表的靠近头部的位置。

具体可以回到分析Handler发送消息的过程的篇幅中我列举的三个例子理解一下。

那么,在Looper取消息的时候,调用了MessageQueue的next方法,我们前面只是简单的提了一下,取出头部的消息,给到Looper。现在,我们详细分析一下这个方法,我们进入MessageQueue的next方法:

Message next() {
        //当消息队列已经被回收了,就直接返回null
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        //等待处理的空闲Handler的数量,默认值-1。而我们所用的Handeler并未实现IdleHandler接口,因此这个变量的值
        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            //nextPollTimeoutMillis这个变量为-1,则执行nativePollOnce表示线程进入无限等待状态,如果为0,则无需等待,nativePollOnce方法立即返回。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                //msg为消息队列中头部的消息
                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) {
                    //如果msg不为null,
                    if (now < msg.when) {
                        //如果msg的执行时间还没到,则计算when的时间到now还有多久,并保存到nextPollTimeoutMillis变量中
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //如果msg执行时间到了,mBlocked变量赋值为false,并返回这个msg给到Looper
                        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 {
                    //如果msg为null,则nextPollTimeoutMillis为-1
                    nextPollTimeoutMillis = -1;
                }

                //如果消息队列已经被回收了,就直接返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }

                /**
                 * 如果等待处理的空闲Handler的数量小于0
                 * 且mMessages为null,即消息队列已经没有消息了或当前消息还没有到执行的时间
                 * pendingIdleHandlerCount被赋值为空闲Handler的总数
                 */
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                /**
                 * 如果等待处理的空闲Handler的数量小于等于0
                 * mBlocked变量将置为true,然后当前循环退出
                 */
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                ........................................................
            }

            ........................................................
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }

我们首先看到一个next方法中的一个变量pendingIdleHandlerCount,即等待处理的空闲Handler的数量,默认值-1,这个变量循环中还有个地方被赋值为mIdleHandlers.size(),而我们平时所用的Handeler并未实现IdleHandler接口,因此这个变量的值要么为-1,要么为0。

其实,我们看到另一个变量:nextPollTimeoutMillis,它的默认值是0。它的值表示线程什么时候可以被唤醒,

它是在nativePollOnce()方法中作为第二个参数传入的。而当执行了nativePollOnce方法,当前线程将进入挂起等待状态,并且到达指定时间后被唤醒,这个时间就是由nextPollTimeoutMillis变量来决定:

  • nextPollTimeoutMillis值为-1,代表线程进入无限等待状态
  • 值为0,代表无需等待,nativePollOnce方法将直接返回
  • 值为指定时间,代表等待指定时间后,nativePollOnce方法才会返回

知道了这两个变量以及nativePollOnce方法的大概作用,

我们就继续看next()方法,可以看到,有个无限循环,我们把循环中的流程大致理一下出来,如下:

1、执行了nativePollOnce方法,将线程挂起等待,一般来说,第一次循环都是无需等待时间时间的,因为nextPollTimeoutMillis的值为0嘛

2、然后从消息队列中取出头部第一个消息msg

3、如果第一个消息msg不为null且msg的执行时间还没到,则计算执行时间到当前时间还有多久,并保存到nextPollTimeoutMillis变量中

4、如果第一个消息msg不为null且msg的执行时间已经到了,则直接将msg返回给Looper,此时next方法就执行完毕了

5、如果第一个消息msg为null,则nextPollTimeoutMillis变量将被赋值为-1

6、代码继续往下走,判断到pendingIdleHandlerCount小于等于0,则当前循环结束

7、进入下一次循环,执行nativePollOnce方法,根据nextPollTimeoutMillis的值将线程挂起指定时间

好了,next方法的循环流程大致就是这样了!!!

那么还有个问题,如果当前线程进入了等待状态,什么时候才会被唤醒呢?

我们刚刚也提到,当有新消息产生的时候,线程就会被唤醒,继续执行nativePollOnce方法后面的代码。新消息被发送最终会走到MessaageQueue.enqueueMessage方法,所以,我们看到MessaageQueue.enqueueMessage方法:

boolean enqueueMessage(Message msg, long when) {
    .............................
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

方法中,如果线程需要被唤醒,就执行nativeWake方法,没错,这就是用来唤醒线程的,我们看nativeWake方法:

private native static void nativeWake(long ptr);

它也是个native方法,它的参数是用来寻找对应的线程的。

那么,还有哪些地方用到了nativeWake方法呢,全局搜索一下MessaageQueue这个类,可以看到MessaageQueue的quit方法也执行了这个方法:

MessaageQueue.java:
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    ..............................................
       nativeWake(mPtr);
    }
}

而,MessaageQueue.queue方法又在哪被调用呢?我们看到Looper.java类中,可以看到Looper的quit()方法执行了MessaageQueue的quit方法:

Looper.java:
public void quit() {
    mQueue.quit(false);
}

Looper的quit()方法是个public方法,因此,我们可以在外部调用Looper的quit方法,例如:

mHandler.getLooper().quit()

通过这样的方式,我们也能将处于等待状态的线程唤醒。

那么我们再问一个问题,主线程能执行mHandler.getLooper().quit()方法吗?

答案是不行的,还是看到MessageQueue的quit函数:

MessageQueue.java:
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
..................................
    nativeWake(mPtr);
}

如果是mQuitAllowed为false,将会抛出"Main thread not allowed to quit."这个异常,而在主线程中的MessageQueue对象的mQuitAllowed,就是为false的。

Looper

Looper的作用就是用于轮询消息队列,不断取出消息回调到Handler处理。

如何初始化?

我们先来看看Looper是怎样初始化的。我们从它的构造函数看起:

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

在构造函数中,它对MessageQueue对象进行了初始化,以及对mThread变量进行了赋值。然后我们注意到,这个构造函数是私有的,那么意味着不可能在其他类中进行初始化,那么它我们看看它在Looper类中的哪里被调用的。找到了,是在prepare方法中调用的:

Looper.java:   
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));
}

prepare方法中,先判断sThreadLocal.get()如果不为null,则抛出异常,否则,通过sThreadLocal.set()方法,将构造出来的Looper对象设置到sThreadLocal中,sThreadLocal其实是ThreadLocal类型的,而ThreadLocal又是什么?我们后面再讲。

这个prepare方法也是私有的,我们再找找它是在Looper类中的哪里被调用的,有两个地方:

Looper.java:   
一、
public static void prepare() {
    prepare(true);
}

二、
/**
 * @deprecated The main looper for your application is created by the Android environment,
 *  so you should never need to call this function yourself.
 */
@Deprecated
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

一个是Looper类的prepare()方法,一个是Looper类的prepareMainLooper方法,可以看到,prepareMainLooper是被Deprecated注解标记的,为什么不建议使用呢?因为应用程序的主线程的Looper是由Android环境创建的(ActivityThread类中),所以我们永远不需要自己调用这个函数。

综上所述,想在子线程中构建出一个Looper对象,我们只需要在子线程中调用Looper.prepare()方法即可(当然,子线程中想让Looper开启循环还需调用Looper.loop()方法);而主线程中的Looper对象,我们无需调用,因为已经由ActivityThread帮我们建好了。

Looper与线程的关系

Looper初始化的时候,我们知道,构建出一个Looper对象后,这个对象就被set到sThreadLocal这个变量了:

sThreadLocal.set(new Looper(quitAllowed));

而sThreadLocal这个变量,它在Looper中是这样的定义的:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

可以看到,它是一个ThreadLocal类型的变量,

我们看它的set函数:

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

毋庸置疑,传参value就是我们的Looper对象,然后通过Thread.currentThread()方法获取当前的线程对象t。然后再根据当前的线程对象获取当前线程的ThreadLocalMap对象map

如果当前线程的ThreadLocalMap不为null,则执行map.set(this, value)方法将ThreadLocal对象(即Looper对象的sThreadLocal)作为键,Looper对象作为值,保存在ThreadLocalMap中

如果当前线程的ThreadLocalMap为null,则执行createMap(t, value)方法创建线程的ThreadLocalMap对象。

线程的ThreadLocalMap又是个什么东西呢?我们看getMap(t)方法:

ThreadLocal.java:
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

getMap(Thread t)方法直接返回线程的threadLocals成员变量,threadLocals是Thread类的成员变量,它的类型是ThreadLocal.ThreadLocalMap,定义如下:

Thread.java:
ThreadLocal.ThreadLocalMap threadLocals = null;

因此,我们可以得出结论:一旦调用了Looper的prepare()方法,则Loop对象将会和Looper.sThreadLocal变量进行一对一的映射关系,并保存在线程的ThreadLocalMap中!!

那么,此时可以判断一个关系:

一个线程,有一个ThreadLocalMap变量,当调用了Looper的prepare()方法,Looper.sThreadLocal变量将作为键,Loop对象将作为值,保存在这个线程的ThreadLocalMap变量中。

那么有没有一种可能,我在同一个线程中连续调用两次Looper的prepare()方法呢?这样就可以在一个线程中包含两个Looper了!

很遗憾,这是不行的。。我们回到Looper的prepare方法:

Looper.java:   
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));
}

可以看到,如果sThreadLocal.get()不为null,将会抛出“Only one Looper may be created per thread”异常,因此,在同一个线程中不能连续调用两次Looper的prepare()方法 ~

由此我们已经可以断定,一个线程最多只能对应一个Looper对象

Looper的消息循环

Looper的消息循环我们在讲解Handler处理消息的流程中,已经讲解过了,这里就不再赘述。

这里需要注意一个点,就是Looper的无限循环是可能会阻塞的。也就是说,这个循环不会一直占用CPU时间片,在没有消息的时候,线程还是会进入等待状态唤醒的状态的。

Looper循环会导致应用卡死吗

我们常说的卡死指的就是ANR,我们回想一下出现ANR的原因:

1、5秒内没有响应输入事件,比如按键、屏幕触摸;

2、广播接收器10秒内没有执行完毕

出现这两种情况,就会导致ANR。

然后我们知道,在ActivityThread的main()方法中,会执行Looper的loop()方法,此时主线程就会进入循环,不断的取消息来处理,

而当loop()方法中取不出消息的时候,主线程就会进入等待状态或者说进入睡眠状态。

而产生ANR问题我们也说了,就上面的1、2两点,并不是因为主线程睡眠了导致。

如果主线程进入了睡眠,当一有消息发送过来,比如一个更新UI的消息,那么主线程就会马上被唤醒,立即对UI进行更新。

因此,Looper死循环不会导致应用卡死。

Handler的内存泄露

Handler如果使用不当,就会发送内存泄漏。

我们回想一下,在使用Handler的过程中,通过匿名内部类的方式来创建Handler对象:

MainActivity中:
val handler: Handler = object : Handler(Looper.myLooper()!!) {
    override fun handleMessage(msg: Message) {
        //todo handleMessage.
    }
}

首先,我们要知道一个点,匿名内部类是会持有外部类的引用的,假设这个handler我们是在Activity中创建的,那么这个handler对象将会持有Activitiy的引用。

然后我们继续回想一下文章前面说到的Handler发送消息的流程中,执行到HandlerenqueueMessage方法时,会对msg的target变量赋值为handler,也就是说,此时msg将持有handle的引用,而handler又持有Activity的引用,因此,现在的引用关系是这样的:

msg(Message) -> handle -> Activity

当msg被发送后,它将会在被放在MessageQueue中等待被处理,因此引用链是这样的:

MessageQueue -> msg(Message) -> handle -> Activity

假设,msg被设置了延迟处理,那么这个引用关系将保持到msg被处理完成之后才会被解除,那么我们再考虑这样的一种情况,在消息msg还没被处理掉的情况下,Activity被销毁了,当GC到来时,Activity对象理应被回收,

但是,因为Activity仍被引用着,导致GC无法回收它,这样,就发生内存泄漏了

有些人可能会问,如果我不通过匿名内部类的方式来创建Handler对象,而是通过传入一个Handler.Callback参数来构建Handler:

handler = Handler(Looper.myLooper()!!, object : Handler.Callback {
    override fun handleMessage(msg: Message): Boolean {
        TODO("Not yet implemented")
    }
})

这样是不是就不会产生上面那个引用链了,就不会导致内存泄漏了呢?

好吧,确实不会发生上面那样的引用链,但是我们看到,Handler.Callback也是一个匿名内部类,因为会产生另一条引用链:

MessageQueue -> msg(Message) -> handle -> Handler.Callback -> Activity

因此,内存泄漏仍会发生!!!

如何避免内存泄漏

我们应该怎样避免使用Handler时发生的内存泄漏呢?

比较常见的解法是使用静态内部类加弱引用的形式,

即Handler设置为静态内部类,这样Handler将不会再持有外部类的强引用,而我们想要持有外部类的引用,我们可以持有外部类的弱引用,这样在GC触发时,如果Activity已经被销毁,但是它只是被handler以弱引用的方式引用着,所以Activity就可以正常被回收了,也就避免了内存泄漏的发生。

写法如下:

class MainActivity : AppCompatActivity() {
    private var mHandler: MyHandler? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_text_mod)
        //创建mHandler时将activity的弱引用传递进去
        mHandler = MyHandler(WeakReference(this))
    }
    
    //静态内部类MyHandler
    private class MyHandler(val mWeakReference: WeakReference<MainActivity>) : Handler() {
        override fun handleMessage(msg: Message) {
            mWeakReference.get().also {
                //这样就能使用外部类MainActivity的成员了
            }
        }
    }
    
    fun clickBtn(view: View) {
        mHandler?.sendMessage(Message.obtain(mHandler, 1))
    }
}