書海拾貝|開發藝術探索之 android 的消息機制

提到消息機制讀者應該都不陌生……從開發角度來說, Handler 是 Android 消息機制的上層接口,這使得在開發過程中只需要和 Handler 交互即可。……通過它可以輕松將一個任務切換到 Handler 所在的線程中去執行。

正如開篇詞所說,“主線程中不能進行網絡通信等耗時操作,而子線程中不能進行 UI 更新”,是我在 android 開發入門遇到的第一個知識點(keng),但當時只是單純記憶,本篇將順著開發藝術探索的講述,梳理 android 的消息機制有關知識。

開篇知識小點

Handler 是 Android 消息機制的上層接口,使用場景通常是更新 UI。
Android 消息機制主要指 Handler 的運行機制,Handler 的運行需要底層的 MessageQueue 和 Looper 的支持。

  • MessageQueue:消息隊列,內部存儲一組消息,以隊列形式對外提供插入和刪除的工作,但其內部實現并非隊列,而是單鏈表的數據結構實現的,是一個消息的 存儲單元,不能主動處理消息。
  • Looper:消息循環,以無限循環的形式查找是否有新消息,有的話就處理,否則等待。
  • ThreadLocal:Looper中的一個特殊概念,作用是可以在不同線程中互不干擾地存儲數據。Handler 創建的時候需要采用當前進程的 Looper 來構造消息循環系統,此時通過 ThreadLocal 可以輕松獲取每個線程的 Looper。

注意:線程是默認沒有 Looper 的,如果需要使用 Handler 就必須為線程創建 Looper。主線程,即UI線程,是 ActivityThread ,ActivityThread 被創建時就會初始化Looper,所以主線程中默認可以使用 Handler。

概述

幾乎所有的 Android 開發者都知道在 Android 中訪問 UI 只能在主線程中進行。CheckThread() 方法會對線程調用 UI 操作的正確性做出驗證,如果當前訪問 UI 的線程并非主線程,則會拋出異常。
但, Android 建議不要在主線程使用耗時操作,以免導致程序無法響應,即ANR。在開發工作中,我們常常會遇到需要從服務端拉取信息,并在 UI 中進行顯示。Handler 的存在就是為了解決在子線程中無法訪問 UI 的矛盾。

  • 為什么在子線程中不允許訪問 UI 呢?因為 Android 的 UI 控件并非線程安全的,如果多線程并發訪問會導致 UI 控件處于不可預期的狀態。
    *為什么不加鎖?缺點有:1.加鎖會導致 UI 訪問邏輯變得復雜,其次鎖機制會降低 UI 訪問的效率,因為鎖機制會阻塞某些線程的執行。

* Handler的簡單使用

方法書里沒有介紹,翻出萌新筆記貼一點:
簡單應用:

private Handler handler = new Handler() {
 public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    // 在這里可以進行UI操作
                   break;
                default:
                    break;
            }
        }

    };
//在需要耗時操作的地方,開子線程
new Thread(new Runnable() {
    @Override
    public void run() {
//可以進行耗時操作
        Message message = new Message();
        message.what = UPDATE_TEXT;
        handler.sendMessage(message); //將Message對象發送出去
        }
    }).start();

過程如下:

  1. 首先在主線程中創建一個 Handler 對象,并重寫HandleMessage方法
  2. 在子線程中需要進行 UI 操作的時候創建一個 Message 對象,
  3. 通過 Handler 將信息發送出去,
  4. 該信息被添加到 MessageQueue 中等待被處理,Looper 則會一直嘗試從 MessageQueue 中取出待處理信息,最后分配到 Handler 的handleMessage() 方法中。

注意:在Activity中,并沒有顯式調用 Looper.prepare() 和Looper.loop() 方法,因為在 Activity 的啟動代碼中,已經在當前 UI 線程調用了Looper.prepare() 和 Looper.loop() 方法,這就是前文提到 UI 線程默認可以使用 Handler 的原因。
runOnUiThread() 是一個異步消息處理機制的接口封裝,用法簡單但實際原理是一樣的。

Handler 的工作原理

Handler 創建時會采用當前線程的 Looper 來構建內部消息循環系統,如果當前線程沒有 Looper ,那么就會報錯。
解決方法:為當前線程創建 Looper ,或者在一個有 Looper 的線程中創建 Handler
Handler 創建完畢之后,其內部的 Looper 以及 MessageQueue 就可以和 Handler 一起協同工作了,然后通過 Handler 的 post 方法將一個 Runnable 投遞到 Handler 內部的 Looper 中處理,也可通過 Handler 中的 send 發送消息,同樣在 Looper 內處理。post 的本質也是調用 send 。工作過程如圖:


Handler 工作過程.png

當 send 方法被調用,它會調用 MessageQueue 的 enqureMessage 方法將這個消息放入消息隊列,由 Looper 處理,最后消息中的 Runnable 或者 Handler 的 handlerMessage 方法會被調用。Looper 是運行在創建 Handler 所在的線程中的,這樣一來, Handler 中的業務邏輯就會被切換到創建 Handler 所在的線程中執行,完成切換線程的目的。

Android 的消息機制全面分析

ThreadLocal

一個線程內部的數據存儲類,通過它可以獨立存儲指定線程中的數據。日常開發中較少用到,但是 android 源碼中有時會利用它實現一些看似復雜的問題。

  1. 一般來說,當某些數據是以線程為作用域并且不同線程有不同的數據父本的時候,就會使用 ThreadLocal。比如 Handler,需要獲取當前線程的 Looper ,且 Looper 作用域為線程,不同線程間的 Looper 互相獨立,這時候使用 ThreadLocal 則可以輕松實現 Looper 在線程中的存取。 否則,系統必須提供一個全局的哈希表供 Handler 查找指定線程的 Looper,就必須存在類似于 LooperManage 這樣類,會使機制變得復雜。
  2. 可用于復雜邏輯下的對象傳遞,比如監聽器傳遞。當函數調用棧比較深的時候,如果把監聽器作為參數傳遞,會使程序設計變得糟糕;如果把監聽器作為靜態變量供線程訪問,則基本不具備擴展性。而使用 ThreadLocal ,每個線程都將擁有自己的監聽器,可以在線程內全局,一鍵 get 到。
    書上舉了簡單例子及源碼,說明 ThreadLocal 在各個線程的數據存儲獨立性,因為例子較簡單而源碼部分比較繁瑣,這里不再贅述。總之,不同線程訪問 ThreadLocal 的 get 方法, ThreadLocal 將從各線程內部取出一個數組,按照當前線程的索引,查找相應的 value 值,所以線程不用,值不同。從源碼分析可得, ThreadLocal 的 set 和 get 方法都僅能訪問當前線程的 localValue 對象的 table 數組,因此在不同數組中訪問同一個 ThreadLocal 的 set 和 get 方法可以互不干涉。

MessageQueue

前文已提過,消息隊列實際上內部是用單鏈表實現的,包含兩大重要方法, enqueueMessage 和 next 。

  • enqueueMessage 源碼
// android SDK-27

    boolean enqueueMessage(Message msg, long when) {
      ...
        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;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } 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;
                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;
    }

實際上就是單鏈表插入操作。

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

                // 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源碼解析

在構造方法中創建一個 MessageQueue ,然后將當前線程對象保存起來。

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

通過Looper.prepare() 即可手動當前線程創建一個 Looper, 接著通過 Looper.loop() 開啟循環。
Looper 提供了 quit 和 quitSafely 兩種方法退出 Looper,前者直接退出,后者設定一個安全標記,等消息隊列內所有消息處理完畢之后才會安全退出。如果在子線程里手動創建了 Looper 在所有消息完成之后應該調用 quit 方法,否則這個子線程會一直處在等待狀態。

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

loop 方法是一個死循環,唯一跳出死循環的條件是 MessageQueue.next 方法返回 null 。當 Looper.quit 被調用,Looper 調用 MessageQueue.quit 或者 quitSafely 方法通知消息隊列退出。next 是一個阻塞方法,如果未接到新消息將一直等待,如果接到新消息,則交給 dispatchMessage 處理,這個方法是在 handler 創建的 Looper 中執行的。

Handler 的工作原理

發送消息的典型過程

 public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }


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

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

也就是說,Handler 調用 sendMessage 方法,依次調用 sendMessageDelayed,sendMessageAtTime,enqueueMessage 方法后,調用 queue.enqueueMessage 向消息隊列插入了一條消息,接下來,按上文分析, Looper 中調用 MessageQueue.next 方法得到消息,最后將消息交給 Handler.dispatchMessage 處理。
實現如下:

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

Handler 處理消息過程如下:

  1. 檢查 Message callback 是否為 null,不為 null 則通過 handleCallback 來處理消息,Message 的 callback 是一個 Runnable 對象,實際上就是 Handler 的 post 方法傳遞的 Runnable 參數。
private static void handleCallback(Message message) {
        message.callback.run();
    }

2.檢查 mCallback 是否為 null ,不為 null 就調用 mCallback 的 handlerMessage 方法

 /**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     *
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public interface Callback {
        public boolean handleMessage(Message msg);
    }

通過Callback 可以采用Handler handler = new handler(callback)的方式創建 Handler 對象。callback 的意義在于可以創建一個 Handler 實例但不需要派生 Handler 的子類。在日常開發中,最常見的方式就是派生一個 Handler 的子類并且重寫 handlerMessage 方法,當不想用該方式的時候可以采用 callback 實現。

Handler 消息處理.png

主線程的消息循環

在主線程的入口方法中國,調用 Looper.prepareMainLooper() 來創建主線程的 Looper 以及 MessageQueue,并通過調用 Looper.loop() 來開啟循環。

 public static void main(String[] args) {
     ……
        Process.setArgV0("<pre-initialized>");
         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");
    }

主線程消息循環開始之后,ActivityThread 還需要 Handler 來和消息隊列進行交互,這個 Handler 就是 AcitivityThread.H。
ActivityThread 通過 Application Thread 和 AMS 進行進程間通信,AMS以進程間通信的方式完成 ActivityThread 的請求后會回調 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 會向 H 發消息,H 收到消息后將 ApplicationThread 中的邏輯切換到 ActivityThread 中執行,即切換到主線程中去執行。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容