Handler

概要

Handler用于线程间的消息传递,它能够将一个线程中的工作切换到另一个线程执行。切换的指标线程与Handler外部持有的Looper所在线程统一。若初始化Handler时未手动设置Looper,Handler会通过ThreadLocal获取并持有以后(初始化Handler时)线程的Looper。当Handler发送一条音讯后,这条音讯会进入指标线程的MessageQueue,指标线程的Looper扫描并且取出音讯,最终由Handler执行这条音讯。

结构器

Handler的结构器大抵分为以下两种:

public Handler(Callback callback, boolean async){}public Handler(Looper looper, Callback callback, boolean async){}复制代码

结构器的参数列表:

  • callback:Handler解决音讯的接口回调,执行音讯时可能会调用该接口。
  • async:默认false,若该值为true,则音讯队列中的所有音讯均是AsyncMessage。AsyncMessage的概念请看后续章节。
  • looper:音讯的查问者,会一直轮询查看MessageQueue是否有音讯。

若调用者传递Looper,间接应用该Looper;否则通过ThreadLocal从以后线程中获取Looper。所以执行工作所在的指标线程不是创立Handler时所在的线程,而是Looper所在的线程

sendMessageAtTime

无论是应用post(Runnable r)还是sendMessage(Message m)发送音讯,最终都会执行到sendMessageAtTime办法。该办法指定了Message的执行者(msg.target=handler)和调用机会(msg.when)。

dispatchMessage

dispatchMessage办法用于执行当时注册的Message和Handler回调,源码如下:

public void dispatchMessage(Message msg) {     if (msg.callback != null) {         handleCallback(msg);     } else {         if (mCallback != null) {             if (mCallback.handleMessage(msg)) {                 return;             }         }         handleMessage(msg);     } }复制代码

能够发现回调的优先级是:Message的回调>Handler的回调(结构器章节中的callback)>Handler子类重写的handleMessage办法。

ThreadLocal

ThreadLocal是一个线程外部的数据存储类,用于寄存以线程为作用域的数据,在不同的线程中能够持有不同的数据正本。通过ThreadLocal就能够很不便的查找到以后线程的Looper。ThreadLocal外部实现的UML类图如下:

通过ThreadLocal查找Looper的流程如下:

  1. 通过Thread.currentThread()获取以后线程对象。
  2. 取出线程对象持有的ThreadLocalMap对象。
  3. 以本身为key,获取ThreadLocalMap中对应Entry的value。

Looper

Looper在Handler中扮演着音讯循环的角色。它会一直查问MessageQueue中是否有音讯。当没有音讯时Looper将始终阻塞。

若以后线程没有Looper,且调用者未传Looper,Handler会因为未获取Looper而报错。解决办法是通过Looper.prepare在以后线程手动创立一个Looper,并通过Looper.loop开启音讯循环:

new Thread("Thread#2") {    @override    public void run() {        Looper.prepare();        Handler handler = new Handler();        Looper.loop();    }}复制代码

Looper提供了quit和quitSafely两种形式来退出一个Looper。区别在于前者会间接退出;后者则是在解决完音讯队列的已有音讯后才平安退出。

Looper所在的线程会始终处于运行状态,所以倡议音讯处理完毕后及时退出Looper,开释线程。

MessageQueue

MessageQueue是音讯的存储队列,外部提供了很多精彩的机制。

IdleHandler

IdleHandler实质上只是一个形象的回调接口,没有做任何操作:

/** * Callback interface for discovering when a thread is going to block * waiting for more messages. */public static interface IdleHandler {    /**     * Called when the message queue has run out of messages and will now     * wait for more.  Return true to keep your idle handler active, false     * to have it removed.  This may be called if there are still messages     * pending in the queue, but they are all scheduled to be dispatched     * after the current time.     */    boolean queueIdle();}复制代码

看上述正文能够理解,MessageQueue会在将要进入阻塞时执行IdleHandler的queueIdle办法,队列阻塞的触发机会是:

  • 音讯队列没有音讯。
  • 队首音讯的执行工夫大于以后工夫。

当咱们心愿一个工作在队列下次将要阻塞时调用,就能够应用IdleHandler。在Android工程中最常见的例子就是:给Activity提供生命周期以外的回调

比方我心愿在布局绘制实现后执行某个操作,然而Activity的onStart和onResume回调均在View绘制实现之前执行,能够看看onResume的官网正文:

/** * ... * <p>Keep in mind that onResume is not the best indicator that your activity * is visible to the user; a system window such as the keyguard may be in * front.  Use {@link #onWindowFocusChanged} to know for certain that your * activity is visible to the user (for example, to resume a game). * ... */  @CallSuper  protected void onResume() {...}复制代码

这种状况下就能够给MessageQueue设置一个IdleHandler,等以后队列中的音讯(包含绘制工作)执行结束并将要进入阻塞状态时,调用IdleHandler的工作,确保工作在绘制完结后执行。

应用形式如下所示:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {     Looper.myLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {         @Override         public boolean queueIdle() {             // do something when queue is idle             // 返回值示意bKeepAlive标识:true->持续应用,false->销毁该Handler             return false;         }     });}复制代码

AsyncMessage和SyncBarrier

顾名思义,SyncBarrier示意同步栅栏(也叫作阻碍音讯),用于阻塞SyncMessage,优先执行AsyncMessage。该机制大大晋升了MessageQueue的操作灵活性。

在进一步理解这两个概念之前,须要先理解MessageQueue插入音讯的机制,MessageQueue的enqueueMessage源码如下(省略了唤醒队列的相干代码):

boolean enqueueMessage(Message msg, long when) {     synchronized (this) {         msg.markInUse();         msg.when = when;         Message p = mMessages;         if (p == null || when == 0 || when < p.when) {             // New head.             msg.next = p;             mMessages = msg;         } else {             // Inserted within the middle of the queue.             Message prev;             for (;;) {                 prev = p;                 p = p.next;                 if (p == null || when < p.when) {                     break;             }         }         msg.next = p; // invariant: p == prev.next         prev.next = msg;     }     return true;}复制代码

从上述源码可知,音讯依照调用机会(when)有序排列,当when等于0时,间接将音讯插在队头;当when等于队列中音讯的when时,将音讯插在这些音讯的前方。

假如这样一个场景:咱们有一个十分紧急的工作,心愿可能优先执行,该如何解决?

很简略,发送一个when为0的音讯,它将主动被插到列表的头部。Handler中也提供了现成的接口:

public final boolean postAtFrontOfQueue(Runnable r){    return sendMessageAtFrontOfQueue(getPostMessage(r));}public final boolean sendMessageAtFrontOfQueue(Message msg) {    return enqueueMessage(queue, msg, 0);}复制代码

将场景降级一下:咱们有一个工作A,其余所有工作都依赖于A,若A未执行,则其余所有工作都不容许执行。

A插入队列的工夫和执行工夫都是不确定的,在此之前,所有工作都不容许执行。依照以后的机制无奈实现该需要,此时SyncBarrier和AsyncMessage就派上了用场,实现流程如下:

  • 调用MessageQueue.postSyncBarrier将SyncBarrier插入队列:SyncBarrier实质上是一个target为空的音讯,插入逻辑和一般音讯统一,也是依照when确定插入地位。SyncBarrier的when固定是SystemClock.uptimeMillis(),因而将其插入到队列的两头(SyncBarrier后面可能会有一些无时延的音讯,前面可能会有带时延的音讯)。
  • 插入SyncBarrier后,轮询音讯直至SyncBarrier排到队列头节点,此时应用next办法查问音讯将主动过滤同步音讯,只执行异步音讯。源码如下所示:
// mMessages示意队首音讯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());}复制代码
  • 插入工作A(将A定义为AsyncMessage),因为SyncBarrier的存在,A将优先被执行(不排除A有时延,此时队列将进入阻塞状态,即使队列里可能存在无时延的同步音讯)。
  • 只有SyncBarrier放在队首,同步音讯将始终被阻塞,音讯队列只能输入AsyncMessage。当工作A执行结束后,须要调用removeSyncBarrier手动将SyncBarrier移除。

Handler提供了接口让咱们插入AsyncMessage,即结构器中的asyc参数。当async为true时,所有通过Handler传递的音讯均会被定义为AsyncMessage(前提是要和SyncBarrier配合应用,不然AsyncMessage没有成果)。

再从新思考SyncBarrier和AsyncMessage机制的利用场景,实质上就是为了阻塞从Barrier音讯到AsyncMessage音讯之间的同步音讯的执行

在Android源码中,布局的绘制就应用了这种机制。在ViewRootImpl的scheduleTraversals办法中,会当时往主线程的音讯队列设置Barrier,再去提交AsyncMessage,阻塞在此期间的所有同步音讯。源码如下:

void scheduleTraversals() {    if (!mTraversalScheduled) {        mTraversalScheduled = true;        // 设置Barrier        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();        // 该办法最终会提交一个AsyncMessage        mChoreographer.postCallback(                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);        if (!mUnbufferedInputDispatch) {            scheduleConsumeBatchedInput();        }        notifyRendererOfFramePending();        pokeDrawLockIfNeeded();    }}复制代码
Tips:对于Barrier的概念在Java并发中多有波及,比方CountDownLatch、CyclicBarrier等。详情请查看《Thinking in Java》21.7章节。

阻塞和唤醒机制

阻塞和唤醒机制是MessageQueue的精华,极大升高了Loop轮询的频率,缩小性能开销。

在IdleHandler章节曾经提及MessageQueue阻塞的机会:

  • 音讯队列没有音讯。
  • 队首音讯的执行工夫大于以后工夫。

next办法的源码如下:

Message next() {    int nextPollTimeoutMillis = 0;    for (;;) {        if (nextPollTimeoutMillis != 0) {            Binder.flushPendingCommands();        }        // 要害办法,将线程阻塞nextPollTimeoutMillis毫秒,若nextPollTimeoutMillis为-1,线程将始终处于阻塞状态。        nativePollOnce(ptr, nextPollTimeoutMillis);        synchronized (this) {            // Ignore SyncBarrier code             final long now = SystemClock.uptimeMillis();            Message prevMsg = null;            Message msg = mMessages;            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;                    msg.markInUse();                    return msg;                }            } else {                // No more messages.                nextPollTimeoutMillis = -1;            }            // Ignore IdleHandler code            if (pendingIdleHandlerCount <= 0) {                // No idle handlers to run.  Loop and wait some more.                mBlocked = true;                continue;            }        }    }}复制代码

插入音讯时唤醒MessageQueue的机会(假如队列处于阻塞状态):

  • 队首插入一条SyncMessage。
  • 队首是一个栅栏,且插入一条离栅栏最近的AsyncMessage。

enqueueMessage办法的源码如下:

boolean enqueueMessage(Message msg, long when) {    synchronized (this) {        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 {            // Inserted within the middle of the queue.  Usually we don't have to wake            // up the event queue unless there is a barrier at the head of the queue            // and the message is the earliest asynchronous message in the queue.            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;}复制代码

唤醒的第二种机会特意强调了插入离Barrier最近的AsyncMessage。对于如下的阻塞状况,插入AsyncMessage时不须要将其唤醒:

[图片上传失败...(image-d6b8ad-1636620493886)]

Handler内存透露剖析

理解了Handler的外部原理后,再来剖析由Handler引起的内存泄露问题:

  1. 当定义了一个非动态的Handler外部类时,外部类会隐式持有外围类的援用。
  2. Handler执行sendMessageAtTime办法时,Message的target参数会持有Handler对象。
  3. 当Message没有被执行时(比方now<when),若退出了Activity,此时Message仍然持有Handler对象,而Handler持有Activity的对象,导致内存泄露。

解决方案:

  1. 将Handler定义为动态外部类。
  2. 退出Activity时清空MessageQueue中对应的Message。

相干视频举荐:
【Android源码】handler运行的整个流程源码解析

本文转自 https://juejin.cn/post/6947962229291647007,如有侵权,请分割删除。