Handler相關知識

1、概念

Handler可以通過與線程相關的MessageQueue發送和處理Message和Runnable對象。通過這種機制,Android可以在相同進程中不同線程間通信。

2、Handler類

在源碼中,Handler類中持有對Looper與MessageQueue的引用,如下:
Handler對Looper及MessageQueue的引用
這兩個變量在構造方法執行時會賦值,Handler提供了多個構造方法,如下
Handler構造函數
通過傳入不同的Looper及Callback可以構造不同的Handler對象。比較常用的是不傳任何參數的第一個構造方法。這些方法最終都會調用如上圖中的第四個構造方法,其中主要邏輯如下:

public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

構造方法中,首先會獲取當前線程的Looper對象,如果爲空的話則拋出異常。下面是Looper中myLooper的方法:
Looper.myLooper()
從代碼中可以看出,Looper獲取的是與當前線程關聯的Looper對象。而這個對象的設置是在prepare()方法中, 如下
Looper.prepare()
所以在一個新的線程中使用Handler時,如果不調用Looper.prepare()方法會拋出異常。而在主線程中直接new Handler()沒有報錯,是因爲在主線程啓動時調用了相關的代碼,如下

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");
		
		// 創建主線程的looper
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // 開啓循環,這裏面是無限循環,基本不可能走到後的拋異常。
        // 因爲一個線程執行完成了就會退出,所以需要阻塞住線程。
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

有了Handler對象之後,我們就可以使用Handler來發送消息了。Handler類提供的多種發送消息的方法,如下:
sendMessage
這些方法最終都會調用到sendMessageAtTime,其代碼如下:

public boolean sendMessageAtTime(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;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

從代碼中可以看到主要的邏輯是調用了enqueueMessage方法,即將消息加入隊列。enqueueMessage的方法如下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	// 指定msg的target對象,即爲當前Handler
    msg.target = this;
    // 設置消息是否同步,一般的消息都是同步的。源碼中有一些用到了異步的消息。
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 此處的queue是MessageQueue對象,通過Looper對象獲取。 
    return queue.enqueueMessage(msg, uptimeMillis);
}

從代碼中可以看出,Handler的sendMessage只是將message加入到了MessageQueue的隊列中。 至此Handler完成了消息的發送。

3、MessageQueue

上節中Handler發送消息後實際上是調用了MessageQueue的enqueueMessage方法,代碼如下:

boolean enqueueMessage(Message msg, long when) {
	// message的target類型爲Handler。Handler中調用enqueueMessage方法時已經指定了該message的target對象了。
    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;	// mMessages相當於鏈表的頭節點。Message相當於鏈表中的節點,持有下一個節點的引用next。
        boolean needWake;
        // 如果當前節點爲空 或 msg的觸發時間爲0 或 觸發的時間早於頭節點,則將該msg插入到頭節點,並賦值爲新的頭節點
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;	// 將msg設置爲新的頭節點
            needWake = mBlocked;	// 根據block狀態來確定是否需要喚醒
        } 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.
            // 如果消息是異步的 或 target爲空的情況需要wake
            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;
                }
            }
            // 此時p是null,相當於將p插入到鏈表尾部。
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
        	// 調用native方法喚醒(因爲looper對象在for循環中調用next方法取消息,而取消息的方法會阻塞線程)
            nativeWake(mPtr);
        }
    }
    return true;
}

該方法相當於將消息放到了消息鏈表的尾部,同時根據消息是否是同步還是異步來喚醒線程。
至此,已經將消息放到鏈表這樣的數據結構了,接下來肯定是要取出這些消息,然後進行相應的操作的。 消息的取出是通過Looper調用了MessageQueue的next方法實現的,next方法的代碼如下:

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

		// 根據nextPollTimeoutMillis的值不同,這裏可能會發生阻塞
		// -1時會無限阻塞,直到被喚醒。0表示不阻塞,直接返回。其他正數,則會阻塞相應的時間,然後返回。
		// 所以剛進來沒有消息時,會一直阻塞在這裏。
		// 在handler發送消息後,MessageQueue的enqueueMessage方法中就會喚醒這裏的線程,跳過這裏,從而取出消息處理。
        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;
            // target爲空的情況一般在源碼中。
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                // 當鏈表頭message的target爲空的情況下,找到第一個異步消息。故這之間的同步消息會被忽略掉。 
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
            	// 如果消息的處理時間還未到,則設置nextPollTimeoutMillis
                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; 	// 將prevMsg與msg後面的節點鏈接起來。
                    } 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;
    }
}

通過該方法,就可以取出一個消息了。拿到消息後,Looper中就會做相應處理。

3.1、MessageQueue的native層

在MessageQueue的源碼中使用如下的native代碼:

private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

在native層與之對應的類是NativeMessageQueue。通過對源碼的查看,可以得知MessageQueue的阻塞與喚醒是通過epoll機制實現的。(這裏還需要深入瞭解)

4、Looper

Looper類的loop會調用MessageQueue類的next方法取出消息,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;

    // ...... 省略 

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

   		// ...... 省略
        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        final long dispatchEnd;
        try {
        	// 最主要的是這行代碼,調用target的dispatchMessage方法。 
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        // ...... 省略

        msg.recycleUnchecked();	// 回收消息
    }
}

從代碼中看,最重要的是msg調用了target對象的dispatchMessage方法,而這個target即是Handler類型,也即是在sendMessage時賦值的。由此可以知道,消息的發送和處理均是在Handler中處理的。Handler中dispatchMessage方法如下

 /**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
	// 如果消息中攜帶有callback參數,callback類型爲Runnable
    if (msg.callback != null) {
        handleCallback(msg);	// 這個方法會直接調用message.callback.run()
    } else {
    	// 如果在構造Handler時傳入了Callback參數,則用此Callback對象處理消息
        if (mCallback != null) {
        	// 如果callback的handleMessage方法返回false,還會繼續用handler自身的方法。
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
		// 常見的構造handler時需要覆寫的方法,handler中該方法爲空實現。
        handleMessage(msg);
    }
}

到這裏,即完成了消息的分發與處理。

5、Message

Message類相當於鏈表中的一個節點,因爲其保存了對下一個節點的引用,即next變量。同時還定義了what,arg1,arg2、data,callback成員變量。獲取Message的方法除了直接new之外,系統還提供了一個obtain方法從緩存池中獲取,代碼如下:

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;	// 此處的sPool指向鏈表的頭節點
            sPool = m.next;
            m.next = null;	// 斷開與頭節點的鏈接
            m.flags = 0; // clear in-use flag
            sPoolSize--;	// 緩存池大小減1
            return m;
        }
    }
	// 如果沒有緩存則new一個Message
    return new Message();
}

而緩存池的釋放則是在message的recycleUnchecked方法中,如下

/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 */
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) {
        	// 採用頭插法,將回收到的Message放在鏈表的頭部
        	// 當前節點的next鏈接到原鏈表頭部。
            next = sPool;	
            // 將當前節點作爲新頭節點
            sPool = this;
            // 緩存池大小加1	
            sPoolSize++;
        }
    }
}

6、IdleHandler

在MessageQueue的next方法中,如果消息爲空的情況下,會調用IdleHandler對象的queueIdle方法,而Idlehandler對象可以通過MessageQueue暴露的接口添加多個。使用方法如下:

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        Log.i("test", "idleHandler queueIdle " + System.currentTimeMillis());
       // 返回true時,會保持該IdleHandler一直活躍。false則會移除該IdleHandler。
        return true;
    }
});

該方法的註釋說明了調用的場景

/**
* 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.
*/

我們可以通過此方法將一些操作在沒有消息時處理,從而避免搶佔資源優化交互體驗。

7、總結

1、構造Handler時,會通過ThreadLocal獲取Looper對象及其MessageQueue對象。Handler發送消息時會調用MessageQueue的enqueueMessage方法將消息加入到鏈表中去。同時,Looper的loop循環會調用MessageQueue的next方法取出一個消息,調用該消息的dispatchMessage方法,從而將消息分發出去。
2、其中,MessageQueue的next方法中的nativePollOnce會阻塞住線程,而enqueueMessage中會調用nativeWake喚醒線程。
3、主線程中的Looper會阻塞住主線程。由於線程的特性,線程執行完成後就會退出,故ActivityThread類中的Loop.loop方法的無限循環會阻塞住主線程,從而無法走到最後一行的拋出異常。在主線程阻塞沒有引起ANR的原因是發生阻塞時沒有事件發生,而當有事件發生時,系統會喚醒線程從而避免了ANR。即有事件時喚醒線程處理,無事件時阻塞等待。
4、在一個handler對象上removeMessage時需要注意的是,只能該handler對象的messgae。當出現message不能刪除的時候,要檢查一下該message是不是關聯到當前的handler對象。

8、參考

以下兩篇文章寫的非常好。
1、MessageQueue
2、Android中線程間通信機制Handler疑問解答

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