跳到主要內容

Android基礎轉貼,JAVA side

Source:  http://milochen.wordpress.com/2011/03/25/understanding-android-os-src-looperhandler-message-messagequeue/

(如有任何不清楚或不懂之處,歡迎到我的居家(G+) 討論 http://gplus.to/gplus2 討論喔)
Hi All:
Handler, Message, Looper, MessageQueue 是 android.os 中的class
也是深度開發 Application 時,必須具備的基本觀念,若清楚了解,
便可運用的當。
因為網路有太多模糊不清的文章,大家說法看起來也都不太一樣,
很容易讓人猜東猜西,想東想西的。至於,不想瞎猜的話,就不如直接把source code都讀懂吧。
因此本篇文章,目地在於,快速引導大家快速「正確」的了解,重點在於「正確性」
並且透過靜態 trace code  的方式,跟大家解釋它 source code 的運作原理。
因此,對於四個 class沒信心的時候,
就直接將文章看下去,文章會完整交代 trace source code的部份。
關於這四個 class 的結論:
========================================================
整個Handler, Message, MessageQueue, Looper 它們四個 class 只有一個共同的目標
就是讓程式碼,可以丟到其它 thread 去執行。這麼作有什麼好處呢 ??
例如 android 的 GUI 元件是 thread safe的 (意思是,元件的使用,無法multi-thread執行)
Activity 的畫面顯示是由 UI Thread所負責的,若是你寫了 mutlti-thread 程式時
又想更新畫面,就必須要將 Thread 內部的一段程式碼,交由 UI Thread 來執行才行。
OK, 上面四個 class 的共同目地已經說明完畢了,那麼這四個 class有其分工方式。
因此每個 class 的設計又有不同目地。說明如下 …
Handler 的目地,在於提供 callback function,預其給其它 Thread 作執行
但Handler又要如何 transfer 至其它 Thread 呢 ?  於是有了 Message
Message 的目地,將 Handler 包裝起來,傳送給其它 Thread
但是同時有多條 thread 不斷的在系統中傳遞 Message 那麼如何緩衝呢 ?
MessageQueue 的目地,是為了讓 Message 能夠作緩衝,好讓Message先暫存起來。
因此,當Message 已經被放在其它 Thread上的MessageQueue 之後,
它裡面包著 Handler,而 Handler上的 callback function 總得有人來執行吧 ??
Looper 的目地 :
就是為了將 Message 由 Thread 所對應的 MessageQueue 取出來,並且拿出 Handler
來執行它上面的 callback function.
當 Looper.java 中的 loop() 被呼叫起來之後,它就是在反覆作這件事
不斷將Handler由Message拆包出來,並且執行Handler上的callback function。
                                                                               
======================================================================
以上,已經將這四個class的關係完整說明了。看到這邊您還有疑慮嗎 ?
接下來小弟就直接講 trace source 的部份,
教你快速 trace 懂這些 code,迅速驗證出這四個 class 的用途。
以下開始 trace source code .. Follow Me ^____^
Looper 中的 mThread, mQueue 只有在 Ctor 建立,並且”不會再更改”
mThread = Thread.currentThread() //紀綠此Looper由那條Thread建立
mQueue = new MessageQueue() //每個Looper只有唯一的Queue
主要的執行函式為
Looper.java: loop()  {
  MessageQueue queue = myLooper().mQueue //取得CurrentThread下Looper的MsgQueue
  while(true) {
    Message msg = queue.next() //跳到msg一個message
    msg.target.dispatchMessage(msg)
    //target 被almost設定的方式,是透過Message.obtain(Handler h)設 h 為target
    msg.recycle(); //In Message class, 只有recycle()與obtain() 作sync同步
  }
}
                                                                               
上面程式中,所提到的東西,在以下深入探討。
(1) dispatchMessage(msg) 是如何重要呢 ?
它呼叫 Handler 上的 handleMessage().
———————————————
PS: 一般來說,我們會寫個 EHandler extends Handler,
並且重寫handleMessage()function 好讓 Handler 上的 handlerMessage()
被 UI Thread呼叫,來更新畫面。
——————————
(2) 至於 loop() 是如何被使用的呢 ?
typical example 大約是這樣子的
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();
}
類似的 typical example 在 Android 系統中的 ActivityThread.java :: main()
public static final void main(String[] args) {
  Looper.prepareMainLooper();
  ActivityThread thread = new ActivityThread();
  Looper.loop();
}
                                                                               
額外話 …
此範例trace下去將發現, Looper.mMainLooper 變數被設定為
(Looper)sThreadLocal.get()
許多重要的 android source code 皆會透過 getMainLooper() 函數取出
Looper.mMainLooper
(3) msg.target 是個 Handler 類別,  又是從何而來的呢 ?
直接copy高煥堂網路文章中的example code過來 …
講義摘錄之28:Anrdroid 的Message Queue(3/3) 的example code如下
class AnyClass implements Runnable {
 public void run() {
         Looper.prepare();
         h = new Handler(){
               public void handleMessage(Message msg) {
                  EventHandler ha = new EventHandler(Looper.getMainLooper());
                    String obj = (String)msg.obj + ”, myThread”;
                     Message m = ha.obtainMessage(1, 1, 1, obj);
                     ha.sendMessage(m); //sendMessage的原理,請見(4)的說明
         }
       };
  Looper.loop();
  }
}
                                                                               
我們直接由此來作解釋,
追蹤當中的 obtainMssage 可發現 target的由來。原理如下
Handler.java: Message obtainMessage(int what, int arg1, int arg2)
Message.java:
static Message obtain(Handler h, int what, int arg1, int arg2) {
        Message m = obtain();
        m.target = h;  // 這邊就是 msg.target 的由來
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
}
而 Message m = obtain() 是執行下面這段程式
    public static Message obtain() {
        synchronized (mPoolSync) { //與 recycle() 共用 mPoolSync
            if (mPool != null) {
                Message m = mPool;
                mPool = m.next;
                m.next = null;
                return m;
            }
        }
        return new Message();
    }
因此你可從 sample code 知道
Handler 呼叫 obtainMessage 的時候,其實是由 mPool 取出 Message來
將 msg.target 設為原 Handler. 並且設定好 what, arg1, arg2 等參數
好讓 Looper 來執行它 …
———————————————–
(4) 接續 (3) 中的 example code, 它的 sendMessage() 又作了什麼事呢 ?class AnyClass implements Runnable {
 public void run() {
         Looper.prepare();
         h = new Handler(){
               public void handleMessage(Message msg) {
                  EventHandler ha = new EventHandler(Looper.getMainLooper());
                    String obj = (String)msg.obj + ”, myThread”;
                     Message m = ha.obtainMessage(1, 1, 1, obj);
                     ha.sendMessage(m); //sendMessage 作什麼事呢?
         }
       };
  Looper.loop();
  }
}
                                                                               
以這個例子,簡單來說,Looper.getMainLooper() 會回傳一個ActivityThread的
Looper object, 即為 Looper.mMainLooper. 而mMainLooper有自己的mQueue
==================================================
在此穿插一小段 sendMessage() 的作用
Handler本身在暫存一個mQueue, 當Handler的成員函數sendMessage 被呼叫時,即是把帶著 Handler ha 的 Message m,enqueue 至 Handler自己存存的mQueue中。而mQueue的設置,通常是在建構子就被決定好的。因此你得特別注意 Handler 的建構子。
==================================================
像上面的例子中 sendMessage 即是把帶著 Handler ha 的 Message m,enqueue 至 mMainLooper.mQueue
sendMessage 即是把帶著 Handler ha 的 Message m,enqueue 至 mMainLooper.mQueue
好讓 mMainLooper.loop() 函數把 m 由這個 mMainLooper.mQueue取出(取出時名為msg)
來dispatchMessage,因此就會執行到 msg.target.handleMessage(0
也就是 exmaple code 中的 ha.handleMessage();
因為在 Handler ha = new Handler(Looper looper) 這 Ctor 時,
ha.mLooper = looper 便被紀錄下來,而且ha.mQueue=looper.mQueue也被紀錄下來
也就是 looper.mQueue    (PS:若是用 new Handler(), 則looper取Looper.myLooper())
當 ha.sendMessage 被執行時,便將 msg 塞入 looper.mQueue
—————————————————————–
                                                                               
(5) 所以整個 Looper, Message, MessageQueue, Handler 的運作原理是什麼?
因此你的 ha = JohnHandler(MaryLooper) 就像信紙一樣,上面寫著Dear MaryLooper:
上面寫著要執行的程式碼 handleMessage(msg)
透過信封(Message),以Handler.java 的 sendMessage 將信紙(Handler)傳出去
傳到 MaryLooper 的個人信箱 MessageQueue (也就是MaryLooper.mQueue)
在 MaryLooper 中,有個有個固定的 loop() 會不斷被執行
(假設當初宣告此looper的thread, 有 去running 此 function loop 的話 )
那麼 loop 會收到 Message msg. 而 msg.target (Handler) 即為 JohnHandler這封信紙
看著 JohnHandler 上有 handleMessage() 的信紙內容,
故對 Handler 執行了 dipsatchMessage(),因此執行了 JohnHandler
當初信紙內容的交辦事項。
======================================================================
若有任何問題,歡迎一起討論喔
Best Regard,
Milo Chen

留言

這個網誌中的熱門文章

[UML]學習筆記-循序圖型(Sequence Diagrams)-8

定義 如果說之前提到的物件圖型是描述一個時間點的系統運作的樣子(memory snapshot),那麼循序圖型就是表示系統要做某件事情的那段時間內,運作的樣子(一個連續的過程)。 循序圖的重點是在描述一件事情,以及系統要完成這件事情的一連串動作,也是一種軟體系統運作的動態圖型。 上圖就是一個循序圖型,而有下圖的解析。循序圖會以做的動作(任務)出發,並列出所以參與到的物件/類別。接下來由上到下就是動作與物件彼此間的順序運作關係。下圖可以清楚展示出物件與類別/任務的互動關係。 循序圖組成的元素 有以下元素組成 -物件節點(Object Node) -生命線(Lifeline) -活化區塊(Activation Box) -訊息(Message) -內部訊息 -解構物件 -迴圈 要建構循序圖,必須要確定要描述的任務為何。 確定任務後,就要列出任務會用到的物件(即為物件節點)。 物件節點 ***************************************************** public class TestThermos {      public static void main(String[] args) {          HotWaterContainer h = new HotWaterContainer( 2 );          CoolWaterContainer c = new CoolWaterContainer( 50 );          ThermosGui g = new ThermosGui();          Thermos t = new Thermos(h, c, g);      } } ******************************************************* 上面的程式片段就是在描述一件任務,那件任務就是模擬一個冷熱水開飲機。 一開始先畫出類別和物件的節點,可以很清楚地分出類別和物件的區別。 最前面要是為一個主程式或是匿名物件的話可以用Use case diagram的小人圖來代替。

[UML]學習筆記-狀態圖型(Statechart Diagrams)-10

定義 狀態圖型主要會應用到軟體系統中,某項任務的生命週期。任務的生命週期中,會有不同的狀態,藉由不同狀態的檢視,可以去檢查任務是否有未考慮的情況或是邏輯的謬誤。 上圖就是表示一個執行緒的生命週期,還有它本身的狀態變化。 另外,狀態的數量必須是有限的。 組成元素 狀態節點(State node) 狀態圖型主要使用兩個特定的符號來表示生命週期的開始與結束。 初始狀態(Initial State),使用實心黑色的圓形。 結束狀態(Final State),使用實心黑色的圓形,外層再包一圈空心的圓形。 除了開始和結束外,狀態節點就是表示生命週期的某一種狀態,它的節點內容包含兩個部分。 名稱區格(Name Compartment) 內部轉換區格(Internal Transition Compartment) 通常內部轉換區格會因簡化而省略。 下圖就是含有內部轉換區格的狀態圖型 接下來仔細解釋名稱區格以及內部轉換區格的定義 名稱區格 名稱區格的文字表示生命週期中的一個狀態,UML的規定並非必填,如果沒有填寫就稱為匿名狀態。 內部轉換區格 內部轉換區格主要用來表示狀態節點內部的轉換狀況,這個地方使用四個標籤來說明進入狀態節點後到離開狀態節點時,狀態節點內會做哪些動作。 entry: 進入狀態節點的動作 exit: 離開狀態節點時的動作 do: 停留在此狀態節點時執行的動作 自訂標籤: 使用下列格式來自訂標籤與動作 其實跟其他標籤格式差不多,除了標籤名稱外,就是有參數可以去填寫。 轉換(Transition) 在狀態圖型中,兩個狀態節點間的標示就稱為「轉換」,用來表示狀態節點間如何轉換過去的。 轉換標示的格式如下。 事件名稱: 通常會是物件/類別的方法名稱 參數: 可選擇性的宣告,就是傳遞給事件的參數 條件: 可選擇性地宣告,用來表示狀態轉換的條件 這邊用個修改紀錄的的狀態圖來做例子。 從這個例子可以看到[update record]的狀態可以經過update的事件後,來到達下一個狀態[record updated] 子狀態(Sub-State) 從之前開飲機的例子來說,狀態圖可以如下圖。

[UML]學習筆記-活動圖型(Activity Diagrams)-11(全系列結束)

定義 活動圖型是用來顯示軟體系統中特定的活動情形,和其他圖型最大的差異,就是它專注在「活動」上面,而不會去理會物件、類別相關的問題。 因此,活動圖型只關心活動的開始、過程、與結束,使用一般流程圖的方式來繪製就可以了。 譬如一個偵測溫度到自動加溫的活動流程。 活動圖型的用途 針對循序圖型中比較複雜的訊息傳遞加以說明 顯示狀態圖裡面較複雜的狀態轉換事件 說明合作圖型裡面的訊息 圖型組成的元素 活動圖型由下列的基本元素組成。 狀態與活動(State and Activity) 轉換(Transition) 分支(Branch) 分歧與結合(Fork and Join) 水道(Swimlane) 狀態與活動 狀態元素其實和狀態圖型中的開始與結束狀態是相同的,而活動則是圓角矩形來表示。 上圖如果轉成程式碼會是下面的樣子 轉換 轉換主要是一個箭號,用來連結活動圖型中的狀態與活動。只有在分支出來的箭頭才會在上面加上說明或是條件。 分支 分支會根據判斷式而導向不同的活動,分支後的轉換就需要加上說明或是條件。 水道 活動圖型中,活動流程可能很單純在一個方法或類別中就完成了,但是也有可能是由不同的角色去一起完成流程。 下圖是一個聊天的程式。 圖中,以Mary發訊息給[Chat Server]的流程來說,就需要使用不同的「水道」來區分不同角色所負責的活動。 水道也可以使用行為者標記來表示角色,這樣可以讓活動圖型更加清楚。像下圖就是是客戶去購買產品的活動流程。裡面的角色就包含了[客戶]、[服務中心]、以及[訂單系統] 分歧與結合 以聊天程式的客戶端來說,其實有同時執行兩個執行緒,一個是sender,用來傳送訊息,一個是receiver,用來接收chat server回傳的訊息,因此可以使用Fork來顯示這種同時進行的活動 在圖中,分岐的表示是使用一條粗黑線來分成兩個執行緒所做的事情。 結合的部分,就是要把分歧的活動再次結合成一個活動的表示方式。 上圖的計算機就是一個結合的典型例子,它裏頭的活動運作的加減乘除活動之後,均會顯示在一個統一的活動(螢幕顯示)上。 程式碼的