前言

Handler 音讯机制是 Android 中用于处理线程间通讯的重要机制。在 Android 开发中,UI 线程(主线程)是一个十分重要的线程,用来处理用户交互、界面更新等操作。可是,假如在 UI 线程中履行耗时操作,就会导致界面卡顿乃至 ANR(Application Not Responding)的状况发生。

为了处理这个问题,Android 供给了 Handler 音讯机制,经过它能够完结不同线程之间的通讯。Handler 首要用于向特定的线程发送音讯,然后在该线程中处理这些音讯。经过 Handler,咱们能够将耗时操作放到子线程中履行,然后运用 Handler 将结果发送回 UI 线程进行界面更新。

在运用 Handler 音讯机制时,通常会涉及到以下几个中心概念:

  1. Handler:用于发送和处理音讯的类,能够与 Looper 和 MessageQueue 一起工作来完结音讯的处理。
  2. Looper:用于不断地从 MessageQueue 中取出音讯,并将其分发给对应的 Handler 进行处理。
  3. MessageQueue:用于存储音讯的行列,Handler 发送的音讯会被增加到 MessageQueue 中等待处理。
  4. Message:音讯的载体,包含了要传递的数据及相关信息。

经过 Handler 音讯机制,咱们能够完结线程间的通讯,方便地在不同线程之间进行数据传递和操作处理,保证 UI 界面的流畅性和呼应性。在 Android 开发中,Handler 音讯机制被广泛应用于异步使命处理、守时使命调度等场景。

下图是网上撒播较为形象的 Handler 音讯机制的流程图解:

[Android] Handler 音讯机制原理解析
下面咱们将剖析上图流程,并经过源码来解析 Handler 音讯机制的原理。

1、Handler 处理音讯的流程解析

1)发送音讯

要处理音讯,首要要发送,Handler 类供给了很多发送音讯的办法供给给用户依据需求调用,但不论哪个办法,终究都会调用到 Handler 的私有办法:Handler.enqueueMessage() -> MessageQueue.enqueueMessage(),如下图:

[Android] Handler 音讯机制原理解析

这儿的 MessageQueue 相当于一个单链表,每个 Message 内部都有一个 Message 类型的成员变量 next,在 MessageQueue.enqueueMessage() 办法中,便是将 MessageQueue 中的最终一个 Message 的 next 变量赋值为当前 Message。

2)接收音讯、处理音讯

音讯发送之后,怎样接收并处理呢?这儿就要用到 Looper,咱们来从源码看看履行流程:

public static void loop() {
    ......
    ......
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}
private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        return false;
    }
    ......
    ......
    try {
        msg.target.dispatchMessage(msg);
        ......
    } catch (Exception exception) {
        ......
    } finally {
        ......
    }
    ......
    ......
}

来看 me.mQueue.next() 办法:

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        ......
        ......
    }
}

这个 next() 办法其实便是从 MessageQueue 中轮询取一个 Message,假如没有就回来 null。假如取到了 Message,那么上述 loopOnce() 办法会继续向下运行,到了 msg.target.dispatchMessage(msg) 这行调用的便是 Handler 的 dispatchMessage() 办法,dispatchMessage() 办法又会回调到 Handler 的 handleMessage() 办法处理音讯。

全体流程便是:Looper.loop() -> MessageQueue.next() -> handler.dispatchMessage() -> handler.handleMessage()

2、Handler 的创立

了解完上述流程之后,咱们知道 Handler 音讯机制中最重要的便是 Looper,假如没有正确运用 Looper,Handler 的功能就不能正常完结,那么假如咱们要自定义运用一个 Handler,该怎样做呢?

咱们知道 Android 程序的主线程履行入口是 ActivityThread 的 main() 办法,其间就会开启主线程的 Looper,咱们在自定义的时候模仿它的行为就好了,来看看这儿是怎样做的吧:

public static void main(String[] args) {
    ......
    ......
    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");
}

只看创立 Handler 有关的代码,咱们发现体系创立主线程的 Handler 首要分三步:

  1. 调用 Looper.prepareMainLooper()
public static void prepareMainLooper() {
   prepare(false);
   synchronized (Looper.class) {
       if (sMainLooper != null) {
           throw new IllegalStateException("The main Looper has already been prepared.");
       }
       sMainLooper = myLooper();
   }
}

能够看到办法里检查了 sMainLooper 是否为空,因而这个办法体系调用了之后咱们再调用就会报错,而咱们运用自定义 Handler 时需求调用的是 Looper.prepare()

  1. 调用 sMainThreadHandler = thread.getHandler(),这步便是实例化主线程 Handler,那么咱们也应当在 prepare() 之后实例化一个 Handler。
  2. 调用 Looper.loop(),最终就再用这个办法发动音讯轮询,处理音讯。

可是实例化 Handler 的次序不是固定的,而 Looper.prepare() 办法和 Looper.loop() 办法有必要先后履行。

3、Message 的创立

正常状况下,咱们需求实例化一个目标都是直接调用结构函数,可是在 Handler 机制中运用 Message 则不能直接创立,原因如下:

拿一块 60Hz 的屏幕来说,每秒钟会改写 60 次 UI,每次改写都会发送至少 3 个 Message,分别是:1、保证 UI 能改写的音讯屏障 Message;2、改写 UI 的 Message;3、移除音讯屏障的 Message(这儿说到的音讯屏障下文会解释)。那么每秒钟就会创立至少 180 个 Message 目标,而目标太多就会占内存,从而导致 GC,而重复创立目标再 GC 的行为,就会导致内存抖动,咱们知道每次 GC 都会有一段 STW(Stop The World) 的时刻,频频的 GC 就会导致应用卡顿,因而咱们不能经过直接调用结构函数的方式来创立 Message,下面让咱们看看该如何来防止上述问题。

首要咱们来了解一下 Message 的规划形式:享元规划形式,经过这个规划形式能很好的防止上述问题,和 RecyclerView 的机制类似,Message 也有自己的缓存复用机制 recycle()obtain(),咱们来看看这两个办法的源码:

recycle()

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}
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++;
        }
    }
}

回收的首要流程便是将一个 Message 的目标的成员变量初始化,然后判别当前缓存池的巨细是否小于缓存池最大容量(50),假如小于就存入缓存池中,假如大于,则缓存池满了,就不再回收。

obtain()

public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;
    return m;
}
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 并回来,为空则只能 new() 一个 Message 并回来。

咱们在运用 Message 的过程中应该经过 Handler 中的 obtainMessage() 办法来获取一个 Message 目标:

public final Message obtainMessage()
{
    return Message.obtain(this);
}

这儿的调用链是:handler.obtainMessage() -> Message.obtain(Handler h) -> Message.obtain()

4、音讯屏障机制

在了解音讯屏障机制前,咱们首要要知道这儿的 Message 首要分为三类:一般 Message、屏障 Message、异步 Message,其间由于音讯屏障机制,异步 Message 的履行优先级最高,比方咱们上面说到了 UI 改写的例子,为了不让界面卡顿,那么每次履行 UI 改写的 Message 都有必要马上履行,可是正常状况下 MessageQueue 中的 Message 履行是按次序的,因而就将 UI 改写的 Message 类型设置为异步音讯,使得 UI 改写的 Message 能马上履行。

音讯屏障机制的作用首要体现在以下几个方面:

  1. 控制音讯处理次序: 音讯屏障机制能够保证音讯行列中的音讯按照特定的次序被处理。这对于一些需求严格控制音讯处理次序的场景十分有用,能够防止并发问题,保证音讯的处理契合预期。
  2. 优化功能: 经过音讯屏障,能够将一组相关的音讯放在一个屏障音讯后边,保证它们在一起被处理。这样能够削减音讯处理的次数,优化程序的功能。
  3. 防止死锁: 在多线程环境下,假如音讯处理的次序不正确,可能会导致死锁问题。音讯屏障机制能够协助防止这种状况的发生,保证音讯的处理次序是安全的。

下面咱们来了解一下音讯屏障机制的履行流程。

1) postSyncBarrier() 发送音讯屏障 Message

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

能够看到,先是经过 obtain() 办法获取了一个 Message,随后在音讯行列适宜的方位刺进这个 Message,那一般 Message 和音讯屏障 Message 有什么区别呢?仔细看上面代码,发现并没有给获取到的 Message 的 target 变量赋值(这个 target 变量便是处理这个 Message 的 Handler),也便是说:targetnull 的 Message 便是屏障音讯,MessageQueue 便是靠这个变量来区别音讯屏障和其他 Message 的。

2) postCallbackDelayedInternal() 发送需求优先处理的异步 Message

发送异步音讯终究都会调用到下面这个办法:

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

能够看到在获取音讯之后,调用了 msg.setAsynchronous(true) 办法,将标志位设置为 true,然后才发送出去,这样在 next() 办法中就能分辨出异步 Message 并优先处理。

3) 处理音讯屏障 Message

还记得咱们上文说到的从 MessageQueue 中取出 Message 的 next() 办法吗,不记得的能够翻上去看看,便是那个 for() 循环句子里悉数省略没有张贴上来的那块,

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 && 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;
        }
        ......
        ......
}

咱们能够看到同步代码块里的第一个 if 句子就判别了当前 Message 的 target 是否为 null,即是否为音讯屏障,假如满意条件,则进入 do{} while() 循环找到满意 msg != null && !msg.isAsynchronous() 条件的 Message 并履行,这个 isAsynchronous() 便是判别是否为异步音讯的标志,咱们下面会介绍。

4) removeSyncBarrier() 移除音讯屏障 Message

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 {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();
        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

这儿便是调用了 Message 的 recycleUnchecked() 办法回收了这个 Message。

那么,为什么要移除音讯屏障呢?

由于假如不移除的话,依据 next() 办法中的 for() 循环履行逻辑,在有音讯屏障的状况下只会处理异步音讯,那么一般的音讯就不能履行,因而有必要移除。

5、IdleHandler

IdleHandler 又是什么东西? 来看

public static interface IdleHandler {
    boolean queueIdle();
}

它是一个接口,在 MessageQueue 闲暇时,就会履行它的 queueIdle() 办法,咱们能够经过下面的办法增加 IdleHandler:

Looper.myQueue().addIdleHandler(new IdleHandler(){
    @Override
    public boolean queueIdle(){
        // 在这儿处理你想做的事,比方打印一个日志
        Log.v("TAG", String.valueOf(System.currentTimeMillis()));
        // 这儿的回来值决议了是否保留,假如回来 true,则每次闲暇都履行,回来 false,则一次闲暇履行完之后就被移除了。
        return false; 
    }
});

Kotlin 版本:

Looper.myQueue().addIdleHandler {
    Log.v("${System.currentTimeMillis()}")
    return true
}

那么这个 queueIdle() 办法在什么时候履行呢?仍是在上面说到的 MessageQueue 的 next() 办法中(这么屡次说到,可见这个 next() 办法有多重要):

for (;;) {
    ......
    synchronized (this) {
        ......
        ......
        if (pendingIdleHandlerCount < 0
                && (mMessages == null || now < mMessages.when)) {
            pendingIdleHandlerCount = mIdleHandlers.size();
        }
        if (pendingIdleHandlerCount <= 0) {
            // No idle handlers to run.  Loop and wait some more.
            mBlocked = true;
            continue;
        }
        if (mPendingIdleHandlers == null) {
            mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
        }
        mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    }
    // Run the idle handlers.
    // We only ever reach this code block during the first iteration.
    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);
            }
        }
    }
    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0;
}

能够看到,上面流程是先找到一切的 IdleHandler,遍历履行 queueIdle() 并获取回来值,之后依据回来值判别是否移除这个 IdleHandler。

说了这么多,IdleHandler 一般用在哪呢?用在:

  1. Activity 发动优化:onCreate(),onStart(),onResume() 中耗时较短但非必要的代码能够放到 IdleHandler 中履行,削减发动时刻。
  2. 想要一个 View 绘制完结之后,增加其他依附于这个 View 的 View,当然这个用 View 的 post() 办法也能完结,区别便是前者会在 MessageQueue 闲暇时履行。
  3. 增加一个回来值为 true 的 IdleHandler,在里面履行办法让某个 View 不断履行某个操作。
  4. 一些三方库中会运用,比方 LeakCanary、Glide 中运用到,用来检测内存泄漏。

以上便是对 Handler 音讯机制原理的解析。