天天看點

Android 關于Handler機制的十個問題

1.Handler是什麼?

  Handler機制主要為線程間通信而生,是Android中定義的一套消息傳遞機制。

  主要是為了解決子線程執行完耗時操作後,怎麼回調到主(UI)線程的問題。

2.Handler機制的主要成員有哪些?

  Handler、Looper、Message和MessageQueue

  Handler:負責發送Message到MessageQueue,同時負責接收消息

  Looper:負責從MessageQueue讀取消息,并通過Message的target,調用handler的消息分發處理

  Message:消息載體

  MessageQueue:消息隊列

3.handler消息機制的主要流程是什麼?

  消息發送過程:Handler在子線程發送Message,由于Handler初始化的時候有持有目前線程的Looper和Looper中的                      MessageQueue,而這個Handler在主線程初始化,也就是該Handler持有主線程Looper和MessageQueue,發送普通消息會給    Message的target指派Handler本身,發送消息會最終會調用MessageQueue的enqueueMessage方法。

  消息插入過程:根據系統相對時間+延遲時間 按照順序 插入到單連結清單MessageQueue中,如果在表頭,會判斷是否有阻塞,有    阻  塞會調用native層的nativeWake方法喚醒線程,如果在表中間或表尾會判斷該消息是否是異步消息,且阻塞的話,也會調用    native層的nativeWake方法喚醒線程。

  消息取出過程:Looper循環loop方法,會調用MessageQueue的next方法,這個方法在源碼上面顯示有可能會阻塞,                    MessageQueue next 也有一個死循環操作,如果上一個取出的消息有阻塞時間,會先調用native層的nativePollOnce方法進行      阻塞,然後會判斷是否是屏障Message,如果是屏障消息先過濾掉普通消息,先擷取異步消息。如果Message消息的when大于    目前系統相對時間則會設定需要阻塞的時間,如果Message消息的when小于或者等于目前系統相對時間,會取出這個消息。如    果有阻塞的情況,還會調用IdleHandler的回調。通過MessageQueue取出Messgae後,會通過Message的target(target就是發    送該  Message的Handler)調用 Handler的dispatchMessage方法處理消息分發。

  消息分發過程:如果Message callback不為空,代表是通過post的方式發送消息,調用Runnable的 run方法;如果Handler中的    mCallback不為空,調用mCallbcak的 handleMessage;否則會調用handleMessage回調。

4.延時消息的實作 是在插入MessageQueue的時候,還是Looper取的時候?

  取消息的時候實作的!

  插入的時候會根據相對時間+延遲時間作為message的屬性,并根據該屬性進行排序。

  真正的延時是在Looper取消息的時候進行判斷是否為延時消息,然後調用native層的nativePollOnce方法進行阻塞。

5.發送消息用send 和 post 有什麼差別?

  send方式和post方式本質沒有差別,都是發送Message,隻不過post方式會把Runnable對象指派給Message的callback,在      最後消息分發的時候會回調Runnable的run方法。

6.為什麼建議用obtain方法建立Message?

  Message 本身包含兩個Message對象,一個是sPool,一個是next,但通過看源碼可知道sPool是一個static對象,是所有對象共    有,Message sPool就是一個單連結清單結構,Message就是單連結清單中的一個節點。

  使用obtain方法,取的是Message 的 sPool ,改變sPool指向sPool的next,取出sPool本身,并清空該Message的flags和next。    這樣的好處是是可避免重複建立多個執行個體對象,可以取消息池子之前已經存在的消息。

7.子線程能直接建立Handler并使用嗎?

(1)直接建立會報錯!

    在建立Handler方法中,Handler會持有該線程的Looper 和 Looper中的MessageQueue,Handler的使用必須是結合                  Looper,而子線程中并沒有綁定Looper,是以會報錯。

    因為Handler的構造方法中可以傳遞Looper:handler對象所綁定的線程其實并不取決于該handler對象由哪個線程建構,而        是取決于該handler對象所綁定的Looper屬于哪個線程。

    handleMessage 方法執行的線程一定是在建立Handler的線程嗎?答案是否定的,是在綁定Looper所屬于的那個線程。

(2)想要建立需要幹嘛

    1. 在建立Handler前首先調用Looper.prepare()建立Looper

    2. 然後調用Looper.loop()開啟循環(隻要保證發消息之前調用過該方法即可,否則Looper不運轉,

    注意:不必糾結這一步是在new Handler前 還是在 new Handler後)

    兩步缺一不可!   

8.Looper是如何確定一個線程隻能建立一個的?MessageQueue呢?

  1. 利用 ThreadLocal特性。(1)判斷ThreadLocal裡面是否為空(2)把Looper放到ThreadLocal中,

  2. 利用私有構造方法 隻能在類裡面被通路,無法被類外通路。

  3. ThreadLocal 的詳細解釋:https://blog.csdn.net/wsq_tomato/article/details/82390262

public static void prepare() {
        prepare(true);
    }
 
    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));
    }
 
  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
           

9.HandlerThread和普通線程有何差別?

  其實就是一個自己封裝好Looper的線程,源碼如下

  構造方法:

public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
           

  run方法:

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
           

10.Handler同步屏障

(1)Message消息主要分為三種:同步消息、屏障消息、異步消息

(2)同步屏障就是通過屏障消息來屏蔽所有同步消息,處理後續異步消息

核心代碼:

  首先是 MessageQueue 取消息的代碼:

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

最核心的是的判斷是這段代碼:

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

屏障消息主要是起一個屏蔽作用,是以不需要有handler 處理消息分發,是以target為null。

如果有屏障消息,且消息不是異步的則繼續取下一個,直到遇到異步消息。

異步消息如何設定?:

  (1) 可以直接調用Message的setAsynchronous方法

public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }
           

  (2) Handler的構造方法也可以設定 mAsynchronous,但是是hide标簽修飾的方法,我們可以通過反射去調用,也是可以的。

同步屏障的應用?:

  比如:螢幕重新整理機制

  Android 每隔16.6ms會重新整理一次螢幕,每個Activity對應一個 DecorView 根布局View樹。

  初始化過程中DecorView會被添加到viewRootImp(根視圖);

  根視圖setView的過程:

viewRootImp.setView() —> 
    viewRootImp.requestLayout() —> 
    viewRootImp.scheduleTraversals() —> 
    viewRootImp.doTraversal() —>                 
    viewRootImp.performTraversals()—>
           

主要看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();
        }
    }
           

其中“ mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();  ”這行代碼就是設定同步屏障。

然後咱們對這行代碼進行跟蹤:

mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
           
@TestApi
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }
 
     @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);
    }
    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);
            }
        }
    }
           

到最後發送了一個異步消息。

因為 同步屏障的作用,是以 異步消息的執行優先于同步消息,保證了螢幕重新整理的及時性和優先性。

完畢,如果哪些地方寫錯了,請各位指出,深海謝過各位同行。

繼續閱讀