Android 消息機制學習

Android消息機制大家都不陌生吊趾,想必大家也都看過Handler、Looper的源碼(看過就可以忽略下文咯穆碎,直接看后文的重點)牙勘,下面就整合一下這方面的資料,加深對這方面的印象所禀。

用法

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MESSAGE_TEXT_VIEW:
                mTextView.setText("UI成功更新");
            default:
                super.handleMessage(msg);
        }
    }
};

new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
              Message message = new Message();   
            message.what = MESSAGE_TEXT_VIEW;     
            mHandler.sendMessage(message); 
        }
    }).start();

Handler 機制架構(gòu)

從上圖可以看到方面,是圍繞 Handler、Message色徘、MessageQueue 和 Looper 進行的恭金。先介紹相關(guān)的概念

從開發(fā)角度看, Handler 是 Android 消息系統(tǒng)機制的上層接口褂策,這使得在開發(fā)過程中只需要和 Handler 交互即可横腿。另外, Handler 并不是專門用來更新 UI 的斤寂,只是經(jīng)常被開發(fā)者用來更新 UI 而已耿焊,但是不能忽略它的其他功能,例如進行耗時的 I/O 操作等遍搞。

疑問:為什么子線程不能更新 UI罗侯?

這是因為 ViewRootImpl 對 UI 操作進行了驗證

void checkThread() {
   if (mThread != Thread.currentThread()) {//Thread.currentThread()是UI主線程
       throw new CalledFromWrongThreadException(
               "Only the original thread that created a view hierarchy can touch its views.");
   }

另外, Android 的 UI 空間不是線程安全的溪猿,如果在多線程中并發(fā)訪問可能會導致 UI 控件處于不可預期的狀態(tài)钩杰。

疑問:為什么不對 UI 控件的訪問加上鎖機制呢?

這是因為加鎖诊县,會導致 UI 訪問的邏輯變復雜讲弄;其次,鎖機制會降低 UI 訪問的效率依痊。

這也是為啥會存在 Hanlder 的原因避除。

MessageQueue:消息隊列,顧名思義,它的內(nèi)部存儲了一組消息驹饺,以隊列的形式對外提供插入和刪除的工作钳枕。但是其內(nèi)部是采用單鏈表來存儲消息列表缴渊。

Looper:循環(huán)(消息循環(huán))赏壹,以無限循環(huán)的形式去查找是否有新消息,有則處理衔沼,無則等待蝌借。

Handler 源碼分析及其原理

Handler 的構(gòu)造方法

Handler 的構(gòu)造方法有很多,核心的構(gòu)造方法如下

/**
 * Use the {@link Looper} for the current thread with the specified callback interface
 * and set whether the handler should be asynchronous.
 *
 * Handlers are synchronous by default unless this constructor is used to make
 * one that is strictly asynchronous.
 *
 * Asynchronous messages represent interrupts or events that do not require global ordering
 * with respect to synchronous messages.  Asynchronous messages are not subject to
 * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
 *
 * @param callback The callback interface in which to handle messages, or null.
 * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
 * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
 *
 * @hide
 */
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {//默認是false指蚁,若為true菩佑,則會檢測當前handler是否是靜態(tài)類
        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();//獲得了 Looper 對象
    if (mLooper == null) {//如果是工作線程,就為空
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");//不能在未調(diào)用 Looper.prepare() 的線程創(chuàng)建 handler
    }
    mQueue = mLooper.mQueue;//mLooper對應的消息隊列
    mCallback = callback;
    mAsynchronous = async;
}

一個構(gòu)造方法凝化,Android 消息機制的三個重要角色全部出現(xiàn)了稍坯,分別是 Handler 、Looper 以及 MessageQueue搓劫。

mLooper = Looper.myLooper();//獲得了 Looper 對象

下面看看 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 @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

sThreadLocal 是個嘛

 // sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

好溫馨的提示瞧哟,定義在 Looper 中,是一個 static final 類型的 ThreadLocal<Looper> 對象(在 Java 中枪向,一般情況下勤揩,通過 ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的秘蛔,也訪問不到的陨亡,各個線程中訪問的是不同的對象。)至于 ThreadLocal 是個嘛深员,參考這里

大概說一下负蠕, ThreadLocal 是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲數(shù)據(jù)倦畅,數(shù)據(jù)存儲以后虐急,只有在指定線程中可以獲得存儲的數(shù)據(jù),對于其他線程來說則無法獲取到數(shù)據(jù)滔迈。

對于 Handler 來說止吁,它需要獲取當前線程的 Looper,很顯然燎悍,Looper 的作用域就是線程并且不同線程具有不同的 Looper敬惦,這個時候通過 ThreadLocal 就可以輕松實現(xiàn) Looper 在線程中的存取。

根據(jù)提示谈山,看看 prepare() 方法

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {//一個線程只會有一個 Looper
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

這段代碼首先判斷 sThreadLocal 中是否已經(jīng)存在 Looper 了俄删,如果還沒有則創(chuàng)建一個新的 Looper 設(shè)置進去。下面看看 Looper 的構(gòu)造方法

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

構(gòu)造方法可以看出,創(chuàng)建了一個 MessageQueue畴椰,傳入?yún)?shù)值為 true (子線程默認是true臊诊,why?后面有講到)斜脂;創(chuàng)建了一個當前 thread 的實例引用抓艳。很明顯,one looper only one MessageQueue

到此帚戳,就有一個疑問了:在 UI Thread 中創(chuàng)建 Handler 時沒有調(diào)用 Looper.prepare()玷或,但是卻能正常運行(但是,我們注意到片任,sThreadLocal.get() will return null unless you've called prepare())偏友,Why?

既然能正常運行对供,那么肯定是調(diào)用了 prepare 方法位他,但是,在哪里調(diào)用了呢产场,這就要看主線程 ActivityThread 鹅髓。首次啟動 Activity 時通過 Process.start 創(chuàng)建應用層程序的主線程,創(chuàng)建成功后進入到主線程 ActivityThread 的 main 方法中開始執(zhí)行涝动, main 方法有:

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

很明顯咯迈勋,秘密就在 prepareMainLooper() 里面(即使后面加了個 MainLooper,但也是個 prepare)

/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);//可以看出醋粟,UI thread傳入的是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 對象),從而不需要再手動去調(diào)用 Looper.prepare() 方法了米愿。如下 Looper 類提供的 get 方法:

/**
 * Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

到這里厦凤,上面疑問的答案就顯而易見了。同時育苟,如果在子線程實例化 Handler较鼓,就必須要先調(diào)用Looper.prepare() 方法才可以。

到此先初步總結(jié)下上面關(guān)于 Handler 實例化的一些關(guān)鍵信息违柏,具體如下:

  • 在主線程中可以直接創(chuàng)建 Handler 對象博烂,而在子線程中需要先調(diào)用 Looper.prepare() 才能創(chuàng)建 Handler 對象,否則運行拋出 ”Can’t create handler inside thread that has not called Looper.prepare()” 異常信息漱竖。

  • 每個線程中最多只能有一個 Looper 對象禽篱,否則拋出異常。

  • 可以通過 Looper.myLooper() 獲取當前線程的 Looper 實例馍惹,通過 Looper.getMainLooper() 獲取主(UI)線程的 Looper 實例躺率。

  • 一個 Looper 只能對應了一個M essageQueue 玛界。

  • 一個線程中只有一個 Looper 實例,一個 MessageQueue 實例悼吱,可以有多個 Handler 實例慎框。

Handler 對象也創(chuàng)建好了,接下來就該發(fā)送消息了 mHandler.sendMessage(message);

/**
 * Pushes a message onto the end of the message queue after all pending messages
 * before the current time. It will be received in {@link #handleMessage},
 * in the thread attached to this handler.
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

嗯后添,繼續(xù)往下看咯

/**
 * Enqueue a message into the message queue after all pending messages
 * before (current time + delayMillis). You will receive it in
 * {@link #handleMessage}, in the thread attached to this handler.
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

最終走到了 sendMessageAtTime 這個方法

/**
 * Enqueue a message into the message queue after all pending messages
 * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
 * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
 * Time spent in deep sleep will add an additional delay to execution.
 * You will receive it in {@link #handleMessage}, in the thread attached
 * to this handler.
 * 
 * @param uptimeMillis The absolute time at which the message should be
 *         delivered, using the
 *         {@link android.os.SystemClock#uptimeMillis} time-base.
 *         
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;//mQueue是在Handler實例化時構(gòu)造函數(shù)中實例化的
    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);
}

sendMessageAtTime() 方法接收兩個參數(shù)笨枯,其中 msg 參數(shù)就是我們發(fā)送的 Message 對象,而uptimeMillis 參數(shù)則表示發(fā)送消息的時間吕朵,它的值等于自系統(tǒng)開機到當前時間的毫秒數(shù)再加上延遲時間猎醇,如果調(diào)用的不是 sendMessageDelayed() 方法窥突,延遲時間就為0努溃。

而 mQueue 是在 Handler 實例化時構(gòu)造函數(shù)中實例化的,在 Handler 的構(gòu)造函數(shù)中可以看見 mQueue = mLooper.mQueue ;而 Looper 的 mQueue 對象上面分析過了阻问,是在 Looper 的構(gòu)造函數(shù)中創(chuàng)建的一個MessageQueue梧税。

最終的 MessageQueue 的 enqueueMessage() 方法是個嘛,下面看看

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

這個方法首先將我們要發(fā)送的消息 Message 的 target 屬性設(shè)置為當前 Handler 對象(進行關(guān)聯(lián))称近;接著將 msg 與 uptimeMillis 這兩個參數(shù)都傳遞到 MessageQueue (消息隊列)的 enqueueMessage() 方法中第队,如下

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {//上面的target已經(jīng)是handler對象,not 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(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();//設(shè)置當前msg的狀態(tài)
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {//檢測當前頭指針是否為空(隊列為空)或者沒有設(shè)置when 或者設(shè)置的when比頭指針的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.
            //幾種情況要喚醒線程處理消息:1)隊列是堵塞的 2)barrier刨秆,頭部結(jié)點無target 3)當前msg是堵塞的
            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 將當前msg插入第一個比其when值大的結(jié)點前凳谦。
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue 消息隊列對于消息排隊是通過類似 C 語言的鏈表來存儲這些有序的消息的。其中的 mMessages 對象表示當前待處理的消息衡未;消息插入隊列的實質(zhì)就是將所有的消息按時間( uptimeMillis 參數(shù)尸执,也就是 when )進行排序。具體的操作方法就根據(jù)時間的順序調(diào)用 msg.next 缓醋,從而為每一個消息指定它的下一個消息是什么如失。

當然如果你是通過 sendMessageAtFrontOfQueue() 方法來發(fā)送消息的,它也會調(diào)用 enqueueMessage() 來讓消息入隊送粱,只不過時間為0褪贵,這時會把 mMessages 賦值為新入隊的這條消息,然后將這條消息的 next 指定為剛才的 mMessages 抗俄,這樣也就完成了添加消息到隊列頭部的操作脆丁。

通過上面了解到,消息入隊并不是按照我們的認知那樣:入隊入的是隊尾动雹,而是根據(jù)when 的大小來插入(頭插是因為when = 0))

這里有一個小問題槽卫,大家可以自行考慮一下:當我利用 sendMessageDelayed() 方法延遲一段時間發(fā)送后,立馬開啟一個死循環(huán)洽胶,不停的 sendMessageAtFrontOfQueue() 晒夹,那么裆馒,之前的延遲發(fā)送的消息還會被執(zhí)行到嗎?

到此丐怯,消息也通過 handler 發(fā)送了喷好,并且存到了 MessageQueue 中,那么读跷,系統(tǒng)怎么處理 message 呢梗搅?

我們知道 MessageQueue 的對象在 Looper 構(gòu)造函數(shù)中實例化的;一個 Looper 對應一個 MessageQueue效览,所以說 Handler 發(fā)送消息是通過 Handler 構(gòu)造函數(shù)里拿到的 Looper 對象的成員 MessageQueue 的enqueueMessage 方法將消息插入隊列无切,也就是說出隊列一定也與 Handler 和 Looper 和 MessageQueue有關(guān)系。

既然會涉及到出隊丐枉,那么肯定就有出隊的方法哆键,那么找來找去,就在 loop() 方法里面(為啥 UI Thread 沒有調(diào)用 loop瘦锹,loop 也會執(zhí)行呢籍嘹?一看就沒有好好的看剛才貼的代碼,明明已經(jīng)調(diào)用弯院,卻說沒有調(diào)用

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

可以看到 for (;;) {} 就是一個死循環(huán)辱士,然后不斷的調(diào)用 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;
    }
}

同樣,可以看到 for (;;) {}听绳,一個死循環(huán)

它的簡單邏輯就是如果當前 MessageQueue 中存在 mMessages (即待處理消息)颂碘,就將這個消息出隊,然后讓下一條消息成為 mMessages椅挣,否則就進入一個阻塞狀態(tài)头岔,一直等到有新的消息入隊。

這里有個疑問贴妻,相信網(wǎng)友的回答應該能回答這個問題了切油。

另外,可以參考這里名惩,里面有對 epoll_wait 的介紹澎胡。

繼續(xù) loop 方法,每當有一個消息出隊就將它傳遞到 msg.target 的 dispatchMessage() 方法中娩鹉。其中這個 msg.target 其實就是當前 Handler 對象

/**
 * 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 方法中的邏輯比較簡單攻谁,具體就是檢查 Message 的 callback 是否為空,不為空弯予,就通過 handleCallback() 方法處理消息戚宦。 Message 的 callback 是一個 Runnable 對象,就是handler 的 post 方法所傳遞的 Runnable 參數(shù))不為空锈嫩,handleCallback() 方法受楼,如下

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

在這里垦搬,把沒有講到的一個方法列一下

/**
 * Causes the Runnable r to be added to the message queue.
 * The runnable will be run on the thread to which this handler is 
 * attached. 
 *  
 * @param r The Runnable that will be executed.
 * 
 * @return Returns true if the Runnable was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

我們在使用的過程中,還可以采用上面的方法艳汽,那么問題來了猴贰,Runnable為何可以轉(zhuǎn)變?yōu)镸essage呢,答案就在 getPostMessage(r) 中河狐;

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

答案顯而易見了米绕,在結(jié)合上面的 dispatchMessage 方法,我們就更容易明白了馋艺。

否則栅干,就檢查 mCallback 是否為空,不為空就調(diào)用 Callback 的 handleMessage() 方法處理消息捐祠。mCallback 是一個接口碱鳞,如下

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

通過注釋可知,可以采用如下方式創(chuàng)建 Handler 對象: Handler handler = new Handler(callback)雏赦。對應的構(gòu)造方法如下

/**
 * Constructor associates this handler with the {@link Looper} for the
 * current thread and takes a callback interface in which you can handle
 * messages.
 *
 * If this thread does not have a looper, this handler won't be able to receive messages
 * so an exception is thrown.
 *
 * @param callback The callback interface in which to handle messages, or null.
 */
public Handler(Callback callback) {
    this(callback, false);
}

Callback 的意義如同注釋一般:可以用來創(chuàng)一個 Handler 的實例但不需要派生出 Handler 的子類劫笙。

因為在日常開發(fā)過程中芙扎,創(chuàng)建 Handler 最常見的方式就是派生一個 Handler 的子類并重寫其handleMessage 方法來處理具體的消息星岗,而Callback給我們提供了另外一種使用 Handler 的方式,當我們不想派生子類時戒洼,就可以通過 Callback 實現(xiàn)俏橘。

最后,調(diào)用 Handler 的 handleMessage 方法來處理消息圈浇。

為什么handleMessage() 方法中可以獲取到之前發(fā)送的消息寥掐,這就是原因。

因此磷蜀,一個最標準的異步消息處理線程的寫法應該是這樣(下面這段代碼召耘,就在 Looper 類的注釋里面,注釋往往很有用的哦褐隆,谷歌程序猿的某些注釋也是很搞笑很認真的):

class LooperThread extends Thread {
  public Handler mHandler;

  public void run() {
      Looper.prepare();

      mHandler = new Handler() {
          public void handleMessage(Message msg) {
              // process incoming messages here
          }
      };

      Looper.loop();
  }
}

現(xiàn)在再看 handler 的架構(gòu)圖污它,是不是就更清晰了。

當我們在子線程調(diào)用 loop.prepare() 和 loop() 方法后庶弃,最好調(diào)用 loop.quit() 方法退出衫贬,終止消息循環(huán),否則這個子線程就會一直處于等待狀態(tài)歇攻。那么 quit 方法如下

/**
 * Quits the looper.
 * <p>
 * Causes the {@link #loop} method to terminate without processing any
 * more messages in the message queue.
 * </p><p>
 * Any attempt to post messages to the queue after the looper is asked to quit will fail.
 * For example, the {@link Handler#sendMessage(Message)} method will return false.
 * </p><p class="note">
 * Using this method may be unsafe because some messages may not be delivered
 * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
 * that all pending work is completed in an orderly manner.
 * </p>
 *
 * @see #quitSafely
 */
public void quit() {
    mQueue.quit(false);
}

再找

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

我們知道固惯,在子線程中調(diào)用 preare 時

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

默認的是 true,也是就是說缴守,子線程是可以退出的葬毫,而在 UI Thread 中

public static void prepareMainLooper() {
    prepare(false);//可以看出镇辉,UI thread傳入的是false
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

傳的 false,就是提示 UI Thread 是不可以退出的

回到 quit 方法繼續(xù)看贴捡,可以發(fā)現(xiàn)實質(zhì)就是對 mQuitting 標記置位摊聋,這個 mQuitting 標記在MessageQueue 的阻塞等待 next 方法中用做了判斷條件,所以可以通過 quit 方法退出整個當前線程的loop 循環(huán)栈暇。

到此整個 Android 的一次完整異步消息機制分析使用流程結(jié)束麻裁。

前面涉及到的幾個主要的類 Handler、Looper源祈、MessageQueue 和 Message 的關(guān)系如下所述:

  • Handler 負責將 Looper 綁定到線程煎源,初始化 Looper 和提供對外 API(我們在應用層,只關(guān)注這個香缺,多么好的設(shè)計模式)手销。
  • Looper 負責消息循環(huán)和操作 MessageQueue 對象。
  • MessageQueue 實現(xiàn)了一個消息隊列图张,好比那抽水泵锋拖,源源不斷的處理 Message。
  • Message 是一次業(yè)務中所有參數(shù)的載體(里面還涉及到 Message 池祸轮,感興趣的自己查閱源代碼)兽埃。

重點

如果您看到了這里,那么今天分析 Handler适袜、Loop 和 MessageQueue柄错,主要是為了引出下面的這個東西 BlockCanary, 一個 Android 平臺的一個非侵入式的性能監(jiān)控組件,項目中已經(jīng)打算性能優(yōu)化專項中引入并解決相關(guān)性能問題苦酱,為了了解其原理售貌,故整理了一下整個 Handler 的原理。該控件的相關(guān)說明在這里疫萤。

如果應用滑動卡頓颂跨,可以使用該控件進行監(jiān)控(作者實現(xiàn)這個控件的原理,相比大家看過就會了解扯饶,很佩服作者在消息機制中發(fā)現(xiàn)了這么一個方式能夠監(jiān)控性能恒削,同樣是看過源碼分析,差距還是很明顯的帝际,腦子不夠開竅哇)

參考資料

Android開發(fā)藝術(shù)探索[M]. 電子工業(yè)出版社, 2015.372-390

android在線程中創(chuàng)建handler應注意什么 #44

Android異步消息處理機制完全解析蔓同,帶你從源碼的角度徹底理解

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

補充

Android 定時器實現(xiàn)的幾種方式和removeCallbacks失效問題詳解

上面這個鏈接主要是針對項目中,偶現(xiàn)無法正常 remove 的情況蹲诀,有時候處理起來很費解斑粱,涉及到生命周期、多線程脯爪、是否會重建等比較多的方面则北,應該從代碼的健壯性矿微、項目的復雜性、線程切換尚揣、生命周期切換等很多方法考慮原因(具體問題具體分析涌矢,群策群力)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末快骗,一起剝皮案震驚了整個濱河市娜庇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌方篮,老刑警劉巖名秀,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異藕溅,居然都是意外死亡匕得,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門巾表,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汁掠,“玉大人,你說我怎么就攤上這事集币】稼澹” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵惠猿,是天一觀的道長羔砾。 經(jīng)常有香客問我,道長偶妖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任政溃,我火速辦了婚禮趾访,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘董虱。我一直安慰自己扼鞋,他們只是感情好,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布愤诱。 她就那樣靜靜地躺著云头,像睡著了一般。 火紅的嫁衣襯著肌膚如雪淫半。 梳的紋絲不亂的頭發(fā)上溃槐,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音科吭,去河邊找鬼昏滴。 笑死猴鲫,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谣殊。 我是一名探鬼主播拂共,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼姻几!你這毒婦竟也來了碾局?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤鸵赖,失蹤者是張志新(化名)和其女友劉穎赌朋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體豁陆,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡柑爸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了盒音。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片表鳍。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祥诽,靈堂內(nèi)的尸體忽然破棺而出譬圣,到底是詐尸還是另有隱情,我是刑警寧澤雄坪,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布厘熟,位于F島的核電站,受9級特大地震影響维哈,放射性物質(zhì)發(fā)生泄漏绳姨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一阔挠、第九天 我趴在偏房一處隱蔽的房頂上張望飘庄。 院中可真熱鬧,春花似錦购撼、人聲如沸跪削。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碾盐。三九已至,卻和暖如春揩局,著一層夾襖步出監(jiān)牢的瞬間毫玖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留孕豹,地道東北人涩盾。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像励背,于是被迫代替她去往敵國和親春霍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容