Android Handler 訊息機制原理解析

來源:互聯網
上載者:User

標籤:簡單   重要   command   too   內部使用   迴圈   列表   bug   identity   

前言

做過 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 訊息機制原理解析

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.