为什么使用Handler机制?

Android应用位于一个进程中运行,应用启动时 Android 系统会分配一个 Jvm 进程来执行,同时进程中会启动一个主线程 MainThread(也称作为 UI 线程),负责处理与 UI 相关的事件,比如组件的渲染、用户与屏幕之间交互事件并将事件分发到对应的组件进行处理等;

Android 开发中只能通过主线程来更新UI(谷歌进行了开发约束):

  • 因为UI操作并不是线程安全的,如果多线程进行UI操作,会导致结果不可预测,所以要在同一个线程即主线程进行UI操作;

  • UI操作也不能加锁,加锁会增加性能的下降,无法及时更新渲染UI组件,同时也会增加UI操作的复杂度;

对于一些耗时操作,不能在主线程中进行,因为主线程一旦执行耗时操作,那么表现在屏幕上就是卡顿甚至无响应,因此需要使用子线程来完成这些操作;

耗时操作只能放在子线程,但是子线程无法更新UI,Handle 就是用于解决子线程更新UI的矛盾,更准确来说可以用来进行线程之间的通信,使得子线程可以向主线程发出更新UI的通信,进而主线程接收到这些信息后进行UI的更新;


Handler架构

image-hrgb.png

四大部分:

  • Message:消息实体,用于封装消息的一些信息,比如执行时间、执行代码等;

  • MessageQueue:消息队列,内部是基于单链表实现的 message 执行时间升序 排序的优先队列(并没有继承 PriorityQueue)

    • 用于存放供 Handler 处理的消息;

    • 通过 enqueueMessage 将消息入队,通过 next 将消息出队;

  • Looper:类似一个泵,不断的从 MessageQueue 中查看是否有消息并将其出队,交由 Handler 处理;

  • Handler :可以发送消息

    • 实际上都会调用 MessageQueue#enqueueMessage 方法将消息发送;

    • 通过 Looper 取出消息队列中的消息交由 Handler#dispatchMessage 进行分发,最后到 Handler#handleMessage 进行处理,也就是自己实现 Handler 时通常会重写的方法,用于处理接收到的消息;


代码调用链

sequenceDiagram participant A as Handler participant B as MessageQueue participant C as Looper autonumber Note left of A: post、sendMessage等发送消息的方法 <br> 最终都会调用这个方法 A->>A: Handler.enqueueMessage() A->>B: MessageQueue.enqueueMessage() loop Looper.loop() Note right of C: Looper持续查询<br>消息队列是否存在消息 C->>B: MessageQueue.next() B-->>C: Message返回 C->>A: Handler.dispatcher() A->>A: Handler.handleMessage() Note left of A: Handler最终调用<br>handleMessage处理消息 end

源码分析

Handler发送消息

Handler中发送消息的函数有多个:

  • post :发送一个立即执行的 Runnable

  • postDelayed :发送一个延迟执行的 Runnable

  • postAtTime :发送一个指定时间执行的 Runnable

  • postAtFrontOfQueue :发送一个 Runnable 到队首,下一次迭代时执行;

  • sendMessage :发送一个 Message

  • ...

post 为例:首先封装成一个 Message,并调用 sendMessageDelayed

// Handler#post
public final boolean post(@NonNull Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

检查参数,并调用 sendMessageAtTime 指定在某个时间点(当前时间+延迟时间)执行

// Handler#sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

检查当前消息队列是否存在,准备入队:

// Handler#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);
}

将发送的消息的执行体设置为当前 Handler实例,将其入队消息队列:

// Handler#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);
}

MessageQueue入队&出队

Handler#enqueueMessage 最终调用的是 MessageQueue#enqueueMessage

该方法就是将 传递的 msg 参数插入到内部维护的单链表,按照 when 升序排列;

// MessageQueue#enqueueMessage
boolean enqueueMessage(Message msg, long when) {
	...
	synchronized (this) {
		...
		msg.when = when;
		// 头部节点(单链表)
		Message p = mMessages;	
		
		if (p == null || when == 0 || when < p.when) {
		    // 将当前 msg 插入为队首
		    msg.next = p;
		    mMessages = msg;
		    needWake = mBlocked;
		} else {
		    ...
	
		    Message prev;
			// 在链表中查找到合适的位置(按照when升序排列)插入
		    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;
		}
		...
	}
}

既然有入队操作,那么就会有出队操作:

next 用一个死循环轮询单链表的头部是否为空:

  • 如果不为空则表示有消息,则判断消息的执行时间 when 是否已经到了

    • 如果到了则返回该消息

    • 否则等待指定时间

  • 如果为空则堵塞等待有消息到来;

// MessageQueue#next
Message next() {
    ...
	
	// 死循环,直到消息队列中有消息出现才退出
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
		// 底层实现,堵塞指定时间
        nativePollOnce(ptr, nextPollTimeoutMillis);
		
        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) {
                if (now < msg.when) {
                    // 当前时间小于消息执行的时间,要等待
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
					// now >= msg.when: 消息可以执行了
                    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;
            }
            ...
        }
        ...
    }
}

到这里可能会有疑问,假设已经有一个消息被拿出来,但是距离执行的时间还有很久,消息队列在这段时间会堵塞线程,如果在此期间有新消息入队,那岂不是没办法处理这个新的消息?

  • enqueueMessage 中有一个 needWake 字段,表示是否需要唤醒当前堵塞的线程,也就是调用 nativeWake 方法

  • 当有新的消息入队且当前消息队列正在堵塞,才会调用 nativeWake 唤醒当前线程;

  • 这样就保证了在堵塞时间内能够及时处理新入队的消息;

Looper取出消息

loop 方法内部有一个死循环,只有 looperOnce 返回 false才会终止,但是只有消息队列关闭的时候才会返回 false,所以这里的 loop 会一直循环;

// Looper#loop
public static void loop() {
	// 拿到当前线程的 looper
	final Looper me = myLooper();
	
	...
	
	for (;;) {
	    if (!loopOnce(me, ident, thresholdOverride)) {
	        return;
	    }
	}
}

loopOnce 负责从消息队列取出消息,如果消息队列中没有消息,则会堵塞(详看 MessageQueue#next());

取出消息后将会调用其 target 也就是发送消息的 HandlerdispatchMessage()

// Looper#loopOnce
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
	// 取出消息队列中的队首消息,如果没有则会堵塞
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
	// logging relatived
    ...

    long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
    try {
		// 将取出的消息分发到指定的 Handler
        msg.target.dispatchMessage(msg);
        ...
    } catch (Exception exception) {
      	...
    }
   	...
    msg.recycleUnchecked();
    return true;
}

Handler分发消息

dispatchMessage 将消息 msg 分发各处:

  • 如果消息指定了 callback 则调用 handleCallback

  • 如果 Handler 在创建时传入了 Callback (接口,内部有一个 handleMessage方法),则交由该处理;

  • 如果上面都没有指定,则交由 Handler 内部的 handleMessage 处理(默认是空实现,需要重写)

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

到这里,Handler 整体的发送消息、分发消息的流程就结束了


问题

为什么不将消息给Handler直接处理,而是需要消息队列这些操作?

Handler 是用于线程之间通信的;

在不同的线程中拿到的 Handler 实例是相同的,但是 Looper 是不同的,每个线程都至多只能有一个 Looper;

  • 轮询以及分发的操作由 Looper 完成

  • 也就是说分发执行的操作是在 Looper 所在的线程进行的;

如果直接给 Handler 处理,那执行消息的线程也就是当前线程,而不是我们期望的线程执行;

因此需要发送给消息队列,由轮询的 Looper 负责调度这些消息,并在 Looper 所在的线程中执行;


ThreadLocal在Handler的作用

首先要知道,线程是共用同一块内存区域,因此创建的对象、修改对象都是同一个对象;

ThreadLocal 用于在不同的线程中创建不同的副本;

  • ThreadLocal 是一个线程内部的数据存储类,在指定的线程中存储数据,而存储数据后只有指定线程中可以获取到存储的数据,对于其他的线程就无法获取到数据;

  • 每个 Thread 都有自己的数据副本 Thread.thradLocals,而 ThreadLocal 就是在对应线程的数据副本写入数据(以 ThreadLocal为键,set 的数据为值)

// ThreadLocal

// 拿到对应线程的 ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
// 创建对应线程的 ThreadLocalMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

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

在 Handler 机制中,每个线程中都有一个 Looper对象,这个 Looper 对象就是用 ThreadLocal 实现不同线程有不同的副本:

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

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

Looper、Handler、MessageQueue的引用关系?

一个线程中至多有一个 Looper(主线程中的 Looper 会自己创建,而其他的线程需要使用 Looper.prepare 创建)

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

而一个 Looper 在创建时同时也会创建对应的 MessagQueue,也就是说每个 Looper 都有自己对应的消息队列;

// Looper 的构造函数
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

一个线程中可以有多个 Handler 存在,但消息发送和轮询都是由该线程的 Looper 和 MessageQueue 完成;

Message.target 来区分不同的 Handler

在主线程中创建了 Handler 对象后 (调用无参构造方法,已废弃,仅说明),将会把当前线程即主线程与 Handler 关联;

主线程在创建的时候就自动创建了 Looper 对象以及 MessageQueue;

子线程拿到了主线程中 Hander 对象的引用后,发送的消息都会进入对应 Handler 的消息队列中,也就是主线程的消息队列;

由主线程的 Looper 取出消息并分发给 Handler 执行;

graph LR A(子线程msg) --> F(主线程Handler引用) F --> B(主线程MessageQueue) B --> C(主线程 Looper) C --> D(主线程Handler#handleMessage)

MessageQueue如何保证线程安全?

多个子线程向主线程的 Handler 发送消息,那 MessageQueue如何保证线程安全?

MessageQueue#enqueueMessage 方法中,有一个同步块:

boolean enqueueMessage(Message msg, long when) {
    ...

    synchronized (this) {
		...
		// insert msg into queue
	}
}

保证多个线程同时调用该方法时,只有一个线程能够获取到 MessageQueue 对象,只有当该线程完成插入操作后,其他线程才有机会获取到 MessageQueue 对象;


Handler如何导致内存泄漏问题?

Java知识:非静态内部类 & 匿名内部类都默认持有外部类的引用

如果是使用内部类(包括匿名类)来创建 Handler 对象,那么该内部类会隐式持有一个外部类的引用(在开发中通常是一个 Activity);

image-rezt.png

IDE代码中也会提示内存泄漏

在 Activity 退出后,而消息队列中还有没有处理完的消息:

  • Message.target 持有对应 Handler 引用,而 Handler 又隐式持有外部类 (Activity)的引用;

  • 这种引用关系将会一直保持,直至消息队列中的所有消息被处理完毕;

  • 如果在未处理完毕时需要进行销毁外部类,就会导致内存无法回收而内存泄漏

如何解决?

  1. 避免使用匿名内部类,改用 Handler(Looper, Callback) 构造方法来处理,而不是使用匿名内部类重写 Handler#handleMessage 方法

    private val mHandler = Handler(Looper.getMainLooper()) { msg ->
        // handle message
        true
    }
  2. 在 Activity 销毁的时候,将 Handler 中所有消息都清空(这样可能会导致某些消息没有得到正确处理)

    private val mHandler = object: Handler() {
       override fun handleMessage(msg: Message) {
           // handle...
       }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        mHandler.removeCallbacksAndMessages(null)
    }
  3. 使用 静态内部类 + 弱引用 的方式来处理(这种方法要继承 Handler)

    class MainActivity : AppCompatActivity() {
        companion object {
            
            private class MainHandler(activity: Activity) : Handler() {
                
                private val activity: WeakReference<Activity> = WeakReference(activity)
                override fun handleMessage(msg: Message) {
                    super.handleMessage(msg)
                }
            }
        }
        private val mHandler = MainHandler(this)
    }

总的来说,还是建议使用第一种方式,因为后面两种方法所使用的 Handler() 无参构造方法已经标记为废弃;


子线程创建Handler需要注意的事情

Handler需要一个 Looper,但是子线程中并不会像主线程一样会自动创建 Looper

因此在创建Handler时需要先调用 Looper.prepare() 创建 Looper

Handler 创建时会检查 Looper 是否为空

mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException(
        "Can't create handler inside thread " + Thread.currentThread()
                + " that has not called Looper.prepare()");
}

然后需要调用 Looper.loop() 对消息队列进行轮询(如果没有调用该方法,则发送的消息不会被处理)

Looper.loop() 这个方法会堵塞当前线程,换句话说,当执行了这个方法,后面的代码都无法执行;

当子线程的消息队列为空时,Looper一直堵塞线程会浪费资源,因此需要结束Looper的死循环;

Looper.quit()Looper.quitSafely() 支持将退出循环,两者之间的区别就是

  • 前者不管是否已经执行,直接将所有的消息清空

  • 后者会将已经到了执行时间的消息保留并执行,将未执行的消息清空;

// MessageQueue#quit
// Looper#quit实际上调用的就是这个方法
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        if (safe) {
			// 移除未执行的消息
            removeAllFutureMessagesLocked();
        } else {
			// 移除所有消息,不管是否要执行
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

总结来说,子线程创建Handler需要完成三个步骤:

  • 设置 Looper.prepare()

  • 调用 Looper.loop()

  • 结束 Looper.quit()Looper.quitSafely()


主线程中的Looper死循环为什么不会导致应用卡死?

主线程中所有执行的代码都会封装为消息,进入消息队列由 Looper 处理的,Looper 死循环外并没有处理其他事情,因此不会导致应用卡死;

首先了解下应用的启动流程:Launcher进程(桌面点击应用图标)-> AMS(请求启动主Activity)-> Zygote进程(创建并启动应用进程)-> 启动 ActivityThread#main (也就是主线程)

ActivityThread的main方法就是应用的入口:

  • 可以看到内部启动了一个线程,创建了一个 Looper与主线程关联;

  • 随后就是开启 Looper 的死循环,处理所有消息;

  • 如果Looper退出循环还会抛出一个异常,说明这个Looper的死循环的持续时间与应用的生命周期相同;

// ActivityThread#main
public static void main(String[] args) {
   	...

	// 创建主线程所需的 Looper
	Looper.prepareMainLooper();

	...

    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

	...
	// 开启循环处理消息
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

那是如何处理消息的呢?

ActivityThread 中有一个内部类 H 继承了 Handler:

  • 内部定义了所有消息的类型,以及如何去处理这些消息(重写了 handleMessage

// ActivityThraead#H
class H extends Handler {
	public static final int BIND_APPLICATION        = 110;
	public static final int EXIT_APPLICATION        = 111;
	public static final int RECEIVER                = 113;
	...
	
    public void handleMessage(Message msg) {
	    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
	    switch (msg.what) {
         	case BIND_APPLICATION:	
				...
				break;
			case ...
		}
	...
}
  • EXIT_APPLICATION:应用退出,就是退出 Looper 的循环;

    case EXIT_APPLICATION:
        if (mInitialApplication != null) {
            mInitialApplication.onTerminate();
        }
        Looper.myLooper().quit();
        break;
  • RESUME_ACTIVITY :就是 Activity的 onResume 生命周期(新版本的源码似乎没有),最终调用的是 ActivityThread#handleResumeActivity

  • ...

回到正题,Looper中的死循环堵塞主线程,但是主线程中所有事件操作都是由 Looper 进行读取;

当 Looper 堵塞在 MessageQueue.next() 时,说明当前应用没有事件发生,换句话说,就是在休眠状态,并不是卡死状态;

而卡死状态是 ANR,当应用超过5秒没有响应输入事件时才会发生的;

Handler.dispatcheMessage 中执行耗时操作,导致其他输入事件没有响应时,才会发生ANR;

MessageQueue#next()堵塞时新加入的消息如何处理?

MessageQueue#next 堵塞实际是调用了 Native 层的 nativePollOnce() 方法进行精准时间的堵塞

  • 在 Native 层,将进入 pullInner() 方法,使用 epoll_wait 阻塞等待以读取管道的通知。

  • 如果没有从 Native 层得到消息,那么这个方法就不会返回。

  • 此时主线程会释放 CPU 资源进入休眠状态。

当有消息加入时,会调用 MessageQueue#enqueue() 添加消息,在必要时(当新加入的消息需要插入到头部时)调用 nativeWake() 方法去唤醒:

  • 向管道中写入一个消息,结束上述的堵塞,触发上面的 nativePollOnce() 方法返回

  • 进而能继续处理新加入的消息


Message使用

obtain & recycle

Message 使用了 享元设计模式,内部维护了一个池(基于单链表实现的)

private static Message sPool; // 单链表的头

每次获取对象时不建议使用 new 创建,而是使用 Message#obtain 来获取一个对象:

  • 避免内存抖动,并回收复用已有的对象;

// 从内部维护的池
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#recycle 将 Message 对象清空并加入到内部维护的池

public void recycle() {
	// 检查该 Message 是否在使用;
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

Message#recycleUnchecked 将 Message 置空并加入到内部维护的池:

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    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++;
        }
    }
}

参数

what :(int) 用户自定义的消息码,用于区分每个消息的类型

arg1arg2 :(int)如果只需要存储整型数据,则使用这两个字段,而不用 setData

when :(long,不可以自己设置)表示该消息传递给 target 的时间点

data :(Bundle)消息中包含的数据


同步屏障

Handler 中的Message 可以分为两类:同步消息、异步消息;

  • 通过 Message#setAsynchronous(boolean) 可以设置消息的同步或者异步:

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }
  • 通过 Message#isAsynchronous() 可以判断消息是否为异步:

    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

其实还有第三种消息——屏障消息: target==null

在一般情况下,两种消息没有什么差别;

但是在设置了 同步屏障 之后,可以拦截 Looper 对同步消息的获取和分发;

  • Looper只会获取和处理异步消息;

  • 没有异步消息时会进入堵塞状态;

简单来说,同步屏障为 Handler 消息机制添加了一种简单的优先级机制,异步消息的优先级要高于同步消息;

设置同步屏障

通过 MessageQueue#postSyncBarrier 设置同步屏障(该方法有 @UnsupportedAppUsage注解修饰,不能直接调用,需要通过反射)

  • 实际上是向队列中发送了一个消息,这个消息没有指定 target

  • 在设置屏障的时间点前的所有同步消息都会被处理,在设置屏障后所有同步消息都会被推迟执行(直到移除屏障后才会执行)

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
		// 插入到队首
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
			// 插入到队中
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

该方法会返回一个代表当前屏障的 token 数值,需要通过 MessageQueue#removeSyncBarrier(int) 传入该 token 才能移除对应的屏障消息;

  • 从队列中找出对应 token 的屏障消息并移除;

  • 这里和设置同步屏障不同,移除屏障后需要唤醒队列来及时处理后面的同步消息

public void removeSyncBarrier(int token) {
    // Remove a sync barrier token from the queue.
    // If the queue is no longer stalled by a barrier then wake it.
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
			// prev == null: 屏障消息位于队首,意味着当前没有异步消息等待处理
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

同步屏障是如何生效?

MessageQueue#next() 检索下一个出队的消息前,会先判断队首 msgtarget 是否指定,上面设置屏障的消息就是没有设置 target 的;

因此就会进入下面的判断,检索队列中的 异步消息 ,跳过所有的同步消息;

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) {
	...
}

如何发送异步消息?

在 Handler 的构造方法中,通常有 boolean async 参数,传入 true 表示 Handler 发送的消息都是异步消息;

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

同步屏障应用

Android4.1之后增加了 Choreographer 机制,用于同 Vsync 机制配合,统一动画、输入和绘制时机。

当 UI 更新时,为了让主线程更快的响应 UI 更新的事件,通过设置同步屏障,让异步消息更快的得到处理

// ViewRootImpl#scheduleTraversals
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
		// 开启同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 发送异步消息
		mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}


// 上面发送内部消息的方法最终会调用到该方法
// Choreographer#postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType
                + ", action=" + action + ", token=" + token
                + ", delayMillis=" + delayMillis);
    }
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
			// 消息的类型设置为异步消息
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}