1. 程式人生 > >Android Handler 消息機制原理解析

Android Handler 消息機制原理解析

當前 its leak 示例 異步消息 了解 modifier supported 異步

前言

做過 Android 開發的同學都知道,不能在非主線程修改 UI 控件,因為 Android 規定只能在主線程中訪問 UI ,如果在子線程中訪問 UI ,那麽程序就會拋出異常

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy .

並且,Android 也不建議在 UI 線程既主線程中做一些耗時操作,否則會導致程序 ANR 。如果我們需要做一些耗時的操作並且操作結束後要修改 UI ,那麽就需要用到 Android 提供的 Handler 切換到主線程來訪問 UI 。因此,系統之所以提供 Handler,主要原因就是為了解決在子線程中無法訪問 UI 的問題。

概述

要理解 Handler 消息機制原理 還需要了解幾個概念:

  1. UI 線程

    主線程 ActivityThread

  2. Message

    Handler 發送和處理的消息,由 MessageQueue 管理。

  3. MessageQueue

    消息隊列,用來存放通過 Handler 發送的消息,按照先進先出執行,內部使用的是單鏈表的結構。

  4. Handler

    負責發送消息和處理消息。

  5. Looper

    負責消息循環,循環取出 MessageQueue 裏面的 Message,並交給相應的 Handler 進行處理。

在應用啟動時,會開啟一個 UI 線程,並且啟動消息循環,應用不停地從該消息列表中取出、處理消息達到程序運行的效果。

Looper 負責的就是創建一個 MessageQueue,然後進入一個無限循環體不斷從該 MessageQueue 中讀取消息,而消息的創建者就是一個或多個 Handler 。
流程圖如下:

技術分享圖片

下面結合源碼來具體分析

Looper

Looper 比較重要的兩個方法是 prepare( ) 和 loop( )

先看下構造方法

final MessageQueue mQueue;

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

Looper 在創建時會新建一個 MessageQueue

通過 prepare 方法可以為 Handler 創建一個 Lopper,源碼如下:

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 private static Looper sMainLooper;  // guarded by Looper.class

 public static void prepare() {
        prepare(true);
 }

 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            //一個線程只能有一個looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
 }

 public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

可以看到這裏創建的 Looper 對象使用 ThreadLocal 保存,這裏簡單介紹下 ThreadLocal。

ThreadLocal 是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以後,只有在指定線程中可以獲取到存儲的數據,對於其他線程來說則無法獲取到數據,這樣就保證了一個線程對應了一個 Looper,從源碼中也可以看出一個線程也只能有一個 Looper,否則就會拋出異常。

prepareMainLooper() 方法是 系統在 ActivityThread 中調用的。

ActivityThread.java

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

       //...省略代碼

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

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

由此可以看出,系統在創建時,會自動創建 Looper 來處理消息,所以我們一般在主線程中使用 Handler 時,是不需要手動調用 Looper.prepare() 的。這樣 Handler 就默認和主線程的 Looper 綁定。
當 Handler 綁定的 Looper 是主線程的 Looper 時,則該 Handler 可以在其 handleMessage 中更新UI,否則更新 UI 則會拋出異常。
在開發中,我們可能在多個地方使用 Handler,所以又可以得出一個結論:一個 Looper 可以和多個 Handler 綁定,那麽 Looper 是怎麽區分 Message 由哪個 Handler 處理呢?
繼續看源碼 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();

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

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

            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();// 回收消息 
        }
    }

代碼比較多,我們撿重點看。
2~6 行 獲取當前 Looper 如果沒有則拋異常,有則獲取消息隊列 MessageQueue
所以如果我們在子線程中使用 Handler 則必須手動調用 Looper.prepare() 和 Looper.loop()
系統在代碼中提供了示例代碼

class LooperThread extends Thread {
            public Handler mHandler;

            public void run() {
                Looper.prepare();

                mHandler = new Handler() {
                    public void handleMessage(Message msg) {
                        // process incoming messages here
                    }
                };

                Looper.loop();
            }
        }

獲取到 MessageQueue 後開啟消息循環,不斷從 MessageQueue 中取消息,無則阻塞,等待消息。有則調用 msg.target.dispatchMessage(msg) 處理消息。

msg.target 就是 Message 所屬的 Handler,這個會再後面具體介紹 Handler 中會說明

所以上面的問題就可以回答了,Looper 不需要考慮怎麽區分 Message 由哪個 Handler 處理,只負責開啟消息循環接收消息並處理消息即可。處理完消息後會調用 msg.recycleUnchecked() 來回收消息。

那麽開啟消息循環後,可以停止嗎?
答案是肯定的,Looper 提供了 quit() 和 quitSafely() 來退出。

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

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

可以看到實際上調用的是 MessageQueue 中的退出方法,具體會在 MessageQueue 中介紹。
調用 quit() 會直接退出 Looper,而 quitSafely() 只是設定一個退出標記,然後把消息隊列中的已有消息處理完畢後才安全地退出。在 Loooper 退出後,通過 Handler 發送消息會失敗。如果在子線程中手動創建了 Looper ,則應在處理完操作後退出 Looper 終止消息循環。

到此 Looper 的源碼分析就完了,我們來總結下 Looper 所做的工作:

  1. 被創建時與線程綁定,保證一個線程只會有一個 Looper 實例 ,並且一個 Looper 實例只有一個 MessageQueue
  2. 創建後,調用 loop( ) 開啟消息循環,不斷從 MessageQueue 中取 Message ,然後交給 Message 所屬的 Handler 去處理,也就是 msg.target 屬性。
  3. 處理完消息後,調用 msg.recycleUnchecked 來回收消息

Message 和 MessageQueue

Message 是線程通信中傳遞的消息,它有幾個關鍵點

  1. 使用 what 來區分消息
  2. 使用 arg1、arg2、obj、data 來傳遞數據
  3. 參數 target,它決定了 Message 所關聯的 Handler,這個在後面看 Handler 源碼時會一目了然。

MessageQueue

MessageQueue 負責管理消息隊列,通過一個單鏈表的數據結構來維護。
源碼中有三個主要方法:

  1. enqueueMessage 方法往消息列表中插入一條數據,
  2. next 方法從消息隊列中取出一條消息並將其從消息隊列中移除
  3. quit 方法退出消息列表,通過參數 safe 決定是否直接退出

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

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

               //...省略代碼
        }
    }

可以發現 next 方法是一個無限循環的方法,如果消息隊列中沒有消息,那麽 next 方法會一直阻塞在這裏。當有新消息到來時,next 方法會從中獲取消息出來返回給 Looper 去處理,並將其從消息列表中移除。

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

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        if (p.when > now) {
            removeAllMessagesLocked(); // 移除尚未處理的消息
        } else { // 正在處理的消息不做處理
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}

從 上述代碼中可以看出,當 safe 為 true 時,只移除尚未觸發的所有消息,對於正在處理的消息不做處理,當 safe 為 false 時,移除所有消息。

Handler

Handler 是我們使用最多的類,主要用來發送消息和處理消息。

先來看構造方法

public Handler(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 實例,如果不存在則拋出異常
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can‘t create handler inside thread that has not called Looper.prepare()");
        }
        //關聯 Looper 中的 MessageQueue 消息隊列
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}

構造方法主要有三個參數

  1. looper 不傳值則調用 Looper.myLooper( ) 獲取當前線程的 Looper 實例;傳值則使用,一般是在子線程中使用 Handler 時才傳參數。
  2. callback Handler 處理消息時的回調
  3. async 是否是異步消息。這裏和 Android 中的 Barrier 概念有關,當 View 在繪制和布局時會向 Looper 中添加了 Barrier(監控器),這樣後續的消息隊列中的同步的消息將不會被執行,以免會影響到 UI繪制,但是只有異步消息才能被執行。所謂的異步消息也只是體現在這,async 為 true 時,消息還可以繼續被執行,不會被推遲運行。

從源碼中可看出,因為 UI 線程在啟動時會自動創建 Looper 實例,所以一般我們在 UI 線程中使用 Handler 時不需要傳遞 Looper 對象。而在子線程中則必須手動調用 Looper.prepare 和 Looper.loop 方法,並傳遞給 Handler ,否則無法使用,這一點肯定有不少童鞋都遇到過。
在拿到 Looper 對象後,Handler 會獲取 Looper 中的 MessageQueue 消息隊列,這樣就和 MessageQueue 關聯上了。

關聯上 MessageQueue ,接下來那我們就看下 Handler 是如何發送消息的。

Handler 發送消息方法很多,實際上最後都是調用的 enqueueMessage 方法,看圖說話

技術分享圖片

主要看 enqueueMessage 方法

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

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到在發送消息時給 Message 設置了 target = this 也就是當前的 Handler 對象,並調用了 MessageQueue 的 enqueueMessage 方法,這樣就把消息存在消息隊列,然後由 Looper 處理了。

童鞋們應該記得之前在講 Looper 時,說到 Looper 開啟消息循環後,會不斷從 MessageQueue 中取出Message,並調用 msg.target.dispatchMessage(msg) 來處理消息。

接下來,就來看看 Handler 是如何接收消息的也就是 dispatchMessage 方法

    public interface Callback {
        public boolean handleMessage(Message msg);
    }

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

    }

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

可以看出 Handler 接收消息,只是調用一個空方法 handleMessage 是不是有些眼熟呢,看下我們寫過很多次的 Handler 代碼

private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case value:

                    break;

                default:
                    break;
            }
        }

    };

沒錯這就是我們自己創建 Handler 時重寫的方法,由我們來處理消息,然後根據 msg.what 標識進行消息處理。

總結

應用啟動時會啟動 UI 線程也就是主線程 ActivityThread,在 ActivityThread 的 main 方法中會調用 Looper.prepareMainLooper( ) 和 Looper.loop ( ) 啟動 Looper 。
Looper 啟動時會創建一個 MessageQueue 實例,並且只有一個實例,然後不斷從 MessageQueue 中獲取消息,無則阻塞等待消息,有則調用 msg.target.dispatchMessage(msg) 處理消息。
我們在使用 Handler 時 需要先創建 Handler 實例,Handler 在創建時會獲取當前線程關聯的 Looper 實例 ,和 Looper 中的消息隊列 MessageQueue。然後在發送消息時會自動給 Message 設置 target 為 Handler 本身,並把消息放入 MessageQueue 中,由 Looper 處理。Handler 在創建時會重寫的
handleMessage 方法中處理消息。
如果要在子線程中使用 Handler 就需要新建 Looper 實例,傳給 Handler 即可。

再看下流程圖
技術分享圖片

最後

因篇幅較長,童鞋們的 Handler 肯定用的爐火純青了,所以最後就不寫例子了。
本人寫博客不久,寫的不好的地方,望童鞋們海涵。

Android Handler 消息機制原理解析