Kaithmy 带你深入了解 Handler

792 阅读11分钟

什么是 Handler ?

下面是官方 Handler 的注释说明

There are two main uses for a Handler:
(1) to schedule messages and runnables to be executed at some point in the future; 
and 
(2) to enqueue an action to be performed on a different thread than your own.

简单来讲,HandlerAndorid 用于 消息调度执行线程间通信 的工具

为什么要设计有Handler?

因为 Android 系统的特殊性,其中 UI 线程需要处理众多与界面交互的操作,并且不能被阻塞卡死,又因为 Java 中线程通信都是阻塞式方法,故需要重新设计一套符合以上需求的机制;

认识 Handler 消息处理机制中的四个主要角色

Handler

Handler 顾名思义,是消息的发送者和处理者,其中消息发送最终都会走到 enqueueMessage() 这个方法,而消息处理都会走 handleMessage() 这个方法;

Looper

Looper ,循环器,用于对消息队列 MessageQueue 中的 消息 进行遍历,其中 loop() 方法最为重要,包含了遍历 MessageQueue 、取 Message 、分发 Message 到 Handler 进行处理等逻辑;

MessageQueue

MessageQueue,消息队列,适用于存储要处理的 消息 Message,其中 enqueueMessage()next() 分别对应消息入队及取读取;

Messsage

Message ,消息,用于传递需要处理的数据,一般由 whattargetdata 组成,必要时会用上 arg1arg2;其中 what 一般用在 handleMessage() 中进行逻辑分发,data 一般用于复杂数据传递,是一个 Bundle 类型,对于一些简单的数据传递,如 int,可以使用 arg1arg2 进行传递;

消息发送流程简单解读

通过 Handler 发送的消息最终都会走到 enqueueMessage() 这个方法中,其中参数 msgtarget 会指向当前 handler,即要处理该消息的 Handler,而 Handler 中的 enqueueMessage() 会调用 MesssageQueue 中的 enqueueMessage() 方法; 让我们来看看 MessageQueue.enqueueMessage()

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

撇开其他的不看,我们只看

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

可以看出,当 当前队列为空 p = null 或者 when = 0 或者 当前要添加的 msg 触发时间早于队列第一个 message 时,进入 if 语句 ,由注释和代码知其作用是将要添加的 msg 置于队列头也就是第一个位置,并且唤醒事件队列 MessageQueue;进入 else 语句时,由代码知其作用是将要添加的 msg 插入到 MessageQueue 中,并且在遇上同步屏障,并且要添加的消息是最早的异步消息的时候唤醒队列;什么是异步消息?同步屏障又是啥?先别懵,后面我们会讲到,先记着有这么个东西就可以了;

消息取出处理流程

Looper.loop()

还记得我们在自己的线程使用 Handler 的流程吗?最终我们使用 Looper.loop() 方法便是启动消息取出消费的入口方法;

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        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();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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();
        }
    }

我们只看 for (;;) 循环中的内容

            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

取出队列中的消息 msg,当 msgnull 时退出,那什么时候 msgnull 呢?我们后面再分析; 继续看

           try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } 

我们关注这句 msg.target.dispatchMessage(msg),熟悉吗?前面发送流程提到过 msg.target 即为要处理消息的 handler,我们接着看 Handler.dispatchMessage()

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

先不去关心 if ,在 else 里我们看到了熟悉的身影,handleMessage(msg),一般我们在写 handler 都会要求复写的方法,用来处理消息的逻辑会写在这里,注释也说明了;

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(@NonNull Message msg) {
    }

MessageQueue.next()

我们接着详细看看这个方法,看看是如何从消息队列中取出这个消息的;

@UnsupportedAppUsage
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        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);

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

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

for (;;)

我们先看循环 for (;;) 中的内容,nativePollOnce(ptr, nextPollTimeoutMillis) 这是利用 Linuxpoll 机制进行阻塞但释放 CPU ,其中参数 nextPollTimeoutMillis 传入不同的值代表不同的含义,具体对应如下

nextPollTimeoutMillis = -1,一直阻塞;
nextPollTimeoutMillis = 0,不会阻塞,立即返回;
nextPollTimeoutMillis > 0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回;

synchronized (this)

我们接着来看 synchronized (this) 中的代码,先看第一个 if 语句

               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/else 语句

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

外层

else

// No more messages.
nextPollTimeoutMillis = -1;

我们先看外层,当 msg == null 时,也就是队列为空时,将 nextPollTimeoutMillis 赋值 -1,使其处于阻塞状态,直到有消息入队;

内层

if

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

接着我们看内层 if,当 if (now < msg.when) 时,也就是当最近的一个消息的执行时间晚于前时间,则执行 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE),即为 nextPollTimeoutMillis 赋值需要等待的超时时间 (int) Math.min(msg.when - now, Integer.MAX_VALUE) ,从注释 // Next message is not ready. Set a timeout to wake up when it is ready. 亦可看出其作用;

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,当遇有屏障导致 prevMsg = null 时,将 preMsg.next 指向 msg.next,否则将 mMessages 指向 msg.next,之后将 msg.next 置为 null,并标记为使用中 msg.markInuse(),最终返回 msg

return null

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
    dispose();
    return null;
}

接着看这段代码,当入参 mQuittingtrue 时,返回 null,是不是很熟悉,前面说到 Looper.loop() 退出条件是取得的 msgnull;那么 mQuitting 什么时候为 true 呢?

mQuitting = true

通过定位,我们找到 mQuitting = true 的方法为 MessageQueue.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);
        }
    }

可见,只有调用了 MessageQueue.quit() 才会将 mQuitting 置为 true; 进一步分析这个方法,当 safetrue 时,调用 removeAllFutureMessagesLocked(),移除该时刻之后的所有消息,当 safefalse 时,调用 removeAllMessagesLocked(),移除所有的消息;

safe == true/false

那么什么时候 safe == true 呢?通过定位,我们在 Looper.quit()Looper.quitSafely() 方法中找到答案;

    public void quit() {
        mQueue.quit(false);
    }

可以看到,在 Looper.quit() 中,入参 safe == false

    public void quitSafely() {
        mQueue.quit(true);
    }

Looper.quitSafely 中,入参 safe == true

拓展延伸

IdleHandler

定义

我们先来看 IdelHander 注释

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

大意说的是当消息队列中的暂时消息消费完了,就会回调这个接口方法,返回 true 保持这个回调监听,下次消费完还会触发,返回 false 移除这个监听,下次消费完不触发;

作用

从注释中我们可以窥探出其作用,是在消息队列暂时处理完后做一些工作,利用这个特性我们可以做一些事情;

Andorid 中的应用

ActivityThread.handleResumeActivity() 中,拉到最后一行,可以看到其实际应用

Looper.myQueue().addIdleHandler(new Idler());

那么这个 Idler() 具体做了哪些内容呢?我们来看看

    private class Idler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            ActivityClientRecord a = mNewActivities;
            boolean stopProfiling = false;
            if (mBoundApplication != null && mProfiler.profileFd != null
                    && mProfiler.autoStopProfiler) {
                stopProfiling = true;
            }
            if (a != null) {
                mNewActivities = null;
                IActivityTaskManager am = ActivityTaskManager.getService();
                ActivityClientRecord prev;
                do {
                    if (localLOGV) Slog.v(
                        TAG, "Reporting idle of " + a +
                        " finished=" +
                        (a.activity != null && a.activity.mFinished));
                    if (a.activity != null && !a.activity.mFinished) {
                        try {
                            am.activityIdle(a.token, a.createdConfig, stopProfiling);
                            a.createdConfig = null;
                        } catch (RemoteException ex) {
                            throw ex.rethrowFromSystemServer();
                        }
                    }
                    prev = a;
                    a = a.nextIdle;
                    prev.nextIdle = null;
                } while (a != null);
            }
            if (stopProfiling) {
                mProfiler.stopProfiling();
            }
            applyPendingProcessState();
            return false;
        }
    }

大意是 ActivityonStop/onDestroy 是依赖 IdleHandler 来回调的,正常情况下在主线程空闲时间正常调用,如遇某些场景导致迟迟无法回调,系统提供了兜底机制,在 onResume 执行的 10s 之后还未调用则主动触发(具体代码调用跟踪就不赘述了,大家自行查阅);

同步屏障

定义

可以理解为一个栅栏,放置了这个栅栏后只允许异步消息通过,同步消息则被拦住无法通过,当把栅栏拿走时,异步消息和同步消息都可以通过;

作用

通过定义我们可知其作用是使异步消息优先执行;

Android 中的应用

我们都知道 Activityresume 最终是由 ActivityThread.handleResumeActivity() 来处理的,我们找到这段代码

/** 部分代码省略 **/

if (!a.mWindowAdded) {
    a.mWindowAdded = true;
    wm.addView(decor, l);
} else {
    // The activity will get a callback for this {@link LayoutParams} change
    // earlier. However, at that time the decor will not be set (this is set
    // in this method), so no action will be taken. This call ensures the
    // callback occurs with the decor set.
    a.onWindowAttributesChanged(l);
}

/** 部分代码省略 **/

其中 wm 我们知道是 WindowManagerImpl,找到其中的 addView() 方法

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

mGlobalWindowManagerGlobal 类型,我们找到其 addView() 代码

try {
    root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
    // BadTokenException or InvalidDisplayException, clean up.
    if (index >= 0) {
        removeViewLocked(index, true);
    }
    throw e;
}

其中 rootViewRootImpl 类型,找到其 setView() 方法代码

// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();

继续看 requestLayout()

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

找到 scheduleTraversals() 继续看

@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

通过 mHandler.getLooper().getQueue().postSyncBarrier() 这句我们看到发送了一个同步屏障,紧接着调用 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 我们进入这个方法看一下具体做了什么事儿,mChoreographerChoreographer 类型

@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, 0);
}

继续看 postCallbackDelayed() 这个方法

@TestApi
public void postCallbackDelayed(int callbackType,
        Runnable action, Object token, long delayMillis) {
    if (action == null) {
        throw new IllegalArgumentException("action must not be null");
    }
    if (callbackType < 0 || callbackType > CALLBACK_LAST) {
        throw new IllegalArgumentException("callbackType is invalid");
    }

    postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

继续看 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);
        }
    }
}

可以看到 msg.setAsynchronous(true) ,由此可知在设置了同步屏障后发送了这样一个异步消息,那么这个异步消息处理什么事情呢?继续跟踪后可知是处理 VSYNCDO_FRAME 的,具体代码就不再跟了;当然了,有设置同步屏障也就有移除,那么什么时候移除呢?注意到 mTraversalRunnable 了吗?我们跟一下,mTraversalRunnableTraversalRunnable 实体,我们看看 TraversalRunnable 做了啥

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

继续看 doTraversal()

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

看到那句熟悉的语句了吗? mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier) 由此可知,在设置了同步屏障后,发送了一个异步消息处理 VSYNCDO_FRAME 并在处理完成后 移除同步屏障,继续执行 performTraversals(),后面就不跟了,有兴趣的同学自行查看吧;

ThreadLocal

定义

我们先来看看官方注释

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 **/

通俗来讲,ThreadLocal 是用来保存线程专有变量的,以此来实现线程间变量隔离;

作用

从定义可知,主要是用来实现线程隔离的;

在代码中的体现

我们知道,一般来说 Looper 是线程独有的,一个线程只能有一个 Looper,那么系统是如何实现的呢? 让我们来看 Looperprepare() 方法

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

sThreadLocal 是一个 ThreadLocal<lLooper> 类型的变量,prepare() 方法先从 sThreadLocalLooper ,如果取到了不为 null,则抛出异常 每个线程只能有一个Looper,否则的话 new 一个 Looper,并设置给 sThreadLocal,以此实现 每个线程只有一个Looper

原理

我们先来看 ThreadLocal.set(t) 方法

线程持有 ThreadLocalMap

/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

调用 getMap(t) 方法并传入当前线程 t 得到 ThreadLocalMap 类型的 map,判断 map 是否不等于 null,是则调用 mapset(this,value) 方法,否则调用 createMap(t,value) 方法;

让我们接着看一下 getMap(t) 这个方法干了什么。

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

返回的是线程 t 的变量 threadLocals,而变量是 ThreadLocalMap 类型的,那么 ThreadLocalMap 是什么呢?

ThreadLocalMap

 /**
  * ThreadLocalMap is a customized hash map suitable only for
  * maintaining thread local values. No operations are exported
  * outside of the ThreadLocal class. The class is package private to
  * allow declaration of fields in class Thread.  To help deal with
  * very large and long-lived usages, the hash table entries use
  * WeakReferences for keys. However, since reference queues are not
  * used, stale entries are guaranteed to be removed only when
  * the table starts running out of space.
  */
 static class ThreadLocalMap {

     /**
      * The entries in this hash map extend WeakReference, using
      * its main ref field as the key (which is always a
      * ThreadLocal object).  Note that null keys (i.e. entry.get()
      * == null) mean that the key is no longer referenced, so the
      * entry can be expunged from table.  Such entries are referred to
      * as "stale entries" in the code that follows.
      */
     static class Entry extends WeakReference<ThreadLocal<?>> {
         /** The value associated with this ThreadLocal. */
         Object value;

         Entry(ThreadLocal<?> k, Object v) {
             super(k);
             value = v;
         }
     }

     /**
      * The initial capacity -- MUST be a power of two.
      */
     private static final int INITIAL_CAPACITY = 16;

     /**
      * The table, resized as necessary.
      * table.length MUST always be a power of two.
      */
     private Entry[] table;
     
     /**** 省略 ****/
}

可以看出 ThreadLocalMap 使用数组 table 存放 Entry,而 EntrykeyThreadLocalvalue 为 存放的对象 Object,并且数组 table 的初始化大小为 16

让我们接着看 ThreadLocalMap.set() 方法

/**
 * Set the value associated with key.
 *
 * @param key the thread local object
 * @param value the value to be set
 */
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

由以上代码可知添加流程是这样的:由 ThreadLocal 类型的 key 计算得到目的下标 i,经由下标 i从数组中取 Entry ,如果取不到,则构建 Entry 并放入数组中,否则通过取到的 Entry 拿到对应的 keyk,当 k 与 传入的参数 key 相等时,将传入的 value 覆盖 Entry.value;当 knull 时,则构建 Entry 抹除失效的 Entry 并插入;

ThreadLocalMap.Entry 的 key 为什么设计成弱引用类型的?

由上面分析得知,当 Entrykeynull 时,会被标记为失效 Entry,在下一次插入时被覆盖清除,由此避免了因为 key 的强引用导致的 value 无法释放的问题;那么 ThreadLocal 在创建的地方不会涉及到强引用而导致内存泄漏吗?会,当然会,所以我们一搬搭配其 remove 方法使用,在使用完成后移除以避免内存泄漏;

/**
 * Removes the current thread's value for this thread-local
 * variable.  If this thread-local variable is subsequently
 * {@linkplain #get read} by the current thread, its value will be
 * reinitialized by invoking its {@link #initialValue} method,
 * unless its value is {@linkplain #set set} by the current thread
 * in the interim.  This may result in multiple invocations of the
 * {@code initialValue} method in the current thread.
 *
 * @since 1.5
 */
 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

ThreadLocal 父子线程的访问

ThreadLocal 一定不能再其他线程里访问吗?不一定,InheritableThreadLocal 是个例外,子线程可以共享父线程的 ThreadLocal,让我们来看注释

/**
 * This class extends <tt>ThreadLocal</tt> to provide inheritance of values
 * from parent thread to child thread: when a child thread is created, the
 * child receives initial values for all inheritable thread-local variables
 * for which the parent has values.  Normally the child's values will be
 * identical to the parent's; however, the child's value can be made an
 * arbitrary function of the parent's by overriding the <tt>childValue</tt>
 * method in this class.
 *
 * <p>Inheritable thread-local variables are used in preference to
 * ordinary thread-local variables when the per-thread-attribute being
 * maintained in the variable (e.g., User ID, Transaction ID) must be
 * automatically transmitted to any child threads that are created.
 *
 * @author  Josh Bloch and Doug Lea
 * @see     ThreadLocal
 * @since   1.2
 */

深究一下,具体逻辑是 InheritableThreadLocal 在创建时为线程的 inheritableThreadLocals 变量赋值,而线程的 init2(Thread parent) 方法在执行时,会判断父线程的 inheritableThreadLocals 是否为空,如果不为空,则会复制父线程的 inheritableThreadLocals 赋值给自己的 inheritableThreadLocals 供自己使用;

为什么死循环可以不占用CPU而不导致阻塞卡死?

因为使用了系统内核的 epoll 机制,使得线程处于阻塞状态但不占用CPU;在 MessageQueue 中体现为 next() 方法中的循环调用 nativePollOnce() 这个 native 方法;

了解 I/O 机制

缓存I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

缓存 I/O 的缺点:

数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

I/O 模式

基于以上 两个阶段衍生出五中网络模式方案:

  • 阻塞 I/O (blocking I/O)
  • 非阻塞 I/O (nonblocking I/O)
  • I/O 多路复用 (I/O multiplexing)
  • 信号驱动 I/O (signal driven I/O) -- 不常用
  • 异步 I/O (asynchronous I/O)
阻塞 I/O (blocking I/O)

image.png

如图,在两个阶段都 block 了

非阻塞 I/O (nonblocking I/O)

image.png

如图,在第一阶段需要不断地主动询问数据好了没,第二阶段 block

I/O 多路复用 (I/O multiplexing)

image.png

I/O 多路复用 在于 select 操作可以监听多个 socket 只要有一个 socket 的数据 ready 即可返,回, selectrecvfrom 均为系统方法,是阻塞操作;某种程度上可能比阻塞I/O更耗时,但是在并发量高的情况下,能同时处理更多的 socket 的优势就显现了出来;

异步 I/O (asynchronous I/O)

image.png

进程在发出 aio_read 请求后,内核立即返回,进程可以去做自己的事儿,不对进程进行 block;同时内核在数据准备完成时,拷贝到用户空间,完成后发送一个 singal 给进程通知进程读取操作完成了;

epoll

epollselectpoll 的增强版,使用一个文件描述符管理多个文件描述符,事先通过 epoll_ctl 注册文件描述符,一旦基于某个文件描述符就绪时,内核采用类似 callBack 回调机制迅速激活这个文件描述符,使得进程在调用 epoll_wait 时得到通知;

参考