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

提到消息機制讀者應該都不陌生……從開發角度來講, Handler 是 Android 消息機制的上層接口,這使得在開發過程當中只須要和 Handler 交互便可。……經過它能夠輕鬆將一個任務切換到 Handler 所在的線程中去執行。java

正如開篇詞所說,「主線程中不能進行網絡通訊等耗時操做,而子線程中不能進行 UI 更新」,是我在 android 開發入門遇到的第一個知識點(keng),但當時只是單純記憶,本篇將順着開發藝術探索的講述,梳理 android 的消息機制有關知識。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 。工做過程如圖: app

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

Android 的消息機制全面分析

ThreadLocal

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

  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 中執行,即切換到主線程中去執行。

相關文章
相關標籤/搜索