Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android_Handler消息發送處理機制

android_Handler消息發送處理機制

編輯:關於Android編程

Handler

1. Handler 是什麼?

Handler 是 android 中消息處理機制。一個Handler 會對應一個 Thread 和 該 Thread 綁定的 MessageQueue,Handler 會將 Message 或者 Runnable 發送到 MessageQueue 中,通過 Looper 輪訓器的方式從消息隊列中輪訓消息,每次輪訓到的消息都交由 Handler 去處理這個消息。

2. Handler 的作用是什麼?

可以方便地將任務切換到 Handler 所在的線程中去執行。若是在子線程中進行耗時操作完成後需要更新 UI, 那麼就可以利用 Handler 消息機制的特性對主線程UI進行更新。前提就是 Handler 是在主線程創建。

3. Message消息

3.1 創建消息方式一

//默認構造
public Message() {
}

//創建Message對象
Message msg = new Message();

3.1 創建消息方式二

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                //sPool持有Message 池中的鏈表頭的Message
                Message m = sPool;
                sPool = m.next;//指向到下一個Message
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    //重置 Message 
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;
    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            //修改當前 sPool 的指向為當前回收的Message
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

每一個Message內部都有一個next屬性指向下一個 Message,這樣就構成了一個鏈表結構,而 sPool 是一個持有 Message 鏈表 頭部的引用,是一個 Message 類型的變量。每次通過obtain 方式獲取的 Message 之後 sPoolSize–,這種方式獲取 Message 好處在於重復利用這些消息,節約內存開銷。這裡需要注意的是在 5.2小節中,loop()的最後一段代碼:msg.recycleUnchecked();它是負責對這些消息進行回收操作的。

4. 消息隊列 MessageQueue 的工作原理

消息隊列適用於管理消息的,主要就是消息的插入和移除操作。內部的數據結構采用的是鏈表結構,鏈表的特點在於添加和移除比較方便。通過 Hanadler 發送的消息最終會調用 MessageQueue 中 boolean enqueueMessage(Message msg, long when) 將消息插入。

4.1 boolean enqueueMessage(Message msg, long when) 插入一條消息

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

往鏈表中添加一條數據,若當前鏈表沒有數據則該 Message 做為 head,若是有數據則比對所有的消息的 when 值,將Message 插入到指定的位置。

4.2 next()方法取出消息隊列中的消息

Message next() {
    ...
    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;
    }
}

next() 是個死循環方法,直到消息隊列中有消息,返回該消息結束循環。這裡為什麼需要使用死循環呢?因為消息隊列中即便有消息,但是由於每一個 Message 有一個 when 屬性,只有達到的 Message 的 delay 時間才會去執行當前消息,所以得通過死循環的方式知道該消息能夠去執行才取出這個消息。

5. Looper 的工作原理

中文名叫輪訓器,Looper 開啟輪訓之後,這個過程是一個死循環的過程,當消息隊列中有消息時,輪訓器會取出這個消息交給 Handler 去處理。在 Handler 創建時,會默認使用當前線程的 Looper 作為輪訓器,那麼它是如何去獲取當前線程的 Looper對象呢?這裡涉及到另外一個知識點,那就是 ThreadLocal 概念,它不是一個線程,它可以在不同線程中互不干擾的獲取或者存儲數據,可以通過 ThreadLocal 獲取不同線程中對應的 Looper,但是線程默認情況是沒有 Looper 的,需要使用Handler 就必須為 當前線程創建一個 Looper 對象。而在默認創建 Handler 的時候會默認使用主線程的 Looper,因為在 ThreadActivity中 main 方法已經創建好了 Looper 對象,所以在主線程使用 Handler 是不需要手動創建 Looper 的。

5.1 准備 Looper 對象

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
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));
}

5.2 開啟輪訓操作

public static void loop() {
    //判斷是否已經創建了 Looper 
    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;
    ...
    for (;;) {//阻塞式
        Message msg = queue.next(); // might block 在隊列中獲取消息
        if (msg == null) {// 消息隊列停止,沒有更多消息
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        //將輪訓到的消息進行分發,將任務切換到 Handler 所在的線程中去執行。
        msg.target.dispatchMessage(msg);
        ...
        msg.recycleUnchecked();//回收這個msg
    }
}

這個方法是阻塞式的,死循環的從 Looper 綁定的隊列中去獲取消息(queue.next()),直到消息隊列停止。當獲取到消息之後,直接獲取到消息然後將其分發出去,具體分發過程待會再分析。

5.3 主線程使用Looper 對象。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    ...
    Looper.loop();
}
public static void prepareMainLooper() {
    prepare(false);//創建一個 Looper,當前的 Looper 是在主線程中調用的
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();//在主線程的 Looper 賦值。
    }
}

因為在 ActivityThread main 方法執行了以上兩段代碼,這就表示應用啟動時就將主線程的 Looper 創建完畢了。所以直接之間創建Handler即可使用。

5.4 如何在子線程使用 Looper?

new Thread(){
    public void run() {
        Looper.prepare();
        Handler handler = new Handler();//handler 使用的就是上面創建的 Looper
        Looper.loop();
    }
}.start();

若是要在非主線程中去使用 Looper 就必須手動地去創建 Looper 對象。而創建 Looper 的方式是通過 Looper.prepare()實現, 底層調用的是 Looper.prepare(boolean) 創建一個 Looper 對象,在方法中判斷 ThreadLocal 中是否保存了當前線程的 Looper 對象,沒有的話,則創建一個 Looper 對象並且保存到 ThreadLocal 中的,,具體的 ThreadLocal 的知識點參考:
http://blog.csdn.net/lwj_zeal/article/details/52814245 【android_ThreadLocal】。創建的同時會為 Looper 創建一個 MessageQueue 消息隊列。該方法的作用只是創建一個 Looper 對象,並且保存起來,此時輪訓器還沒有開啟循環操作。通過 Looper.loop() 開啟消息輪訓操作。

5.5 停止消息輪訓操作

/**
 * Quits the looper.
 * 

* Causes the {@link #loop} method to terminate without processing any * more messages in the message queue. *

* 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. *

* 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. *

* * @see #quitSafely */ public void quit() { mQueue.quit(false); }
/**
 * Quits the looper safely.
 * 

* Causes the {@link #loop} method to terminate as soon as all remaining messages * in the message queue that are already due to be delivered have been handled. * However pending delayed messages with due times in the future will not be * delivered before the loop terminates. *

* 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. *

*/ public void quitSafely() { mQueue.quit(true); }

Looper 中提供了兩個方法去停止輪訓操作,quit()的方法注釋中可以看出該方法是unsafe的,因為quit()被調用之後,那麼其他沒執行完的消息就沒有辦法被執行,而 quitSafe() 的注釋表示該方法是安全的,它會等到所有的消息執行完畢之後采取停止輪訓操作。

6. Handler 的內部實現原理

Handler 的職責就是負責往消息隊列中發送和處理消息的。對於發送消息而言, Handler 中有一些列的 post 和 sendMessage 方法。對於處理消息 Hadnler 是通過 handlerMessage 去處理的。

6.1 通過 sendMessage 的方式往 MessageQueue 插入消息

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

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;//looper綁定的消息隊列
    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);
}
//sendMessageDelayed 最終會調用 enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;//指定message的target為當前handler對象
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);//交給 MessageQueue去處理
}

5.2 通過 post 的方式往 MessageQueue 插入消息

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
//將一個Runnable轉化為一個 Message 對象
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;//保存到callback屬性的,之後分發時需要拿其做判斷使用
    return m;
}

不管是 post ,postDelay 還是sendMessage,sendMessageDelayed的方式去發送消息,底層都是調用 sendMessageDelayed 去實現。從 getPostMessage 可以知道其實發送一個 Runnable 對象實際上也是將其轉化為一個 Message 對象去發送的。sendMessageDelayed 最終會調用 enqueueMessage方法,在這個方法 msg 做一些賦值操作,例如給 msg 綁定一個target,就是說當前這個需要發送的消息它有由這個 Handler 發出的,設置 target 的原因就是方便待會 MessageQueue 知道具體分發給哪個一個Hadnler 去處理這個消息。而代碼最終會走到queue.enqueueMessage(msg, uptimeMillis);這個queue是在哪裡賦值的呢?代碼回到Handler的構造中可以看出,其實 queue 就是 Looper 中綁定的 MessageQueue 對象,在 Handler 中也保存了一份引用。而 MessageQueue 插入一條消息剛才已經分析了。

5.3 處理消息

消息是通過 輪訓器輪訓出來的,也就是通過 loop 中去不斷的調用 messagequeue中的next 方法獲取一條 Message 消息,代碼回到4.2小節中,獲取到的 Message,然後調用 msg.target.dispatchMessage(msg); 將輪訓到的消息進行分發,將任務切換到 Handler 所在的線程中去執行。這裡的msg.target 就是發送消息的 Handler 對象。

5.3.1 handler 是如何通過 dispatchMessage 去處理不同的消息的?

//根據不同的處理方式分發這個消息
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//第一種方式:post 方式
        handleCallback(msg);
    } else {
        if (mCallback != null) { //第二種方式:通過具有 Callback 參數來創建的 Handler
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//第三種方式:創建Handler時沒有指定 Callback 屬性。
    }
}
//Handler 內部的一個接口
public interface Callback {
    public boolean handleMessage(Message msg);
}
//默認的構造,沒有給callback賦值
public Handler() {
    this(null, false);
}

//具有 Callback 的構造來創建 Handler
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class 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;
}

判斷 msg.callback 是否為 null,這個 callback 剛才在 post 方式發送一個 Runnable 對象時,通過 getPostMessage(Runnable) 轉化時,將 Runnable 作為 Message 的 callback 屬性。也就是說若是 msg.callback 不會null,表示這個消息就是通過 post 方式實現的。若為null,分為兩種方式,一是通過通過具有 Callback 參數來創建的 Handler,那麼就會會調用 Callback#handleMessage(),二以默認的方式來創建的Handler,因此會調用 Handler#handleMessage()方法。因為所有的handleMessage都是實現,具體哪種方式處理消息,用戶只需要去覆寫 handleMessahe 即可。好了,這就是我對 Handler 消息處理機制的理解。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved