Android體系課--Handler—按方法進行原始碼解析

語言: CN / TW / HK

Handler系列:

Android體系課--Handler—按方法進行原始碼解析

Android體系課--Handler-Handler面試題

Handler原始碼解析

1.建構函式

java public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper;1.傳入的當前執行緒的looper物件 mQueue = looper.mQueue;2.傳入的當前執行緒的looper物件的MessageQueue mCallback = callback;3.傳入的Handler.CallBack物件,在處理的時候會判斷該物件是否存在還有返回值是否為true mAsynchronous = async; } 總結: 1.哪個執行緒執行訊息處理請求,是根據傳入的looper來確認。

2.獲取Message

java Handler.obtainMessage Message.obtain(this, what){ Message m = obtain();分析1: m.target = h; m.what = what; return m; } 分析1: public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) {sPool指向訊息池的頭節點,如果不為空進入 Message m = sPool; 使用一個臨時變數m=sPool sPool = m.next; 讓sPool指向m的next m.next = null; 打斷m到sPool的指標,這樣sPool還是指向連結串列的頭結點,只是這個節點是之前sPool的next節點 m.flags = 0; // clear in-use flag sPoolSize--訊息池連結串列大小減1 return m; 返回之前從訊息池中取出的頭結點, } } return new Message();如果訊息池沒有訊息,則建立訊息 } 總結:Handler.obtainMessage方法可以從Message的訊息池中獲取訊息,取出的是訊息池的頭結點訊息,如果沒有訊息則建立消息,這個方法可以避免不必要的訊息建立,重用訊息池的訊息減少記憶體開銷

3.傳送sendMessage

```java sendMessageDelayed(msg, 0); sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);延遲時間加上系統時間組成when MessageQueue queue = mQueue;這個mQueue是在Handler建構函式中賦值 enqueueMessage(queue, msg, uptimeMillis); msg.target = this;將當前Handler賦值給msg.target if (mAsynchronous) {如果是非同步訊息,則設定msg的非同步標誌 msg.setAsynchronous(true); } queue.enqueueMessage(msg, uptimeMillis){呼叫MessageQueue的enqueueMessage方法 if (msg.target == null) {判斷handler是否為空 throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) {判斷msg是否被使用 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為使用狀態,防止msg被重複使用
                                    msg.when = when;設定msg的延遲時間
                                    Message p = mMessages;獲取訊息池的頭節點賦值給臨時變數p
                                    boolean needWake; 是否喚醒looper的next
                                    if (p == null || when == 0 || when < p.when) { 訊息池為空或者延遲時間為0或者延遲時間小余頭節點的延遲時間,則將其插入訊息池的頭節點
                                            // New head, wake up the event queue if blocked.
                                            msg.next = p;
                                            mMessages = msg;
                                            needWake = mBlocked; mBlocked是在訊息處理next中賦值,如果有訊息正在處理則mBlocked=false,如果空閒狀態則mBlocked=true,即需要喚醒
                                    } 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();如果是空閒狀態且p.target == null和msg是非同步訊息,則需要喚醒
                                            Message prev;
                                            for (;;) {遍歷連結串列取出當前msg延遲時間小余其延遲時間的msg
                                                    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 將msg插入p的前面
                                            prev.next = msg;將msg插入prev後面,即插入prev和p的中間
                                    }

                                    // We can assume mPtr != 0 because mQuitting is false.
                                    if (needWake) {如果需要喚醒,則呼叫nativeWake喚醒next方法中的nativePoolOnce
                                            nativeWake(mPtr);
                                    }
                            }
                            return true;                                                        
                    }

``` 總結:

1.訊息插入機制:插入訊息的順序是延遲時間最小的放在訊息池的頭部
2.訊息喚醒時機:
    2.1:如果當前訊息插入的是頭節點,則判斷訊息處理是不是空閒狀態,如果是空閒則喚醒
    2.2:如果訊息出入中間節點,則首先判斷是不是空閒狀態還有`p.target == null`和`msg`是非同步訊息,這三個條件都成立都可以把needWake置為true,
         之後還要判斷當前訊息是不是最早的非同步訊息,如果不是最早的,則needWake 置為 false即不需要喚醒,如果是最早的非同步訊息,則直接喚醒訊息處理迴圈
4.訊息獲取過程:

Looper的loop方法:

```java public static void loop() { final Looper me = myLooper();獲取當前執行緒的looper物件 final MessageQueue queue = me.mQueue;獲取MessageQueue物件 for (;;) { Message msg = queue.next(); // might block獲取msg if (msg == null) {沒有訊息的時候則退出迴圈,即主執行緒退出,應用退出 // No message indicates that the message queue is quitting. return; } try { msg.target.dispatchMessage(msg);處理msg } msg.recycleUnchecked();回收msg } } 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 = -1; when = 0; target = null; callback = null; data = null;

synchronized (sPoolSync) {
    if (sPoolSize < MAX_POOL_SIZE) {
        next = sPool;
        sPool = this;
        sPoolSize++;
    }
}

} MessageQueue.java: 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) {同步屏障訊息的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) {msg不為空
            if (now < msg.when) {當前時間小余msg的延遲時間,則等待:msg.when - now
                // 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 {當前時間大於取出的msg的延遲時間
                // Got a message.
                mBlocked = false;將mBlocked空閒時間置為false
                if (prevMsg != null) {
                    prevMsg.next = msg.next;
                } else {
                    mMessages = msg.next;取出msg後,將msg的next節點置為頭節點
                }
                msg.next = null;打斷msg到msg next的連結串列連結
                if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                msg.markInUse();將msg的使用標誌置為true
                return msg;返回msg
            }
        } else {msg為空表示沒有訊息需要處理
            // No more messages.
            nextPollTimeoutMillis = -1;
        }

        // Process the quit message now that all pending messages have been handled.
        if (mQuitting) {當呼叫了退出方法則返回null給上層
            dispose();內部呼叫nativeDestroy(mPtr);
            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.
        下面這些資訊是處理對於設定了空閒訊息處理任務的流程,這個可以用來提高Ui效能,即將主執行緒空閒狀態來處理一些其他事情,充分利用資源
        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; 如果沒有任何空閒狀態事情處理後,將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;
}

}

``` 總結:

訊息取出過程:
首先判斷訊息頭是否是一個同步屏障訊息msg.traget=null,
如果是取出連結串列中第一個非同步訊息進行處理,如果不是則直接取出訊息池中第一個訊息。
如果沒有任何訊息需要處理,則判斷是否有空閒任務需要處理ideHandler,有就去處理空閒任務,沒有就將最終空閒狀態置為true
5.訊息處理:

js Looper.loop方法中: msg.target.dispatchMessage(msg);msg.target =Handler Handler.dispatchMessage(msg){ if (msg.callback != null) {如果msg在建立過程中msg.callback不為null,則直接呼叫handleCallback(msg)---> message.callback.run(); handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) {如果Handler在建立的時候傳入的Handler.CallBack不為空則呼叫CallBack的handleMessage方法 return;如果返回值為true則不會回撥Handler的handleMessage,這裡可以做一個訊息攔截的處理 } } handleMessage(msg);呼叫Handler的handleMessage } }

6.訊息迴圈處理退出:呼叫Looper的quit方法

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

synchronized (this) {
    if (mQuitting) {
        return;
    }
    mQuitting = true;將mQuitting標誌置為true

    if (safe) {
        removeAllFutureMessagesLocked();待處理訊息執行完再清理
    } else {
        removeAllMessagesLocked();直接清理,可能會有記憶體洩露風險
    }

    // We can assume mPtr != 0 because mQuitting was previously false.
    nativeWake(mPtr);喚醒訊息處理執行緒
}

}

```

7.View的繪製流程中:View繪製會走到scheduleTraversals中

```java / :ViewRootImpl.scheduleTraversals() / void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();分析1

        // 通過mHandler.post()傳送一個runnable,在run()方法中去處理繪製流程
        // 與ActivityThread的Handler訊息傳遞機制相似
        // ->>分析7
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);分析2
    }
}
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);分析3

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

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}
分析1:MessageQueue->postSyncBarrier
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();去Message的訊息池中獲取中獲取msg
        msg.markInUse();設定inuse
        msg.when = when;設定延遲時間
        msg.arg1 = token;設定token

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {這個判斷內部其實是訊息連結串列mMessages中取出延遲時間比當前msg的延遲時間更大的msg,
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // 這裡面其實是把msg插入訊息連結串列mMessages中
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;返回msg的token
    }
}

``` 總結:

postSyncBarrier的作用是去訊息池中獲取一個msg,設定了msg.arg1 = token是同步屏障訊息的token值,且msg.target = null;並將這個msg放入到訊息池mMessages中,下次處理執行緒被喚醒時會判斷訊息池的第一個msg的target是不是空並去後面取第一個非同步任務,這個非同步任務其實是一個view的繪製流程同步屏障實現了view優先繪製

```java 分析2:mChoreographer.postCallback postCallbackDelayed(callbackType, action, token, 0); postCallbackDelayedInternal(callbackType, action, token, delayMillis);{ synchronized (mLock) { final long now = SystemClock.uptimeMillis(); final long dueTime = now + delayMillis; mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

    if (dueTime <= now) {
        scheduleFrameLocked(now);//分析8
    } else {
        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);獲取一個msg
        msg.arg1 = callbackType;設定arg1引數型別
        msg.setAsynchronous(true);設定為非同步訊息
        mHandler.sendMessageAtTime(msg, dueTime);傳送訊息
    }
}
分析8:scheduleFrameLocked(now);
private void scheduleFrameLocked(long now) {
        ...
        scheduleVsyncLocked();
        ...
}
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();//這個mDisplayEventReceiver = FrameDisplayEventReceiver物件

} public void scheduleVsync() { if (mReceiverPtr == 0) { Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event " + "receiver has already been disposed."); } else { nativeScheduleVsync(mReceiverPtr);//這裡呼叫nativeScheduleVsync註冊了一個Vsync事件接收器,接收者為前面的mDisplayEventReceiver } } private final class FrameDisplayEventReceiver extends DisplayEventReceiver{ @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);//這裡傳送了一個非同步訊號,每16ms接收到一次訊號,並繪製ui }

}

``` 總結:

postCallback內部主要實現的是獲取一個msg,並設定msg為非同步訊息,最後傳送訊息給MessageQueue
註冊了vsync訊號回撥,每16ms獲取到vsync訊號,並更新ui,所以ondraw方法是在接收到vsync訊號後才呼叫的,會每16ms回撥一次ondraw方法

```java 分析3:mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); 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)) {其實是取出target==null且p.arg1=傳入的token的值,即之前插入訊息連結串列mMessages的msg 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) {這個if是將p在連結串列中去除,prev不為null說明p不在表頭
            prev.next = p.next;
            needWake = false;
        } else {為null說明p在表頭。
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;如果mMessages == null || mMessages.target != null;表頭資料不是同步屏障訊息,或者訊息池資料為空,則喚醒訊息處理執行緒
        }
        p.recycleUnchecked();回收訊息到訊息池sPool中

        // If the loop is quitting then it is already awake.
        // We can assume mPtr != 0 when mQuitting is false.
        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

`` 總結:根據token值移除訊息連結串列中的msg並根據情況喚醒訊息處理執行緒`

分析1和分析2,3可知View的繪製流程其實就是在View繪製流程啟動前,給訊息池傳送一個msg.target為空的訊息,然後給View的繪製任務的msg設定為非同步訊息,下次在Handler取訊息的過程中優先判斷訊息池的msg.target是不是空,如果是,則去訊息池中取出第一個非同步訊息執行。執行前先把同步屏障訊息移除。這就是訊息同步屏障機制

7.ThreadLocal機制:sThreadLocal.set(new Looper(quitAllowed));

```java public void set(T value) { //(1)獲取當前執行緒(呼叫者執行緒) Thread t = Thread.currentThread(); //(2)以當前執行緒作為key值,去查詢對應的執行緒變數,找到對應的map ThreadLocalMap map = getMap(t); //(3)如果map不為null,就直接新增本地變數,key為當前定義的ThreadLocal變數的this引用,值為新增的本地變數值 if (map != null) map.set(this, value); //(4)如果map為null,說明首次新增,需要首先創建出對應的map else createMap(t, value); }

ThreadLocalMap getMap(Thread t) { return t.threadLocals; //獲取執行緒自己的變數threadLocals,並繫結到當前呼叫執行緒的成員變數threadLocals上 }

void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

public T get() { //(1)獲取當前執行緒 Thread t = Thread.currentThread(); //(2)獲取當前執行緒的threadLocals變數 ThreadLocalMap map = getMap(t); //(3)如果threadLocals變數不為null,就可以在map中查詢到本地變數的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //(4)執行到此處,threadLocals為null,呼叫該更改初始化當前執行緒的threadLocals變數 return setInitialValue(); }

private T setInitialValue() { //protected T initialValue() {return null;} T value = initialValue(); //獲取當前執行緒 Thread t = Thread.currentThread(); //以當前執行緒作為key值,去查詢對應的執行緒變數,找到對應的map ThreadLocalMap map = getMap(t); //如果map不為null,就直接新增本地變數,key為當前執行緒,值為新增的本地變數值 if (map != null) map.set(this, value); //如果map為null,說明首次新增,需要首先創建出對應的map else createMap(t, value); return value; } ``` 總結:

ThreadLocal其實是一種用空間換時間的機制: ThreadLocal內部的其實都是針對當前執行緒的ThreadLocalMap做的操作,一個執行緒只有一個Thread,一個Thread只有一個ThreadLocalMap,所以其內部儲存的資料都是執行緒隔離的。 而且在 static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>(); 可以看到這個sThreadLocal在所有執行緒中只有一個,所以獲取value的時候key``都是同一個,只是這個ThreadLocalMap是在每個執行緒中有一份,所以獲取的值是不同執行緒中的valuesThreadLocal同一個物件中,相當於一個統一入口,內部操作獲取value的和設定value都是針對當前執行緒來操作的,所以在不用執行緒中獲取的是當前執行緒的值