Android非同步訊息處理機制詳解及原始碼分析

工匠若水發表於2015-07-11

最近相對來說比較閒,加上養病,所以沒事幹就擼些自己之前的知識點為部落格,方便自己也方便別人。

1 背景

之所以選擇這個知識點來分析有以下幾個原因:

  1. 逛GitHub時發現關注的isuss中有人不停的在討論Android中的Looper , Handler , Message有什麼關係。
  2. 其實這個知識點對於Android初學者來說很常用,但是初學者可能前期一直處於會用不知其原理的階段。
  3. 這個知識點也是Android面試中一個高頻問題。

基於以上幾點也得拿出來分析分析,該篇部落格從例項到原始碼完全進行了剖析(包含Handler、Message、MessageQueue、Looper、HandlerThread等原始碼),不同於網上很多隻是分析區域性的部落格。

你可能在剛開始接觸Android開發時就會知道如下問題:

Android的UI時執行緒不安全的,如果線上程中更新UI會出現異常,導致程式崩潰;同時如果UI中做耗時操作又會導致臭名昭著的ANR異常。

為了解決如上這些問題,我們怎辦呢?很簡單,通常最經典常用的做法就是使用Android的非同步訊息機制實現即可(建立一個Message物件,使用Handler傳送出去,然後在Handler的handleMessage()方法中獲得剛才傳送的Message物件,然後在這裡進行UI操作)。所以說還是很有必要了解非同步訊息機制的Looper , Handler , Message等原理的。

如下開始一個示例使用,然後通過原始碼分析吧。

2 示例展示

如下示例展示了UI Thread與Child Thread之間互相傳送訊息,同時在UI Thread與Child Thread中分別定義Handler。這樣如果沒有mCount的判斷,這段程式會一直死迴圈列印下去。

public class MainActivity extends ActionBarActivity {
    private int mCount = 0;
    private Handler mHandlerThr = null;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d(null, ">>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what="+msg.what);
            //接收傳送到UI執行緒的訊息,然後向執行緒中的Handler傳送msg 1。
            mHandlerThr.sendEmptyMessage(1);
            mCount++;
            if (mCount >= 3) {
                //由於mHandlerThr是在Child Thread建立,Looper手動死迴圈阻塞,所以需要quit。
                mHandlerThr.getLooper().quit();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();
    }

    @Override
    protected void onStop() {
        super.onStop();
        //刪除所有call與msg
        mHandler.removeCallbacksAndMessages(null);
    }

    private void initData() {
        Log.d(null, ">>>>>>>>>>>>>UI# begin start thread!!!");
        new Thread() {
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                mHandlerThr = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.d(null, ">>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=" + msg.what);
                        //接收傳送到子執行緒的訊息,然後向UI執行緒中的Handler傳送msg 0。
                        mHandler.sendEmptyMessage(0);
                    }
                };

                Log.d(null, ">>>>>>>>>>>>>Child# begin start send msg!!!");
                //Activity中啟動Thread,在Thread結束前傳送msg 0到UI Thread。
                mHandler.sendEmptyMessage(0);

                Looper.loop(); //不能在這個後面新增程式碼,程式是無法執行到這行之後的。
            }
        }.start();
    }
}

執行結果展示如下:

>>>>>>>>>>>>>UI# begin start thread!!!
>>>>>>>>>>>>>Child# begin start send msg!!!
>>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0
>>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=1
>>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0
>>>>>>>>>>>>>Child# mHandlerThr--handleMessage--msg.what=1
>>>>>>>>>>>>>UI# mHandler--handleMessage--msg.what=0

怎麼樣,這和你平時用的Handler一樣吧,對於Handler非同步處理的簡單基礎示例先說到這,接下來依據上面示例的寫法分析原因與原始碼原理。

3 分析Android 5.1.1(API 22)非同步訊息機制原始碼

3-1 看看Handler的例項化過程原始碼

3-1-1 Handler例項化原始碼

從哪著手分析呢?當然是例項化建構函式呀,所以我們先從Handler的預設建構函式開始分析,如下:

 /**
     * Default constructor associates this handler with the {@link Looper} for the
     * current thread.
     *
     * If this thread does not have a looper, this handler won't be able to receive messages
     * so an exception is thrown.
     */
    public Handler() {
        this(null, false);
    }

通過註釋也能看到,預設建構函式沒有引數,而且調運了帶有兩個引數的其他建構函式,第一個引數傳遞為null,第二個傳遞為false。

這個建構函式得到的Handler預設屬於當前執行緒,而且如果當前執行緒如果沒有Looper則通過這個預設構造例項化Handler時會丟擲異常,至於是啥異常還有為啥我們們繼續往下分析,this(null, false)的實現如下:

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

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

可以看到,在第11行呼叫了mLooper = Looper.myLooper();語句,然後獲取了一個Looper物件mLooper ,如果mLooper例項為空,則會丟擲一個執行時異常(Can’t create handler inside thread that has not called Looper.prepare()!)。

3-1-2 Looper例項化原始碼

好奇的你指定在想什麼時候mLooper 物件才可能為空呢?很簡單,跳進去看下吧,Looper類的靜態方法myLooper如下:

   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

咦?這裡好簡單。單單就是從sThreadLocal物件中get了一個Looper物件返回。跟蹤了一下sThreadLocal物件,發現他定義在Looper中,是一個static final型別的ThreadLocal<Looper>物件(在Java中,一般情況下,通過ThreadLocal.set() 到執行緒中的物件是該執行緒自己使用的物件,其他執行緒是不需要訪問的,也訪問不到的,各個執行緒中訪問的是不同的物件。)。所以可以看出,如果sThreadLocal中有Looper存在就返回Looper,沒有Looper存在自然就返回null了。

這時候你一定有疑惑,既然這裡是通過sThreadLocal的get獲得Looper,那指定有地方對sThreadLocal進行set操作吧?是的,我們在Looper類中跟蹤發現如下:

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

看著這個Looper的static方法prepare沒有?這段程式碼首先判斷sThreadLocal中是否已經存在Looper了,如果還沒有則建立一個新的Looper設定進去。

那就看下Looper的例項化,如下:

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

可以看見Looper建構函式無非就是建立了一個MessageQueue(它是一個訊息佇列,用於將所有收到的訊息以佇列的形式進行排列,並提供入隊和出隊的方法。)和貨到當前Thread例項引用而已。通過這裡可以發現,一個Looper只能對應了一個MessageQueue。

你可能會說上面的例子在子執行緒中明明先調運的是Looper.prepare();方法,這裡怎麼有引數了?那就繼續看吧,如下:

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

可以看見,prepare()僅僅是對prepare(boolean quitAllowed) 的封裝而已,預設傳入了true,也就是將MessageQueue物件中的quitAllowed標記標記為true而已,至於MessageQueue後面會分析。

稀奇古怪的事情來了!如果你足夠留意上面的例子,你會發現我們在UI Thread中建立Handler時沒有呼叫Looper.prepare();,而在initData方法中建立的Child Thread中首先就調運了Looper.prepare();。你指定很奇怪吧?UI Thread為啥不需要呢?上面原始碼分析明明需要先保證mLooper物件不為null呀?

這是由於在UI執行緒(Activity等)啟動的時候系統已經幫我們自動呼叫了Looper.prepare()方法。

那麼在哪啟動的呢?這個涉及Android系統架構問題比較多,後面文章會分析Activity的啟動流程。這裡你只要知道,以前一直都說Activity的人口是onCreate方法,其實android上一個應用的入口應該是ActivityThread類的main方法就行了。

所以為了解開UI Thread為何不需要建立Looper物件的原因,我們看下ActivityThread的main方法,如下:

    public static void main(String[] args) {
        SamplingProfilerIntegration.start();

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

        Security.addProvider(new AndroidKeyStoreProvider());

        // 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.prepareMainLooper();

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

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

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

        Looper.loop();

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

看見22行沒?沒說錯吧?Looper.prepareMainLooper();,我們跳到Looper看下prepareMainLooper方法,如下:

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

可以看到,UI執行緒中會始終存在一個Looper物件(sMainLooper 儲存在Looper類中,UI執行緒通過getMainLooper方法獲取UI執行緒的Looper物件),從而不需要再手動去呼叫Looper.prepare()方法了。如下Looper類提供的get方法:

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

看見沒有,到這裡整個Handler例項化與為何子執行緒在例項化Handler之前需要先調運Looper.prepare();語句的原理分析完畢。

3-1-3 Handler與Looper例項化總結

到此先初步總結下上面關於Handler例項化的一些關鍵資訊,具體如下:

  1. 在主執行緒中可以直接建立Handler物件,而在子執行緒中需要先呼叫Looper.prepare()才能建立Handler物件,否則執行丟擲”Can’t create handler inside thread that has not called Looper.prepare()”異常資訊。
  2. 每個執行緒中最多隻能有一個Looper物件,否則丟擲異常。
  3. 可以通過Looper.myLooper()獲取當前執行緒的Looper例項,通過Looper.getMainLooper()獲取主(UI)執行緒的Looper例項。
  4. 一個Looper只能對應了一個MessageQueue。
  5. 一個執行緒中只有一個Looper例項,一個MessageQueue例項,可以有多個Handler例項。

Handler物件也建立好了,接下來就該用了吧,所以下面我們們從Handler的收發訊息角度來分析分析原始碼。

3-2 繼續看看Handler訊息收發機制原始碼

3-2-1 通過Handler發訊息到訊息佇列

還記得上面的例子嗎?我們在Child Thread的最後通過主執行緒的Handler物件調運sendEmptyMessage方法傳送出去了一條訊息。

當然,其實Handler類提供了許傳送訊息的方法,我們這個例子只是用了最簡單的傳送一個empty訊息而已,有時候我們會先定義一個Message,然後通過Handler提供的其他方法進行傳送。通過分析Handler原始碼發現Handler中提供的很多個傳送訊息方法中除了sendMessageAtFrontOfQueue()方法之外,其它的傳送訊息方法最終都呼叫了sendMessageAtTime()方法。所以,我們們先來看下這個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);
    }

再看下Handler中和其他傳送方法不同的sendMessageAtFrontOfQueue方法,如下:

    public final boolean sendMessageAtFrontOfQueue(Message msg) {
        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, 0);
    }

對比上面兩個方法可以發現,表面上說Handler的sendMessageAtFrontOfQueue方法和其他傳送方法不同,其實實質是相同的,僅僅是sendMessageAtFrontOfQueue方法是sendMessageAtTime方法的一個特例而已(sendMessageAtTime最後一個引數傳遞0就變為了sendMessageAtFrontOfQueue方法)。所以我們們現在繼續分析sendMessageAtTime方法,如下分析:

sendMessageAtTime(Message msg, long uptimeMillis)方法有兩個引數;msg是我們傳送的Message物件,uptimeMillis表示傳送訊息的時間,uptimeMillis的值等於從系統開機到當前時間的毫秒數再加上延遲時間。

在該方法的第二行可以看到queue = mQueue,而mQueue是在Handler例項化時建構函式中例項化的。在Handler的建構函式中可以看見mQueue = mLooper.mQueue;,而Looper的mQueue物件上面分析過了,是在Looper的建構函式中建立的一個MessageQueue。

接著第9行可以看到,上面說的兩個引數和剛剛得到的queue物件都傳遞到了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屬性設定為當前Handler物件(進行關聯);接著將msg與uptimeMillis這兩個引數都傳遞到MessageQueue(訊息佇列)的enqueueMessage()方法中,所以接下來我們就繼續分析MessageQueue類的enqueueMessage方法,如下:

    boolean enqueueMessage(Message msg, long when) {
        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("MessageQueue", 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;
    }

通過這個方法名可以看出來通過Handler傳送訊息實質就是把訊息Message新增到MessageQueue訊息佇列中的過程而已。

通過上面遍歷等next操作可以看出來,MessageQueue訊息佇列對於訊息排隊是通過類似c語言的連結串列來儲存這些有序的訊息的。其中的mMessages物件表示當前待處理的訊息;然後18到49行可以看出,訊息插入佇列的實質就是將所有的訊息按時間(uptimeMillis引數,上面有介紹)進行排序。所以還記得上面sendMessageAtFrontOfQueue方法嗎?它的實質就是把訊息新增到MessageQueue訊息佇列的頭部(uptimeMillis為0,上面有分析)。

到此Handler的傳送訊息及傳送的訊息如何存入到MessageQueue訊息佇列的邏輯分析完成。

那麼問題來了!既然訊息都存入到了MessageQueue訊息佇列,當然要取出來訊息吧,不然存半天有啥意義呢?我們知道MessageQueue的物件在Looper建構函式中例項化的;一個Looper對應一個MessageQueue,所以說Handler傳送訊息是通過Handler建構函式裡拿到的Looper物件的成員MessageQueue的enqueueMessage方法將訊息插入佇列,也就是說出佇列一定也與Handler和Looper和MessageQueue有關係。

還記不記得上面例項部分中Child Thread最後調運的Looper.loop();方法呢?這個方法其實就是取出MessageQueue訊息佇列裡的訊息方法。具體在下面分析。

3-2-2 通過Handler接收傳送的訊息

先來看下上面例子中Looper.loop();這行程式碼調運的方法,如下:

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

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

可以看到,第6行首先得到了當前執行緒的Looper物件me,接著第10行通過當前Looper物件得到與Looper物件一一對應的MessageQueue訊息佇列(也就類似上面傳送訊息部分,Handler通過myLoop方法得到Looper物件,然後獲取Looper的MessageQueue訊息佇列物件)。17行進入一個死迴圈,18行不斷地呼叫MessageQueue的next()方法,進入MessageQueue這個類檢視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 (false) Log.v("MessageQueue", "Returning message: " + msg);
                        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("MessageQueue", "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;
        }
    }

可以看出來,這個next方法就是訊息佇列的出隊方法(與上面分析的MessageQueue訊息佇列的enqueueMessage方法對比)。可以看見上面程式碼就是如果當前MessageQueue中存在待處理的訊息mMessages就將這個訊息出隊,然後讓下一條訊息成為mMessages,否則就進入一個阻塞狀態(在上面Looper類的loop方法上面也有英文註釋,明確說到了阻塞特性),一直等到有新的訊息入隊。

繼續看loop()方法的第30行(msg.target.dispatchMessage(msg);),每當有一個訊息出隊就將它傳遞到msg.target的dispatchMessage()方法中。其中這個msg.target其實就是上面分析Handler傳送訊息程式碼部分Handler的enqueueMessage方法中的msg.target = this;語句,也就是當前Handler物件。所以接下來的重點自然就是回到Handler類看看我們熟悉的dispatchMessage()方法,如下:

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

可以看見dispatchMessage方法中的邏輯比較簡單,具體就是如果mCallback不為空,則呼叫mCallback的handleMessage()方法,否則直接呼叫Handler的handleMessage()方法,並將訊息物件作為引數傳遞過去。

這樣我相信大家就都明白了為什麼handleMessage()方法中可以獲取到之前傳送的訊息了吧!

對了,既然上面說了獲取訊息在MessageQueue訊息佇列中是一個死迴圈的阻塞等待,所以Looper的quit方法也很重要,這樣在不需要時可以退出這個死迴圈,如上面例項部分使用所示。

3-2-3 結束MessageQueue訊息佇列阻塞死迴圈原始碼分析

如下展示了Looper類的quit方法原始碼:

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

看見沒有,quit方法實質就是調運了MessageQueue訊息佇列的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);
        }
    }

看見上面2到4行程式碼沒有,通過判斷標記mQuitAllowed來決定該訊息佇列是否可以退出,然而當mQuitAllowed為fasle時丟擲的異常竟然是”Main thread not allowed to quit.”,Main Thread,所以可以說明Main Thread關聯的Looper一一對應的MessageQueue訊息佇列是不能通過該方法退出的。

你可能會疑惑這個mQuitAllowed在哪設定的?

其實他是MessageQueue建構函式傳遞引數傳入的,而MessageQueue物件的例項化是在Looper的建構函式實現的,所以不難發現mQuitAllowed引數實質是從Looper的構函式傳入的。上面例項化Handler模組原始碼分析時說過,Looper例項化是在Looper的靜態方法prepare(boolean quitAllowed)中處理的,也就是說mQuitAllowed是由Looper.prpeare(boolean quitAllowed)引數傳入的。追根到底說明mQuitAllowed就是Looper.prpeare的引數,我們預設調運的Looper.prpeare();其中對mQuitAllowed設定為了true,所以可以通過quit方法退出,而主執行緒ActivityThread的main中使用的是Looper.prepareMainLooper();,這個方法裡對mQuitAllowed設定為false,所以才會有上面說的”Main thread not allowed to quit.”。

回到quit方法繼續看,可以發現實質就是對mQuitting標記置位,這個mQuitting標記在MessageQueue的阻塞等待next方法中用做了判斷條件,所以可以通過quit方法退出整個當前執行緒的loop迴圈。

到此整個Android的一次完整非同步訊息機制分析使用流程結束。接下來進行一些總結提升與擴充。

3-3 簡單小結下Handler整個使用過程與原理

通過上面的原始碼分析原理我們可以總結出整個非同步訊息處理流程的關係圖如下:

這裡寫圖片描述

這幅圖很明顯的表達出了Handler非同步機制的來龍去脈,不做過多解釋。

上面例項部分我們只是演示了Handler的區域性方法,具體Handler還有很多方法,下面詳細介紹。

3-4 再來看看Handler原始碼的其他常用方法

在上面例子中我們只是演示了傳送訊息的sendEmptyMessage(int what)方法,其實Handler有如下一些傳送方式:

sendMessage(Message msg); sendEmptyMessage(int what); sendEmptyMessageDelayed(int what, long delayMillis);sendEmptyMessageAtTime(int what, long uptimeMillis); sendMessageDelayed(Message msg, long delayMillis);sendMessageAtTime(Message msg, long uptimeMillis); sendMessageAtFrontOfQueue(Message msg);方法。

這些方法不再做過多解釋,用法雷同,頂一個Message決定啥時傳送到target去。

post(Runnable r); postDelayed(Runnable r, long delayMillis);等post系列方法。

該方法實質原始碼其實就是如下:

    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

額,原來post方法的實質也是調運sendMessageDelayed()方法去處理的額,看見getPostMessage(r)方法沒?如下原始碼:

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

如上方法僅僅是將訊息的callback欄位指定為傳入的Runnable物件r。其實這個Message物件的m.callback就是上面分析Handler接收訊息回撥處理dispatchMessage()方法中調運的。在Handler的dispatchMessage方法中首先判斷如果Message的callback等於null才會去呼叫handleMessage()方法,否則就呼叫handleCallback()方法。那就再看下Handler的handleCallback()方法原始碼,如下:

    private static void handleCallback(Message message) {
        message.callback.run();
    }

額,這裡竟然直接執行了Runnable物件的run()方法。所以說我們在Runnable物件的run()方法裡更新UI的效果完全和在handleMessage()方法中更新UI相同,特別強調這個Runnable的run方法還在當前執行緒中阻塞執行,沒有建立新的執行緒(很多人以為是Runnable就建立了新執行緒)。

Activity.runOnUiThread(Runnable);方法。

首先看下Activity的runOnUiThread方法原始碼:

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

看見沒有,實質還是在UI執行緒中執行了Runnable的run方法。不做過多解釋。

View.post(Runnable);和View.postDelayed(Runnable action, long delayMillis);方法。

首先看下View的postDelayed方法原始碼:

    public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
        return true;
    }

看見沒有,實質還是在UI執行緒中執行了Runnable的run方法。不做過多解釋。

到此基本上關於Handler的所有傳送訊息方式都被解釋明白了。既然會用了基本的那就得提高下,接下來看看關於Message的一點優化技巧。

3-5 關於Handler傳送訊息的一點優化分析

還記得我們說過,當傳送一個訊息時我們首先會new一個Message物件,然後再傳送嗎?你是不是覺得每次new Message很浪費呢?那麼我們就來分析一下這個問題。

如下是我們正常傳送訊息的程式碼區域性片段:

    Message message = new Message();
    message.arg1 = 110;
    message.arg2 = 119;
    message.what = 0x120;
    message.obj = "Test Message Content!";

    mHandler.sendMessage(message);

相信很多初學者都是這麼傳送訊息的吧?當有大量多次傳送時如上寫法會不太高效。不賣關子,先直接看達到同樣效果的優化寫法,如下:

mHandler.sendMessage(mHandler.obtainMessage(0x120, 110, 119, "\"Test Message Content!\""));

咦?怎麼send時沒例項化Message?這是咋回事?我們看下mHandler.obtainMessage(0x120, 110, 119, "\"Test Message Content!\"")這一段程式碼,obtainMessage是Handler提供的一個方法,看下原始碼:

    public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
    {
        return Message.obtain(this, what, arg1, arg2, obj);
    }

這方法竟然直接調運了Message類的靜態方法obtain,我們再去看看obtain的原始碼,如下:

    public static Message obtain(Handler h, int what, 
            int arg1, int arg2, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }

看見沒有?首先又調運一個無參的obtain方法,然後設定Message各種引數後返回。我們繼續看下這個無參方法,如下:

    /**
     * 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 = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

真相大白了!看見註釋沒有?從整個Messge池中返回一個新的Message例項,在許多情況下使用它,因為它能避免分配新的物件。

所以看見這兩種獲取Message寫法的優缺點沒有呢?明顯可以看見通過呼叫Handler的obtainMessage方法獲取Message物件就能避免建立物件,從而減少記憶體的開銷了。所以推薦這種寫法!!!

3-6 關於Handler導致記憶體洩露的分析與解決方法

正如上面我們例項部分的程式碼,使用Android Lint會提示我們這樣一個Warning,如下:

In Android, Handler classes should be static or leaks might occur.

意思是說在Android中Handler類應該是靜態的否則可能發生洩漏。

啥是記憶體洩漏呢?

Java通過GC自動檢查記憶體中的物件,如果GC發現一個或一組物件為不可到達狀態,則將該物件從記憶體中回收。也就是說,一個物件不被任何引用所指向,則該物件會在被GC發現的時候被回收;另外,如果一組物件中只包含互相的引用,而沒有來自它們外部的引用(例如有兩個物件A和B互相持有引用,但沒有任何外部物件持有指向A或B的引用),這仍然屬於不可到達,同樣會被GC回收。本該被回收的物件沒被回收就是記憶體洩漏。

Handler中怎麼洩漏的呢?

當使用內部類(包括匿名類)來建立Handler的時候,Handler物件會隱式地持有一個外部類物件(通常是一個Activity)的引用。而Handler通常會伴隨著一個耗時的後臺執行緒一起出現,這個後臺執行緒在任務執行完畢之後,通過訊息機制通知Handler,然後Handler把訊息傳送到UI執行緒。然而,如果使用者在耗時執行緒執行過程中關閉了Activity(正常情況下Activity不再被使用,它就有可能在GC檢查時被回收掉),由於這時執行緒尚未執行完,而該執行緒持有Handler的引用,這個Handler又持有Activity的引用,就導致該Activity暫時無法被回收(即記憶體洩露)。

Handler記憶體洩漏解決方案呢?

方案一:通過程式邏輯來進行保護

  1. 在關閉Activity的時候停掉你的後臺執行緒。執行緒停掉了,就相當於切斷了Handler和外部連線的線,Activity自然會在合適的時候被回收。
  2. 如果你的Handler是被delay的Message持有了引用,那麼使用相應的Handler的removeCallbacks()方法,把訊息物件從訊息佇列移除就行了(如上面的例子部分的onStop中程式碼)。

方案二:將Handler宣告為靜態類

靜態類不持有外部類的物件,所以你的Activity可以隨意被回收。程式碼如下:

static class TestHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }
}

這時你會發現,由於Handler不再持有外部類物件的引用,導致程式不允許你在Handler中操作Activity中的物件了。所以你需要在Handler中增加一個對Activity的弱引用(WeakReference),如下:

static class TestHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    TestHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}

如上就是關於Handler記憶體洩漏的分析及解決方案。

可能在你會用了Handler之後見過HandlerThread這個關鍵字,那我們接下來就看看HandlerThread吧。

4 關於Android非同步訊息處理機制進階的HandlerThread原始碼分析

4-1 Android 5.1.1(API 22) HandlerThread原始碼

很多人在會使用Handler以後會發現有些程式碼裡出現了HandlerThread,然後就分不清HandlerThread與Handler啥關係,咋回事之類的。這裡就來分析分析HandlerThread的原始碼。如下:

public class HandlerThread extends Thread {
    //執行緒的優先順序
    int mPriority;
    //執行緒的id
    int mTid = -1;
    //一個與Handler關聯的Looper物件
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        //設定優先順序為預設執行緒
        mPriority = android.os.Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    //可重寫方法,Looper.loop之前線上程中需要處理的其他邏輯在這裡實現
    protected void onLooperPrepared() {
    }
    //HandlerThread執行緒的run方法
    @Override
    public void run() {
        //獲取當前執行緒的id
        mTid = Process.myTid();
        //建立Looper物件
        //這就是為什麼我們要在呼叫執行緒的start()方法後才能得到Looper(Looper.myLooper不為Null)
        Looper.prepare();
        //同步程式碼塊,當獲得mLooper物件後,喚醒所有執行緒
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        //設定執行緒優先順序
        Process.setThreadPriority(mPriority);
        //Looper.loop之前線上程中需要處理的其他邏輯
        onLooperPrepared();
        //建立了訊息迴圈
        Looper.loop();
        //一般執行不到這句,除非quit訊息佇列
        mTid = -1;
    }

    public Looper getLooper() {
        if (!isAlive()) {
            //執行緒死了
            return null;
        }

        //同步程式碼塊,正好和上面run方法中同步塊對應
        //只要執行緒活著並且mLooper為null,則一直等待
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            //退出訊息迴圈
            looper.quit();
            return true;
        }
        return false;
    }

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            //退出訊息迴圈
            looper.quitSafely();
            return true;
        }
        return false;
    }

    public int getThreadId() {
        //返回執行緒id
        return mTid;
    }
}

看見沒有,這就是HandlerThread的系統原始碼,整個HandlerThread類很簡單。如上對重點都進行了註釋。

現在可以很負責的告訴你Handler到底與HandlerThread啥關係,其實HandlerThread就是Thread、Looper和Handler的組合實現,Android系統這麼封裝體現了Android系統元件的思想,同時也方便了開發者開發。

上面原始碼可以看到,HandlerThread主要是對Looper進行初始化,並提供一個Looper物件給新建立的Handler物件,使得Handler處理訊息事件在子執行緒中處理。這樣就發揮了Handler的優勢,同時又可以很好的和執行緒結合到一起。

到此HandlerThread原始碼原理也分析完了,那麼就差實戰了,如下繼續。

4-2 Android HandlerThread實戰

上面分析了關於HandlerThread原始碼,下面就來演示一個例項,如下:

public class ListenerActivity extends Activity {
    private HandlerThread mHandlerThread = null;
    private Handler mThreadHandler = null;
    private Handler mUiHandler = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        initData();
    }

    private void initData() {
        Log.i(null, "Main Thread id="+Thread.currentThread().getId());

        mHandlerThread = new HandlerThread("HandlerWorkThread");
        //必須在例項化mThreadHandler之前調運start方法,原因上面原始碼已經分析了
        mHandlerThread.start();
        //將當前mHandlerThread子執行緒的Looper傳入mThreadHandler,使得
        //mThreadHandler的訊息佇列依賴於子執行緒(在子執行緒中執行)
        mThreadHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.i(null, "在子執行緒中處理!id="+Thread.currentThread().getId());
                //從子執行緒往主執行緒傳送訊息
                mUiHandler.sendEmptyMessage(0);
            }
        };

        mUiHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.i(null, "在UI主執行緒中處理!id="+Thread.currentThread().getId());
            }
        };
        //從主執行緒往子執行緒傳送訊息
        mThreadHandler.sendEmptyMessage(1);
    }
}

執行結果如下:

Main Thread id=1
在子執行緒中處理!id=113
在UI主執行緒中處理!id=1

好了,不做過多解釋了,很簡單的。

5 關於Android非同步訊息處理機制總結

到此整個Android的非同步處理機制Handler與HandlerThread等分析完成(關於Android的另一種非同步處理機制AsyncTask後面有時間再分析)。相信通過這一篇文章你對Android的Handler使用還是原理都會有一個質的飛躍。

相關文章