Handler這些巧妙設計你知道多少?

Handler問題思考?

線程間通信機制是什麼?怎麼完成線程間通信的?

由什麼組成?

調度策略是什麼樣的?消息循環機制,消息分發機制?

爲什麼這麼設計?

1. 線程間通信機制Handler

  1. Handler 是典型的生產者-消費者模型。是線程之間進行通信的媒介。

  2. 線程之間內存是不共享的,那麼怎麼完成的線程間通信?

    設計通信機制的思路

    進行消息封裝 ---- 拿到目標線程句柄 --- 發送消息 --- 目標線程進行消息調度(優先級、異步、阻塞?)

實際上Handler也是按照這個思路進行,並有更多優秀的思考。比如消息池機制、消息屏障、IdleHandler、epoll等

首先看Handler的創建過程:

public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            // 是匿名類 或 成員類 或 局部類    ,但字段修飾符不是靜態的時候, 有可能引起內存泄露。
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
		// 獲取Looper    如果當前線程沒有Looper 拋出異常。  
        //  1. 因爲沒有Looper 就執行不了下面mQueue的初始化
        //  2. 沒有Looper將無法完成消息調度
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
    	//這句。。   mQueue 是跨線程的句柄、
    	// 如果不能初始化 也就不能傳遞消息
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

消息是怎麼封裝的?

public final class Message implements Parcelable {
    // 執行等待時長, 在MessageQueue中,以此爲依據排序
    public long when;
	// 發送的數據
    /*package*/ Bundle data;
	// 目標線程的 Handler對象,用於發送 和 處理消息  跨線程
    /*package*/ Handler target;
    // Message Pool 消息池,回收和複用Message對象  MAX_POOL_SIZE = 50; 
    private static Message sPool;
}

消息怎麼調度的? Looper.loop()

/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
    // sThreadLocal.get();
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 從本地線程的ThreadLocal 中找到Looper對象,  並從其中拿到 MessageQueue
    // 那這個Looper對象是什麼時候放進去的呢? 沒錯就是 Looper.prepare()  
    // sThreadLocal.set(new Looper(quitAllowed));  
    //  這也就是子線程不調用 prepare 就不能創建Handler、也不能運行Looper.loop()
    final MessageQueue queue = me.mQueue;


    for (;;) {
        // 如果沒有消息這裏不會返回
        Message msg = queue.next(); // might block
        // 返回null 這裏會退出循環
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        
        logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
        
        // 非空的消息 執行分發操作
        try {
                msg.target.dispatchMessage(msg);
            } finally {
               
            }
        logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        
        // 放回消息池
        msg.recycleUnchecked();
    }
}

// Looper 並沒有實際的調度,那麼問題就在於這個next什麼時候返回了。
// 所以這個調度測量也就是next的返回策略。
// 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.
    // 消息不爲空  但是Target是 null , 這說明什麼問題? 
    //  可以看下這個函數實現    private int postSyncBarrier(long when) {}
    // 在Post這個名字SyncBarrier的消息的時候沒有賦值msg.target
    // 這就是我們所說的消息屏障。
    for (;;) {
        // 這裏進入休眠,下次什麼時候喚醒呢?
        // 1. 
        // boolean enqueueMessage(Message msg, long when) {}
        // 這個函數裏面nativeWake(mPtr)  , 添加消息的時候會對messageQueue排序順便會檢查要不要喚醒
        // 這些條件(p == null || when == 0 || when < p.when) 滿足,並且當前是阻塞狀態,就會調用nativeWake(mPtr) 
        // 這肯定不是唯一的喚醒方式,否則如果一直沒有消息進來不是要死在這裏了。。
        
        // 2. 
        // Looper.cpp-->Looper::pollOnce()
        // 這裏面使用了epoll機制, 超時返回。
        nativePollOnce(ptr, nextPollTimeoutMillis);
        // 消息屏障
        if (msg != null && msg.target == null) {
            // Stalled by a barrier.  Find the next asynchronous message in the queue.
            do {
                prevMsg = msg;
                msg = msg.next;
                // 消息屏障存在的情況  就會搜索msgQueue中的isAsynchronous 異步消息,拿出來執行  。 直到隊列末尾
            } while (msg != null && !msg.isAsynchronous());
        }
        
        // 到這基本流程就結束了 
        // 但是還有一個IdleHandler機制

}

 

2. 三種消息類型

  1. 同步消息
  2. 異步消息
  3. 消息屏障

同步消息和異步消息

Handler 發送的消息只有Message一種,怎麼就出現了三種消息類型?

前面分析Handler創建的時候,可以看到最後一個參數是async,這裏就註定這個Handler是發送出去同步消息還是異步消息。

public Handler(@Nullable Callback callback, boolean async) {
	/// async
    mAsynchronous = async;
}

// post 方法最終調用到的MessageQueue的入隊方法。在這裏設置了Message中一個屬性  msg.setAsynchronous(true);
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
       // 這裏必定會設置Target
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
		// 如果是異步的Handler,發出去的消息Message 就會帶有 異步標記
    	// 這個標記的 就區分出了兩種消息, 同步消息和異步消息。 
        // 什麼區別呢?前面調度分析的時候,同步消息會被消息屏障阻塞, 而異步不會。
        if (mAsynchronous) {
            msg.setAsynchronous(true);
      
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

消息屏障

好像只有這兩種消息類型,消息屏障又是什麼? 準確的說叫同步消息屏障,也就是說只會阻塞同步消息,通常是和異步消息一起使用。

消息屏障有什麼特殊嗎?


@TestApi
    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++;
            
            
            /// ******注意看這個消息的創建過程
            // 從消息池中拿到了這個消息對象之後,設置了when、 arg1 就加入到消息隊列的單鏈表中了。
            // !!! 沒設置 target 。  可以對比上面的 代碼  msg.target = this;
            // 什麼是消息屏障?  Looper調度的時候,如果發現沒有target的消息,那麼就是消息屏障了
            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;
            }
            // 返回了一個 token  。  實際上就是一個計數
            // 刪除的時候就據此token  
            return token;
        }
    }

刪除消息屏障的過程。

    @TestApi
    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;
            // 找到 消息target爲空  arg1 是 token的那個Message對象
            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) {
                // 有消息需要喚醒 nativePollOnce的阻塞   epoll_wait() 實現
                nativeWake(mPtr);
            }
        }
    }

3. 消息屏障的使用場景

在向Choreography訂閱了VSync信號接收消息之後,VSync回調會觸發重繪操作。

顯然,這次界面刷新的優先級最高,因此使用了消息屏障以阻塞消息隊列中不重要的同步消息。並創建一個異步刷新,加入到主線程Loop中的MessageQueue隊列中。

ViewRootImpl.java

// Post消息屏障到消息隊列
@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 設置消息屏障
        // 這裏會返回Token , 刪除的時候會以此爲依據進行刪除
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

// 刪除 消息屏障
void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //  mTraversalBarrier 拿Token 去MessageQueue中刪除屏障
            // 刪除消息屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

4. Handler 延時消息的實現

// 從開始加入消息開始看起
// Handler 中兩種消息延遲的方法
// 1. 定時執行
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    ...
    return enqueueMessage(queue, msg, uptimeMillis);
}
// 2. 延遲執行
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}


// uptimeMillis 消息延遲執行的時間點
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;
        }
    	// 在上節三種消息類型 中提到的queueMessage
        // queue.enqueueMessage 方法(msg, uptimeMillis);
        // 這裏最終調用的是MessageQueue的 enqueueMessage 方法
        return enqueueMessage(queue, msg, uptimeMillis);
}

// 至此並沒有找到爲什麼Handler 會延遲處理消息,只是在Message的when中增加了一個時間戳。
// 那麼就只剩消息隊列,和消息調度環節了。
boolean enqueueMessage(Message msg, long when) {

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
    
    		//插入隊列  兩種情況 
            // 1. 消息隊列爲空\待插入時間爲0\第一條執行的when時間比待插入的時間更長
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //將msg 插入到頭部
                msg.next = p;
                mMessages = msg;
                // 插入到頭部要喚醒
                needWake = mBlocked;
            // 2. 其他情況
            } 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;
                // 找到比當msg 執行時間更晚的消息
                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;
    }

5. 消息隊列設計優勢

Handler 是Android的核心基礎之一, 能夠適應複雜的代碼環境,被廣泛的應用在Android系統代碼中。

Handler設計策略是典型的生產消費者模型,調度策略巧妙的解決了生產消費模型中的執行順序和優先級的問題。

被廣泛使用另一原因是高效,使用epoll機制,完成跨線程和超時喚醒,使Handler在消耗極少CPU資源的情況下準確的完成調度工作。

同時Handler設計中包含了很多的優化策略,比如消息池機制是比較常用的內存優化策略。再比如內存泄漏的檢測,並且給出了友好的提示。同時Handler 提供了比較完善的監測機制,可以參考Looper 的代碼進行詳細的分析。

思考問題:

消息喚醒機制 eventFd

Handler中的監控機制設計

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章