Android Handler Looper Message 詳細分析

threezj發表於2016-01-24

Android非同步訊息機制架構

Android非同步訊息處理架構,其實沒那麼複雜。簡單來說就是 looper 物件擁有 message queue ,並且負責從 message queue 中取出訊息給 handler 來處理。同時 handler 又負責傳送 message 給 looper ,由 looper 把 message 新增到 message queue 尾部。就一個圈兒。下面給出圖解,應該不難吧?

Android Handler Looper Message 詳細分析

所以很明顯 handler 和 looper 是來聯絡在一起的。需要說明的是,多個 message 可以指向同一個 handler ,多個 handler 也可以指向同一個 looper 。

還有一點很重要,普通的執行緒是沒有 looper 的,如果需要 looper 物件,那麼必須要先呼叫 Looper.prepare() 方法,而且一個執行緒只能有一個 looper 。呼叫完以後,此執行緒就成為了所謂的 LooperThread ,若在當前 LooperThread 中建立 Handler 物件,那麼此 Handler 會自動關聯到當前執行緒的 looper 物件,也就是擁有 looper 的引用。

Looper

簡而言之, Looper 就是一個管理 message queue 的類。我們直接來看下原始碼好了。

public class Looper {
    ......

    private static final ThreadLocal sThreadLocal = new ThreadLocal();

    final MessageQueue mQueue;//擁有的訊息佇列

    ......

    /** 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()}.
 */
    //建立新的looper物件,並設定到當前執行緒中
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    ·····
    /**
 * Return the Looper object associated with the current thread. Returns
 * null if the calling thread is not associated with a Looper.
 */
    //獲取當前執行緒的looper物件
    public static final Looper myLooper() {
        return (Looper)sThreadLocal.get();
    }

    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

    ......
}

由原始碼可知, looper 擁有 MessageQueue 的引用,並且需要呼叫 Looper.prepare() 方法來為當前執行緒建立 looper 物件。Android註釋很詳細的說明了這個方法。

This gives you a chance to create handlers that then reference

this looper, before actually starting the loop. Be sure to call

loop() after calling this method, and end it by calling

quit()

也就是說只用在呼叫完 Looper.prepare() 之後,在當前的執行緒建立的 Handler 才能用有當前執行緒的 looper 。然後呼叫 loop() 來開啟迴圈,處理 message .來簡單看一下 loop() 的原始碼

public static void loop() {
        final Looper me = myLooper();//獲取looper物件
        if (me == null) {
            //若為空則說明當前執行緒不是LooperThread,丟擲異常
            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;
            }

            // This must be in a local variable, in case a UI event sets the logger
            //列印log,說明開始處理message。msg.target就是Handler物件
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            //重點!!!開始處理message,msg.target就是Handler物件
            msg.target.dispatchMessage(msg);

            //列印log,處理message結束
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
            .....
        }
    }

很明顯,就是一個大的迴圈,不斷從訊息佇列出取出訊息。然後呼叫一個很關鍵的方法 msg.target.dispatchMessage(msg) 開始處理訊息。 msg.target 就是 message 對應的 handler .其實我一直在重複文章開頭的概念。 looper 物件管理 MessageQueue ,從中取出 message 分配給對應的 handler 來處理。

在分析 msg.target.dispatchMessage(msg) 方法之前,先讓大家瞭解下 message 和 handler

Message

Message 就是一些需要處理的事件,比如訪問網路、下載圖片、更新ui介面什麼的。 Message 擁有幾個比較重要的屬性。

  • public int what 識別符號,用來識別 message
  • public int arg1,arg2 可以用來傳遞一些輕量型資料如int之類的
  • public Object obj Message 自帶的Object類欄位,用來傳遞物件
  • Handler target 指代此 message 物件對應的 Handler

如果攜帶比價複雜性的資料,建議用 Bundle 封裝,具體方法這裡不講了。

值得注意的地方是,雖然 Message 的構造方法是公有的,但是不建議使用。最好的方法是使用 Message.obtain() 或者 Handler.obtainMessage() 能更好的利用迴圈池中的物件。一般不用手動設定 target ,呼叫 Handler.obtainMessage() 方法會自動的設定 Message 的 target 為當前的 Handler 。

得到 Message 之後可以呼叫 sendToTarget() ,傳送訊息給 Handler , Handler 再把訊息放到 message queue 的尾部

Handler

public Handler(Looper looper, Callback callback, boolean async) {
       mLooper = looper;
       mQueue = looper.mQueue;
       mCallback = callback;  //handleMessage的藉口
       mAsynchronous = async;
   }

上面是 Handler 構造器,由此可知,它擁有 looper 物件,以及 looper 的 message queue 。

要通過 Handler 來處理事件,可以重寫 handleMessage(Message msg) ,也可以直接通過 post(Runnable r) 來處理。這兩個方法都會在looper迴圈中被呼叫。

還記得剛在loop迴圈中處理資訊的 msg.target.dispatchMessage(msg) 方法。現在我們來看下這個方法的原始碼。

public void dispatchMessage(Message msg) {
    //注意!這裡先判斷message的callback是否為空,否則就直接處理message的回撥函式
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //正是在這呼叫我們平常重寫handleMessage
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

沒錯,正是在這個方法中呼叫了 handleMessage(Message msg) 。值得注意的是,再呼叫 handleMessage(Message msg) 之前,還判斷了message的callback是否為空。這是為什麼?

很簡單,這就是 post(Runnable r) 方法也能用來處理事件的原因。直接看它的原始碼。我就不贅述了。相信已經很清晰了。

public final boolean post(Runnable r)
{
    //獲取訊息併傳送給訊息佇列
    return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    //建立訊息,且直接設定callback
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

總結

現在一切都弄清楚了。先呼叫 Looper.prepare() 使當前執行緒成為 LooperThread , Looper.loop() 開啟迴圈之後,然後建立 message ,再由 Handler 傳送給訊息佇列,最後再交由 Handler 處理。下面是官方給出的LooperThread最標準的用法。

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

最後的最後,再提一下,平時在說的主執行緒也就是UIThread,也是一個LooperThread。這也是為什麼我們能在子執行緒中傳送訊息,然後在主執行緒中更新ui。因為 Handler 是在主執行緒建立的,所以 Handler 關聯的是主執行緒的 looper ,最後給一個更新ui的例項。

 private static Handler handler=new Handler();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.message_activity);  
    new Thread(new Runnable() {                    
        @Override
        public void run() {
            // tvTest.setText("hello word");
            // 以上操作會報錯,無法再子執行緒中訪問UI元件,UI元件的屬性必須在UI執行緒中訪問
            // 使用post方式設定message的回撥
            handler.post(new Runnable() {                    
                @Override
                public void run() {
                    tvTest.setText("hello world");                        
                }
            });                                
        }
    }).start();
}

相關文章