【Android 開發】: Android 訊息處理機制之三: Handler 中 sendMessage() 原始碼剖析
閱讀此文,請先閱讀以下相關連線:
sendMessage()的幾種過載方法的使用方式的不同以及它們的原始碼的剖析.
通過前面幾講的內容,我們知道Android不但可以使用非同步任務處理多執行緒的問題,還可以通過Handler + Message + Thread 的方式進行,例如更新主執行緒UI等.整個架構圖如下所示:
下面我就通過一個Demo來學習一下Handler中sendMessage()方法的幾種過載方法,以及跟蹤它們的原始碼來知道它們之間的關係。
1. 使用Handler中的sendEmptyMessage(int what)方式來發送訊息.
點選按鈕傳送訊息,在Handler中做訊息的處理。只發送一個帶有what屬性的訊息
。在Handler中將訊息取出列印在控制檯中case R.id.button1: new Thread(new Runnable() { // 檢視Handler的api,它有幾種sendMessage()的方式 @Override public void run() { // 使用public final boolean sendEmptyMessage (int what) mHandler.sendEmptyMessage(3); } }).start(); break;
[分析原始碼]:// Handler 可以接受或者傳送訊息,從訊息佇列中提取訊息,使用者更新UI的操作 protected static Handler mHandler = new Handler() { @Override public void handleMessage(android.os.Message msg) { System.out.println("--> what: " + msg.what); } };
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); }
它是呼叫sendEmptyMessageDelayed(what, 0),同時指定延時為0ms,繼續跟蹤它;
Message.obtain();程式碼上一講研究過了,它是Message物件池中獲取到Message物件,然後賦值what屬性值,接著呼叫sendMessageDelayed(msg, delayMillis),此時delayMillis是0ms.public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
在這裡做延時的處理,跟蹤原始碼或者檢視SystemClock的api文件知道SystemClock.uptimeMillis()是獲取系統從開機啟動到現在的時間,期間不包括休眠的時間,這裡獲得到的時間是一個相對的時間,而不是通過獲取當前的時間(絕對時間),android之所以要這樣設計的目的,待會兒會講解到。繼續跟蹤.public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
這裡就是Handler最終傳送訊息的動作,從這裡可以看出,所有的訊息都是在這裡進行入隊的操作的,當訊息佇列(MessageQueue)不為空的時候,指定訊息物件是本身,然後入隊,入隊成功後返回布林值sent,如果訊息成功的放置在訊息佇列(message queue)中的話,sent就返回為true,如果失敗則返回false.繼續跟蹤queue.enqueueMessage(msg, uptimeMillis).public boolean sendMessageAtTime(Message msg, long uptimeMillis) { boolean sent = false; MessageQueue queue = mQueue; if (queue != null) { msg.target = this; sent = queue.enqueueMessage(msg, uptimeMillis); } else { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); } return sent; }
這裡面就是MessageQueue在接受到一個訊息之後,根據傳送訊息的時間來進行隊內的輪轉計算,同時也會考慮到機子處於休眠狀態,阻塞情況的演算法。final boolean enqueueMessage(Message msg, long when) { if (msg.isInUse()) { throw new AndroidRuntimeException(msg + " This message is already in use."); } if (msg.target == null && !mQuitAllowed) { throw new RuntimeException("Main thread not allowed to quit"); } final boolean needWake; synchronized (this) { if (mQuiting) { RuntimeException e = new RuntimeException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); return false; } else if (msg.target == null) { mQuiting = true; } msg.when = when; //Log.d("MessageQueue", "Enqueing: " + msg); Message p = mMessages; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; // new head, might need to wake up } else { Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; needWake = false; // still waiting on head, no need to wake up } } if (needWake) { nativeWake(mPtr); } return true; }
【備註】:其實我們只要知道它是如何入隊的就可以了,至於入隊之後訊息已經推送到訊息佇列中去了,訊息在訊息佇列中的流程走向主要是作業系統在控制了.
【總結】:從這裡我們也可以知道傳送空訊息在底層上的實現並不是沒有訊息體,它還是會從訊息池中獲取訊息物件,賦值what屬性的
2. 使用Handler中的sendEmptyMessageAtTime(int what,long uptimeMillis)方式來發送訊息.
只發送一個帶有what屬性的訊息,並且在一個指定的時間內(單位是ms)去傳送。
case R.id.button1: new Thread(new Runnable() { // 檢視Handler的api,它有幾種sendMessage()的方式 @Override public void run() { // 使用public final boolean sendEmptyMessageAtTime (int what, long uptimeMillis) mHandler.sendEmptyMessageAtTime(3, 1000); } }).start(); break;
[原始碼分析]:檢視原始碼我們可以發現它也是呼叫路線與上訴類似: sendEmptyMessageAtTime(int what, long uptimeMillis) ---> sendMessageAtTime(msg, uptimeMillis)完成訊息的入隊.
3. 使用Handler中的sendEmptyMessageDelayed (int what, long delayMillis)方式來發送訊息.
[原始碼分析]: 檢視原始碼我們可以發現它的呼叫線路: sendEmptyMessageDelayed(int what, long delayMillis) ---> sendMessageDelayed(Message msg, long delayMillis) --> sendMessageAtTime(Message msg, long uptimeMillis) --> 完成入隊操作。case R.id.button1: new Thread(new Runnable() { @Override public void run() { /* * 使用public final boolean sendEmptyMessageDelayed (int what, long delayMillis) * 傳送帶有what屬性值的訊息,在延時3秒鐘後傳送 */ mHandler.sendEmptyMessageDelayed(3, 3000); } }).start(); break;
這裡通過原始碼我們可以知道sendEmptyMessageAtTime()與sendEmptyMessageDelayed()的區別就是延時在時間會做 "SystemClock.uptimeMillis() + delayMillis"的方式的處理,也就是獲得到系統啟動開機的時間到當前的時間(不包括休眠的時間) + 延時的時間。
【注意】:這裡Android為什麼要採用從系統開機到現在的時間來作為時間基準,博文後面會詳細講到,這裡先佩服一下Android開發工程師設計的縝密之處!
4. 使用Handler中的sendMessage (Message msg)方式來發送訊息.
傳送一個訊息到訊息佇列的對尾,它會在處理這個時間的執行緒中的handleMessage(Message),方法中被接受到並且處理。
[原始碼分析]:case R.id.button1: new Thread(new Runnable() { // 檢視Handler的api,它有幾種sendMessage()的方式 @Override public void run() { /* * 使用public final boolean sendMessage (Message msg) */ // 使用Message.obtain()獲取message物件 // Message msg = Message.obtain(); // 使用Handler獲取message物件,兩種獲取都可以 Message msg = mHandler.obtainMessage(); msg.arg1 = 1; msg.what = 3; msg.obj = "AHuier"; mHandler.sendMessage(msg); } }).start(); break;
可見程式碼中兩種獲取Message物件的方式是一樣的public final Message obtainMessage() { return Message.obtain(this); }
跟蹤sendMessage()的原始碼,他們呼叫路線:sendMessage(Message msg) ---> sendMessageDelayed(Message msg, long delayMillis) ---> sendMessageAtTime(Message msg, long uptimeMillis)完成訊息的入隊.
【總結】: 結合上面分析的內容,我們可以知道android 中傳送訊息不管是Message中的幾種過載的obtain()方式,還是Handler中的幾種過載的sendMessage最終都是通過Handler.sendMessage來發送的,而Handler中的幾種sendMessage()過載方法最終都會呼叫到sendMessageAtTime()方法來完成訊息的入隊操作。
[更新]--------------------------------------------------
在上面我們講到sendEmptyMessageAtTime()與sendEmptyMessageDelayed()的區別(其實底層還是sendMessageAtTime()與sendMessageDelayed()這兩種方法的區別)時候講到獲得當前時間的基準在這邊是採用系統開機時間到當前的時間(不包括休眠的時間),這個時間是通過SystemClock.uptimeMillis()來獲得的,而不是直接使用系統當前的時間?
前者是相對時間來計算,後者是絕對時間計算獲得,不管是絕對的還是相對,當前獲得的都是一樣的時間。也就是說這兩句是等效的,都是延時1秒將訊息加入列隊
msgHandle.sendMessageAtTime(msg, SystemClock.uptimeMillis()+1000); msgHandle.sendMessageDelayed(msg, 1000)
我自己的理解是: Android之所以用這種方式來計算時間而不是絕對的獲得當前時間的目的是因為Handler的處理過程始終都會受到阻塞,等待的一些操作。Handler是繫結執行緒用來處理訊息,難免會遇到阻塞,等待等情況,所以不應該是用絕對時間來處理,也就是說在睡眠的情況下,可能就是掛起狀態而不會去處理了,如果是絕對時間的話,那就是直接剝奪搶佔了。
那麼如果我要在11月30日凌晨1點執行的話,應該是 SystemClock.uptimeMillis() + (11月30日凌晨1點 - 當前的時間)轉換成毫秒) 不過這是理論上的值,它會考慮到手機的是否休眠然後再計算的。