Handler消息机制全面解析

1,347 阅读16分钟

Handler消息机制全面解析

1、消息机制的作用

Android的消息机制主要是指Handler的运行机制。Handler能够让你发送和处理消息,以及Runnable对象;每个Handler对象对应一个Thread和 Thread的消息队列。 当你创建一个Handler时,它就和Thread的消息队列绑定在一起,然后就可以传递消息和runnable对象到消息队列中, 执行消息后就从消息队列中退出。

Handler的作用就是:调度消息和runnable对象去被执行;使动作在不同的线程中被执行。

当一个应用程序中进程被创建时,它的主线程专门运行消息队列(MessageQueue),去管理顶层的应用程序相关的对象如:Activity,BroadcastReceiver,Windows等,你可以创建你的Thread,和主线程进行交互——通过Handler,交互的方法就是通过post或者sendMessage。但是在你的新线程中,给定的Message或者Runnable,会在适当的时候的被调度和处理。

2、消息机制基本构成

消息系统模型一般会包括以上七个部分(消息原型,消息队列,消息发送,消息循环,消息获取,消息派发,消息处理)。实际上的核心是消息队列和消息循环,其余部分都是围绕这两部分进行的。

在Android中将这七个部分抽象成四个独立的部分:Handler,Message,MessageQueue,Looper。

Message:就是消息原型,包含消息描述和数据,它是Handler接收与处理的消息对象,它可以在内部携带少量信息.可以传递bundle数据   

MessageQueue:就是消息队列,存储消息对象的队列,每个线程只有一个阻塞队列   

Looper:每个线程只能够有一个Looper,管理MessageQueue,负责循环读取MessageQueue中的消息交给Handler去处理。是每个线程中MessageQueue的管家,调用Looper.loop()方法后,就会进入到一个无限循环中,会从头开始取消息并传递到Handler的handlerMessage()方法中。

Handler:发送消息与处理消息,驾驭整个消息系统模型,统领Message,MessgeQueue和Looper。在其子线程或当前线程发送Message,在UI线程处理消息对象,发送的消息经过处理后最终在handlerMessage方法中处理。

image.png

3、Message

 public final class Message implements Parcelable {
     //用于区别消息的类型
     public int what;
     //携带数据,空消息所携带的数据
     public int arg1;
     public int arg2;
     //携带数据(进程间通信时只能携带系统定义的parcelable对象,自定义的不行)
     public Object obj;
     //Messenger进行进程间通讯时,用于实现双向通讯
     public Messenger replyTo;
     //携带数据,可存放多条
     Bundle data;
     //消息所携带的代码语句数据
     Runnable callback;
     
     //消息的处理目标对象,用于处理消息
     Handler target;
     //用于标记该Message是否在被使用  
     int flags;
     //存放时间,用于在MessageQueue中实现排序
     long when;
     //用于实现单链表,以链表实现Message池
     Message next;
     //链表头指针
     private static Message sPool;
     private static int sPoolSize = 0;//池的当前大小
     private static final int MAX_POOL_SIZE = 50;//池的最大容量
      ...
 }
 public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

Message使用了一个数据池sPool来对Message对象进行回收和再利用,所以建议用obtain方法来获取Message,避免多次分配对象,sPool的上限是MAX_POOL_SIZE = 50。 当开始obtain方法时首先开启了一个对象锁synchronized,如果当sPool不为空的时候,也就是有数据时,会取出表头的Message然后池内数据减1.

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

回收时会先检查一下当前的Message是否正在使用,然后调用recycleUnchecked方法进行回收,当使用完一个Message时,要将不使用的Message回收。recycleUnchecked方法回收之前先重置Message的状态,将状态都清空,将Message放回对象池时会首先判断对象池的容量是否已经满了,只有未满的时候才会回收进对象池,否则将会丢弃等待GC的回收。

除了 Message,Android 系统当中还有很多类也采用 obtain() 来获取实例,比如 MotionEventKeyEventAccessibilityEvent等。

  • MotionEvent 和 KeyEvent 的 obtain() 的原理和 Message 几乎完全一致,都是使用单链表来维护实例的复用,不同的是 Event 池子的 Size 上限为 10
  • AccessibilityEvent 则稍稍不同,obtain() 是从 SynchronizedPool 里取得实例,其本质上是固定长度的数组,每次从最后一个元素获取和存入实例。

4、MessageQueue

是一个队列类型的数据结构,总体上就是先进先出的访问顺序,实现方式和Message对象池一样,也是由Message连接而成的单链表。Message有个long whent属性,就是存入MessageQueue的时间。而MessageQueue会按照when的大小将Message列表进行排序。

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) {
        //若是该队列已经退出,则直接回收消息,只有在quit方法中才设置该属性为true
        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;
        //p指向队列(单链表)的头部
        Message p = mMessages;//这里的队列也是由Message构成的单链表
        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插在对应的位置。将新消息通过 when大小排序,存到消息队列中。 
            //消息队列实际上是一个单链表,when最小,即表示最先触发的消息,会放在链表头部
            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;
        }
        //这里在插入方法中可以看出,只要插入的Message不是异步的,那么
        //needWake的值就会是mBlocked的值,而mBlocked的值会在出队方
        //法next中,当线程阻塞的时候设为True。而这里当有非异步的Message入队时,
        //就会调用nativeWake方法将线程唤醒来处理消息
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

enqueueMessage会按照时间从小到大的顺序将消息插入在相应的位置,也会判断是否需要唤醒线程。因为MessageQueue中的队列是由Message和它的属性next实现的单链表。单链表只能按照从头至尾的顺序访问,因为入队插在表尾,而出队是从表头取出的。

Message next() {
   //ptr是native层对象指针,为0时表示MessageQueue已经结束
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis);
    }
    ...
}

next方法中调用了一个死的for循环,以及一个nativePollOnce的native方法(陷入阻塞,等待被唤醒)。 在队列为空或消息暂未达到处理时间的时候,线程会阻塞,这里阻塞是通过native层的epoll方式进行的阻塞。

Message的执行时刻如何管理?

  • 发送的 Message 都是按照执行时刻 when 属性的先后管理在 MessageQueue 里

    • 延时 Message 的 when 等于调用的当前时刻delay 之和
    • 非延时 Message 的 when 等于当前时刻(delay 为 0
    • 插队 Message 的 when 固定为 0,便于插入队列的 head
  • 之后 MessageQueue 会根据读取的时刻和 when 进行比较

    • 将 when 已抵达的出队,
    • 尚未抵达的计算出当前时刻和目标 when 的插值,交由 Native 等待对应的时长,时间到了自动唤醒继续进行 Message 的读取

事实上,无论上述哪种 Message 都不能保证在其对应的 when 时刻执行,往往都会延迟一些!因为必须等当前执行的 Message 处理完了才有机会读取队列的下一个 Message。

比如发送了非延时 Message,when 即为发送的时刻,可它们不会立即执行。都要等主线程现有的任务(Message)走完才能有机会出队,而当这些任务执行完 when 的时刻已经过了。假使队列的前面还有其他 Message 的话,延迟会更加明显!

onCreate() 里向 Handler 发送大量 Message 会导致主线程卡顿吗?

不会,发送的大量 Message 并非立即执行,只是先放到队列当中而已。

onCreate() 以及之后同步调用的 onStart() 和 onResume() 处理,本质上也是 Message。等这个 Message 执行完之后,才会进行读取 Message 的下一次循环,这时候才能回调 onCreate 里发送的 Message。

需要说明的是,如果发送的是 FrontOfQueue 将 Message 插入队首也不会立即先执行,因为 onStart 和 onResume 是 onCreate 之后同步调用的,本质上是同一个 Message 的作业周期

异步Message或同步屏障

在Android的消息机制中,其实有三种消息: 普通消息、异步消息及消息屏障。

  • 异步 Message:设置了 isAsync 属性的 Message 实例

    • 可以用异步 Handler 发送
    • 也可以调用 Message#setAsynchronous() 直接设置为异步 Message
  • 同步屏障:在 MessageQueue 的某个位置放一个 target 属性为 null 的 Message,确保此后的非异步 Message 无法执行,只能执行异步 Message。可以通过MessageQueue中的postSyncBarrier方法发送一个消息屏障(该方法为私有,需要反射调用)。

  • 原理:当 MessageQueue 轮循 Message 时候发现建立了同步屏障的时候,会去跳过其他 Message,读取下个 async 的 Message 并执行,屏障移除之前同步 Message 都会被阻塞

  • 应用:比如屏幕刷新 Choreographer 就使用到了同步屏障,确保屏幕刷新事件不会因为队列负荷影响屏幕及时刷新。

  • 注意:同步屏障的添加或移除 API 并未对外公开,App 需要使用的话需要依赖反射机制

所以消息屏障和异步消息的作用很明显,在设置消息屏障后,异步消息具有优先处理的权利。

这时候我们回顾将消息添加到消息队列中时,可以发现,其实并不是每一次添加消息时,都会唤醒线程。 当该消息插入到队列头时,会唤醒该线程; 当该消息没有插入到队列头,但队列头是屏障,且该消息是队列中 靠前的一个异步消息,则会唤醒线程,执行该消息;

如何保证 MessageQueue 并发访问安全?

任何线程都可以通过 Handler 生产 Message 并放入 MessageQueue 中,可 Queue 所属的 Looper 在持续地读取并尝试消费 Message。如何保证两者不产生死锁?

Looper 在消费 Message 之前要先拿到 MessageQueue 的锁,只不过没有 Message 或 Message 尚未满足条件的进行等待前会事先释放锁,具体在于 nativePollOnce() 的调用在 synchronized 方法块的外侧。

Message 入队前也需先拿到 MessageQueue 的锁,而这时 Looper 线程正在等待且不持有锁,可以确保 Message 的成功入队。入队后执行唤醒后释放锁,Native 收到 event 写入后恢复 MessagQueue 的读取并可以拿到锁,成功出队。

这样一种在没有 Message 可以消费时执行等待同时不占着锁的机制,避免了生产和消费的死锁。

5、Looper

默认创建一个线程,线程里面是没有消息队列的,如果想用消息队列MessageQueue,就需要通过Looper进行绑定。下面是一个简单的例子:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
             }
         };
        Looper.loop();
    }
}

可以看见Thread通过Looper.prepare() 和 Looper.loop()两个静态方法运行。

从源码里看 Looper的构造函数是private,则说明Looper不能再外部实例化。就可以猜测到Looper和Thread是一一对应的。

private Looper(boolean quitAllowed) {
        //当创建一个Looper实例时,会自动创建一个与之配对的MessageQueue(消息队列)
        mQueue = new MessageQueue(quitAllowed);
        //获取当前线程。
        mThread = Thread.currentThread();
    }

Looper有两个方法可以得到Looper对象,prepareMainLooper(主线程调用)和prepare方法。prepare会先判断是否已经创建过,创建过会抛出异常,所以一个线程只能对应一个looper。如果没创建过,就会创建一个looper放到ThreadLocal中,而ThreadLoacl是按照线程信息存储数据的,获取时也是根据当前线程获取对应数据。

/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        //判断sThreadLocal是否为null,否则抛出异常  即Looper.prepare()方法不能被调用两次
        //也就是说,一个线程中只能对应一个Looper实例
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        ```
        //sThreadLocal是一个ThreadLocal对象,用于在一个线程中存储变量
        //实例化Looper对象并存放在ThreadLocal  这说明Looper是存放在Thread线程里的
        sThreadLocal.set(new Looper(quitAllowed));
    }
    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就准备好了,接着就可以通过Looper.loop()开启消息循环,阻塞式地从MessageQueue中取消息。loop会创建一个消息队列MessageQueue,每个Looper会对应一个消息队列。调用 loop方法就会执行死循环一直从消息队列中取消息。

QQ截图20220113212535.png

  • Looper抽象了无限循环的过程,并且将MessageQueue从Handler中移到Looper中。
  • ThreadLocal将每个线程通过ThreadLocalMap将Looper与Thread绑定,保证能够通过任意Thread获取到对应的Looper对象,进而获取到Thread所需的关键MessageQueue.

死循环未造成卡顿的原因是MessageQueue的next方法中,若是队列中没有Message时,则会阻塞在这里。因此不会造成系统的卡顿。

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }

    //获取当前线程的MessageQueue。
    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 (;;) {
        //获取MessageQuene消息队列的消息.
        Message msg = queue.next(); // might block

        //如果消息队列没有消息,则return,即阻塞在这里,等待获取Message。
        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
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        // msg.target 是一个Handler,
        // 这个意思是让改Message关联的Handler通过dispatchMessage()处理Message。
        msg.target.dispatchMessage(msg);

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

Looper为什么要手动quit?

创建Looper并执行loop()的线程在任务结束的时候,需要手动调用quit。反之,线程将由于 loop() 的轮询一直处于可运行状态,CPU资源无法释放。更有可能因为Thread作为GC Root 持有超出生命周期的实例引发内存泄漏。

当quit调用后,Looper不再因为没有Message去等待,而是直接取到为null的 Message,这将触发轮循死循环的退出。

quit时Message们怎么处置?

Looper的很多处理实则都是MessageQueue在发挥作用,包括这里的Looper#quit()。它其实是调用 MessageQueue的同名函数quit(boolean),并指定safe参数为false

public void quit() { 
    // 默认是不安全的退出 
    mQueue.quit(false); 
}

MessageQueue#quit()则主要执行几项简单工作,包括:标记正在退出,并清空所有未执行的Mesage,最后唤醒线程。

  1. 退出的标记将导致后续的sendMessage()postRunnable()失效,直接返回false
  2. 默认的策略是清空队列里所有Message,包括时间正好抵达的Message都无法处理,不太友好。
  3. 最后唤醒线程,进入读取队列的下一次循环,因为队列已无Message,将直接返回null。
  4. loop()拿到的Message为null,死循环退出,线程结束。

quitSafely做了哪些优化?

quitSafely调用的那刻,满足执行时间条件的Message继续保留在队列中,在都执行完毕才退出轮询。移除未来Message之后唤醒线程的next()循环,其将取出留在队列里的Message进行处理。等残存 Message 都执行完了,下一次轮询的next()将取不到Message,最终因为quitting flag返回null,进而触发 loop()死循环的退出。

主线程Looper需要quit吗?

主线程ActivityThread创建Looper时指定了不允许quit的标志,即不可以手动调用quit。 如果强行在主线程里调用了 quit(),会发生如下异常:

java.lang.IllegalStateException: Main thread not allowed to quit.

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

综上:主线程也不需要退出,在内存不足的时候App由AMS直接回收进程。主线程极为重要,承载着 ContentProvider、Activity、Service 等组件生命周期的管理,即便某个组件结束了,它仍有继续存在去调度其他组件的必要!

Looper 存在哪?如何可以保证线程独有?

  • Looper 实例被管理在静态属性 sThreadLocal
  • ThreadLocal 内部通过 ThreadLocalMap 持有 Looper,key 为 ThreadLocal 实例本身,value 即为 Looper 实例
  • 每个 Thread 都有一个自己的 ThreadLocalMap,这样可以保证每个线程对应一个独立的 Looper 实例,进而保证 myLooper() 可以获得线程独有的 Looper

主线程 Main Looper 和一般 Looper 的异同?

  • 区别:

    1. Main Looper在创建的时候设置了不可 quit 的标志,不可 quit。主线程需要不断读取系统消息和用书输入,是进程的入口,只可被系统直接终止。
    2. Main Looper 实例还被静态缓存。为了便于每个线程获得主线程 Looper 实例,见 Looper#getMainLooper(),Main Looper 实例还作为 sMainLooper 属性缓存到了 Looper 类中。
  • 相同点:

    1. 都是通过 Looper#prepare() 间接调用 Looper 构造函数创建的实例

    2. 都被静态实例 ThreadLocal 管理,方便每个线程获取自己的 Looper 实例

主线程为什么不用初始化 Looper?

App 的入口并非 MainActivity,也不是 Application,而是 ActivityThread。 其为了 Application、ContentProvider、Activity 等组件的运行,必须事先启动不停接受输入的 Looper 机制,所以在ActivityThread的 main() 方法中调用 prepareMainLooper() 创建 Looper 并调用 loop() 轮循。

public static void main(String[] args) {
      ...
        Looper.prepareMainLooper();//创建属于此线程的Looper
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        ...
        Looper.loop();
    }
   public static void prepareMainLooper() {
        prepare(false);
        ...
    }
    private static void prepare(boolean quitAllowed) {
        ...
        sThreadLocal.set(new Looper(quitAllowed));
    }

Handler 或者说 Looper 如何切换线程?

  1. Handler 创建的时候指定了其所属线程的 Looper,进而持有了 Looper 独有的 MessageQueue
  2. Looper#loop() 会持续读取 MessageQueue 中合适的 Message,没有 Message 的时候进入等待
  3. 当向 Handler 发送 Message 或 Runnable 后,会向持有的 MessageQueue 中插入 Message
  4. Message 抵达并满足条件后会唤醒 MessageQueue 所属的线程,并将 Message 返回给 Looper
  5. Looper 接着回调 Message 所指向的 Handler Callback 或 Runnable,达到线程切换的目的

简言之,向 Handler 发送 Message 其实是向 Handler 所属线程的独有 MessageQueue 插入 Message。而线程独有的 Looper 又会持续读取该 MessageQueue。所以向其他线程的 Handler 发送完 Message,该线程的 Looper 将自动响应。

Looper 的 loop() 死循环为什么不卡死?

为了让主线程持续处理用户的输入,loop() 是死循环,持续调用 MessageQueue#next() 读取合适的 Message。但当没有 Message 的时候,会调用 pollOnce() 并通过 Linux 的 epoll 机制进入等待并释放资源。同时 eventFd 会监听 Message 抵达的写入事件并进行唤醒。

这样可以空闲时释放资源、不卡死线程,同时能持续接收输入的目的。

Looper 等待的时候线程到底是什么状态?

调用 Linux 的 epoll 机制进入等待,事实上 Java 侧打印该线程的状态,你会发现线程处于 Runnable 状态,只不过 CPU 资源被暂时释放。

Looper 的等待是如何能够准确唤醒的?

读取合适 Message 的 MessageQueue#next() 会因为 Message 尚无或执行条件尚未满足进行两种等的等待:

  • 无限等待
    无 Message(队列中没有 Message 或建立了同步屏障但尚无异步 Message)的时候,调用 Natvie 侧的 pollOnce() 会传入参数 -1

    Linux 执行 epoll_wait() 将进入无限等待,其等待合适的 Message 插入后调用 Native 侧的 wake() 向唤醒 fd 写入事件触发唤醒 MessageQueue 读取的下一次循环

  • 有限等待

    有限等待的场合将下一个 Message 剩余时长作为参数交给 epoll_wait(),epoll 将等待一段时间之后自动返回,接着回到 MessageQueue 读取的下一次循环

Native 侧的 NativeMessageQueue 和 Looper 的作用是?

  • NativeMessageQueue 负责连接 Java 侧的 MessageQueue,进行后续的 waitwake,后续将创建 wake 的FD,并通过 epoll 机制等待或唤醒。但并不参与管理 Java 的 Message
  • Native 侧也需要 Looper 机制,等待和唤醒的需求是同样的,所以将这部分实现都封装到了 JNI 的NativeMessageQueue 和 Native 的 Looper 中,供 Java 和 Native 一起使用

Native 侧如何使用 Looper?

  • Looper Native 部分承担了 Java 侧 Looper 的等待和唤醒,除此之外其还提供了 Message、MessageHandlerWeakMessageHandlerLooperCallbackSimpleLooperCallback 等 API
  • 这些部分可供 Looper 被 Native 侧直接调用,比如 InputFlinger 广泛使用了 Looper
  • 主要方法是调用 Looper 构造函数或 prepare 创建 Looper,然后通过 poll 开始轮询,接着 sendMessageaddEventFd,等待 Looper 的唤醒。使用过程和 Java 的调用思路类似

6、Handler

Handler 是暴露给外部调用者使用,Handler有多个构造函数。

  • public Handler()
  • public Handler(boolean async)
  • public Handler(Callback callback)
  • public Handler(Callback callback,boolean async)
  • public Handler(Looper looper)
  • public Handler(Looper looper, Callback callback)
  • public Handler(Looper looper, Callback callback, boolean async) 前4个构造函数是没有传Looper的,他将获取该线程的Looper和MessageQueue消息队列
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;
}

后3个构造函数有传Looper,这两个构造函数会将该Looper保存到名为mLooper的成员字段中

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
public interface Callback {
    public boolean handleMessage(Message msg);
}

Handler.Callback是用来处理Message的一种手段,在构造函数中传递Callback,则可以处理Message,如果返回的是true,则不再往下执行,起到拦截的效果。如果构造函数没有传递Callback,则应在Handler中重写handleMessage方法。

sendMessage 发送消息

在线程中可以通过sendMessage方式往消息队列中添加Message。

  • sendMessage(Message msg):
  • sendMessageDelayed(Message msg, long delayMillis):
  • sendMessageAtTime(Message msg, long uptimeMillis) :
  • sendEmptyMessage(int what)
  • sendEmptyMessageDelayed(int what, long delayMillis)
  • sendEmptyMessageAtTime(int what, long uptimeMillis) :

通过看Handler源码可以知道,最终都是调用sendMessageAtTime方法。而在sendMessageAtTime中通过enqueueMessage方法将Message放入消息队列中。

image.png

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

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //将Message的target绑定为当前的Handler 
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • msg.target = this : 将Message的target绑定为当前的Handler .
  • queue.enqueueMessage(msg, uptimeMillis): queue 是当前Handler的消息队列MessageQueue.通过queue.enqueueMessage将Message放入消息队列。

post 方式发送消息

  • post(Runnable r)
  • postAtTime(Runnable r, long uptimeMillis)
  • postAtTime(Runnable r, Object token, long uptimeMillis)
  • postDelayed(Runnable r, long delayMillis) 其中post 和 postDelayed 最后还是调用sendMessageDelayed方法。postAtTime最后调用sendMessageAtTime方法,最终的调用和sendMessage一样。
public final boolean post(Runnable r){
  return  sendMessageDelayed(getPostMessage(r), 0);
}

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

通过上面的代码我们可以看到在getPostMessage方法中,我们创建了一个Message对象,并将传入的Runnable对象赋值给Message的callback成员字段,然后返回该Message,然后在post方法中该携带有Runnable信息的Message传入到sendMessageDelayed方法中。

dispatchMessage

构造方法中的一些属性,都是从Looper中的ThreadLocal中获取的。enqueueMessage时将msg.target赋值为this,将来在looper.loop中调用target.handleMessage。若是没有指定looper,则使用实例化Handler时所在线程的Looper,否则使用指定的Looper。如果构造方法传入callback,则执行callback的handleMessage处理,否则使用Handler本身的handlerMessage处理消息。

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

7、IdelHandler

使用当前线程的MessageQueue.addIdleHandler方法可以在消息队列中添加一个IdelHandler。

MessageQueue messageQueue = Looper.myQueue();
    messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            return false;
        }
});

当MessageQueue 阻塞时,即当前线程空闲时,会回调IdleHandler中的方法;添加IdelHandler时,消息队列不为空,当消息处理完或者剩下消息还没到触发时间,会回调方法;当添加IdelHandler时,消息队列为空,则当时不会触发回调. 当IdelHandler接口返回false时,表示该IdelHandler只执行一次,

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);
        ......
        synchronized (this) {
         ......
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
    }
}

使用场景

1、延迟执行 2、批量任务,任务密集,且只关注最终结果。

8、原理小结

image.png

1、在使用Handler的时候,在Handler所创建的线程需要维护一个唯一的Looper对象,每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证。Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,但是只能有一个Looper和一个MessageQueue。

2、Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个 Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。

3、Looper对象通过loop()方法开启了一个死循环,不断地从Looper内的MessageQueue中取出Message, 然后通过Handler将消息分发传回Handler所在的线程。

9、HandlerThread

HandlerThread其实就是Thread类,它跟普通的Thread相比就是多了一个Looper,但就是多了这么一个Looper让我们省却自定义Looper和担心由于线程并发而造成Looper在未创建成功就被调用的的麻烦。

HandlerThread的常规用法

  • 启动线程,构造参数:String代表线程名,priority代表优先级。优先级范围为-20到19,默认为0,优先级越高,获得的CPU资源更多,反之则越少。-20代表优先级最高,反之19最低。
mThread = new HandlerThread("handler_thread"); 
mThread.start();
  • 创建处理任务的mWorkHandler和更新UI的mUIHandler。
mWorkHandler = new Handler(mThread.getLooper());
mUIHandler = new Handler();
  • mWorkHandler与HandlerThread的Looper关联,并在handleMessage(Message msg)中处理任务,处理完之后通知mUIHandler对UI进行刷新。
  • 在合适的时机退出HandlerThread,比如activity中的onDestroy(),方法有quit()和quitSafely()

具体处理方式要看具体需求,不过总体思路跟上面三个步骤差不多。如果想处理多个任务。就发送多个消息,在mWorkHandler进行处理。

HandlerThread的特点

  • HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。
  • 开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。
    相比多次使用new Thread(){…}.start()这样的方式节省系统资源。
    但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
  • HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
  • 通过设置优先级就可以同步工作顺序的执行,而又不影响UI的初始化;

HandlerThread比较适用于单线程+异步队列的场景,比如IO读写操作,耗时不多而且也不会产生较大的阻塞。对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。

10、关于内存泄露

Handler泄露本质是由于长生命周期的对象Thead间接持有了短生命周期的对象造成。

造成内存泄露的使用场景:

  1. Handler采用匿名内部类或内部类扩展,默认持有外部类 Activity 的引用
  2. Activity退出的时候Handler仍可达,有两种情况:
  • 退出的时候仍有Thread在处理中,其引用着Handler
  • 退出的时候虽然Thread结束了,但Message尚在队列中排队处理或正在处理中,间接持有Handler

原因:异步任务仍然活跃或通过发送的Message尚未处理完毕,将使得内部类实例的生命周期被错误地延长。造成本该回收的Activity实例被别的 ThreadMain Looper 占据而无法及时回收。

正确做法:

  • 无论是 Handler、Handler$Callback 还是 Runnable,尽量采用静态内部类 + 弱引用的写法,确保尽管发生不当引用的时候也可以因为弱引用能清除持有关系
  • 另外在 Activity 销毁的时候及时地终止 Thread、停止子线程的 Looper 或清空 Message,确保彻底切断 Activity 经由 Message 抵达 GC Root 的引用源头。

11、性能优化

复杂情况下,可能会频繁调用sendMessage 往消息队列中,添加消息,导致消息积压,造成卡顿,

1,重复消息过滤

频繁发送同类型消息时,有可能队列中之前的消息还没有处理,又发了一条相同类型的消息,更新之前的数据,这时候,可以采用移除前一个消息的方法,优化消息队列。

    private void sendTypeMsg(){
        Message msg = Message.obtain();
        msg.what = MSG_TYPE;
        handler.removeMessages(MSG_TYPE);
        handler.sendMessage(msg);
    }

2,互斥消息取消

在发送消息时,优先将消息队列中还未处理的信息已经过时的消息 移除,优化队列

3,队列优化-复用消息

创建消息时,优先采用之前回收的消息,避免重复创建对象,引起GC