【轉載】Android異步消息處理機制詳解及源碼分析

PS一句:最終仍是選擇CSDN來整理髮表這幾年的知識點,該文章平行遷移到CSDN。由於CSDN也支持MarkDown語法了,牛逼啊!java

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】android

最近相對來講比較閒,加上養病,因此沒事幹就擼些本身以前的知識點爲博客,方便本身也方便別人。面試

1 背景

之因此選擇這個知識點來分析有如下幾個緣由:api

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

基於以上幾點也得拿出來分析分析,該篇博客從實例到源碼徹底進行了剖析(包含Handler、Message、MessageQueue、Looper、HandlerThread等源碼),不一樣於網上不少只是分析局部的博客。安全

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

Android的UI時線程不安全的,若是在線程中更新UI會出現異常,致使程序崩潰;同時若是UI中作耗時操做又會致使臭名昭著的ANR異常。架構

爲了解決如上這些問題,咱們怎辦呢?很簡單,一般最經典經常使用的作法就是使用Android的異步消息機制實現便可(建立一個Message對象,使用Handler發送出去,而後在Handler的handleMessage()方法中得到剛纔發送的Message對象,而後在這裏進行UI操做)。因此說仍是頗有必要了解異步消息機制的Looper , Handler , Message等原理的。app

以下開始一個示例使用,而後經過源碼分析吧。less

這裏寫圖片描述

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使用仍是原理都會有一個質的飛躍。

【工匠若水 http://blog.csdn.net/yanbober 轉載煩請註明出處,尊重分享成果】

這裏寫圖片描述

相關文章
相關標籤/搜索