TAG:
http://hpc.ee.ntu.edu.tw/~sflin/java/Event-Delegation%20Model_1.htm *前言 ======= 會想寫這篇文章,主要是因為最近實驗室有個計劃,必須Porting Java AWT到一個Embedded Window System,這逼得筆者非得去Trace JDK的AWT Package,而在Trace的過程中,以前一直覺得不太清楚 的Java Event運\作模式,現在終於獲得解決,如果各位有留意過一 些市面上的Java書籍,不管是講Java 1.0的Event Model或是Java 1.1之後的”Event-Delegation” Model(”Source-Listener” Model),大部分都只會提到Event該怎麼用(Event的用法),而對 於Java Event的來源和底層的原理卻是少有著墨。而本篇文章算 是筆者個人的工作心得,重點在於探討”Event的來源”以及” Event dispatch的流程”,在此提出來跟大家分享,希望對各位 正在學Java或已經是Java高手的讀者能有所幫助,當然,非常歡 迎各位讀者來信指教,筆者將會很樂意與您一起切磋求進。 *初探Java Event Model ======================= 首先必須跟各位讀者聲明的是,由於本篇的主角乃Java 1.1之後的 Event Delegation Model(事件委派模式),因此舊有的Java 1.0 Event Model在此將不做任何討論(文後若有出現Java Event Model ,都是指Delegation Model)。再者,本篇文章重點在於探討Event 的實際運\作方式,不在說明個別Event的使用方法,所以遇到關鍵 處大多只是舉例說明,點到為止,至於個別Event的使用方式,大 部分的Java書籍都會提到,筆者就不再贅述。 本篇文章的讀者必須要有一點Java Event Model的基礎,為了讓不 太清楚的讀者也能夠有繼續一探究竟的基礎,所以筆者將以簡短的 篇幅,概略性地描述Java Event-Delegation Model,當然,如果您 早已經熟悉這套Java Event Model,那麼這個單元您可以跳過不看。 學習Event Delegation Model,基本上要把握住兩個基本觀念: 事件的類別 ---------- Event Delegation Model定義了許多新的事件類別(主要被放在 java.awt.event這個package),而事件類別的階層關係如圖1所示。 所有事件類別均為java.util.EventObject這個類別的子類別,而這 個類別中有個重要的成員-source(型態為Object),它代表了發生事 件的元件,在Event Delegation Model中就是靠它來找到發生事件 的來源物件,是不可或缺的成員之一,可由getSource()取得發生事 件的元件。 圖1 而緊接著繼承下來的是java.awt.AWTEvent,此類別為java.awt.event package中所有事件類別的超類別。而這個類別中同樣有個相當重要 的成員-id(為整數型態),它代表了Event的Type,如MouseEvent就可 以表示七種不同型態的滑鼠事件如下: MOUSE_PRESSED、MOUSE_RELEASED 、MOUSE_CLICKED、MOUSE_ENTERED、MOUSE_EXITED、MOUSE_MOVED、 MOUSE_DRAGGED。在Event dispatch的過程中,就是由Event的Type來 決定最後的Event-Handler,可由getID()取出該Event的Type。由此 可知,所有java.awt.event package中所定義的事件類別都具有兩個 最基本也是最重要的屬性-source和id。 委派的機制 ---------- 在說明事件委派機制之前,必須先弄清楚兩個重要的角色分別是 Event-Source跟Event-Listener。Source代表了發生事件的來源物件, 可能是一個Button或是Frame…,而Listener則代表了事件委派者,也 就是被Source委託來處理Event的被委派者。當某個Source有Event發 生時,Source本身會先收到該Event,接著再把Event交給對應的被委 託者(事件委派者)來處理,所以用來處理Event的Event-Handler必須 被實作在事件委派者。 由圖2中可以看出Source跟Listener之間的關係,而且Source是以 Multicast的方式把Event送到各個Listeners手上; 換言之,在Source 發生的Event,可以交由多個事件委派者來處理(這是Event Delegation Model的特點之一)。 圖2 再來必須要知道的是,Source跟Listener之間的委託關係是如何建立 的呢?建立的方式很簡單,只要透過Source所提供的addXXXListener (ListenerClass) (Ex: addActionListener)並傳入對應的ListenerClass 物件[此物件必須實作XXXXListener的介面,否則無法通過編譯],便可 以讓Source和Listener建立委託關係。通常Source會有某些特定可以 處理的Events(例如Button可以處理ActionEvent),只要是Source可以 處理的Event,Source中便會有對應的addXXXListener來提供建立委託 關係的管道,當然相對的removeXXXListener便是解除委託關係的方法 。再次強調,前面所謂的ListenerClass物件,指的就是implements XXXListener介面的物件,每個java.awt.event package中所定義的 Event都會有對應的Event Listener。 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 以下便是一個簡單的例子(處理ActionEvent): import java.awt.event. *;public class ButtonHandler implements ActionListener{ public void actionPerformed(ActionEvent e){ //實作之Event-Handler System.out.println(“Action OK!”); } } ----------------------------------------------------------- import java.awt.* ;public class MyButtonTest{ public static void main(String args[]){ Frame f = new Frame(“Hello”); Button b = new Button(“test”); b.addActionListener(new ButtonHandler()); //建立委託關係(註冊) f.add(b, BorderLayout.CENTER); f.pack(); f.setVisible(true); } } ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 最後針對Source以及Listener做個小結論: Event-Source |負責接收Events,再dispatch到已經註冊過的listener objects。 |所以基本上Event Source必須具備註冊的功能(by addXXXListener) Event-Listener |負責實作出對應之Event Handlers。必須implements java.awt.event |package中的Listener interface。 *Event從何而來? 往哪裡去? 聰明的讀者們,相信你們第一次使用Java寫GUI程式時,一定跟筆者 同樣有個疑問,那就是Java的Event究竟從何而來?畢竟,事件驅動是 GUI程式的基本精神之一,然而卻由於Java本身是直譯式(interpret) 語言,大部分Java語言底層的介面都交由Java虛擬機器(Virtual Machine)這個幕後的大黑手來處理,所以我們從Java的Program(.java) 中,看不到任何像傳統寫Window程式(EX:Win32 SDK)所必須去handle 的Message Loop。當然這對Programmer而言是件好事(因為複雜度減 少),但是追根究底,Java GUI程式的底層,還是會有個Message Loop ,從System Queue或Application Queue中捕捉Message,並一路包裝 到Java這一層來處理。而這就是本篇文章所要探討的主題之一。 我們知道在Win32系統中,當我們執行某個Application(GUI程式), 系統背後會提供這個Application可以使用的System/Application Queue,程式會不斷到這些Queue來GetMessage(不管是系統或是其他 應用程式送過來的訊息),若發現訊息,就處理之;若沒有訊息,則 把控制權交還給系統。那Java呢? Java的Event Model是不是也有 類似的機制呢?答案是肯定的! 由於java.awt.event 這個package位在java.awt的下一層目錄,所 以我們直接到JDK(必須1.1以上的版本,筆者採用的版本為JDK1.1.6) 所附的Class Library Source中的java/awt這個目錄去瞧瞧,果然 ,我們看到了一個叫做EventQueue的類別,從這個類別中我們發現 了兩個Key Points: 1.EventQueue中有個"queue"的私有資料成員,其宣告如下: private EventQueueItem queue; 再來我們看看EventQueueItem的類別定義: class EventQueueItem { AWTEvent event; int id; EventQueueItem next; EventQueueItem(AWTEvent evt) { event = evt; id = evt.getID(); } } EventQueueItem中的event跟id不就是Java Event Model中,所有 Event類別 最基本且最必要的資料嗎!而EventQueue中的queue則 是用來存放所有被丟到 Java EventQueue的Events。 2.在EventQueue的建構子當中(如下): public EventQueue() { queue = null; String name = "AWT-EventQueue-" + nextThreadNum(); new EventDispatchThread(name, this).start(); } 我們發現了一個Java Event Model中很重要的角色-"EventDispatchThread" ,原來在EventQueue物件被產生的同時,會有一條名叫EventDispatchThread 的Thread物件被產生,這究竟代表什麼呢?各位讀者們,你們已經想到 為什麼要這麼做了嗎?如果還沒想出來,就繼續往下看囉...^O^. 由EventDispatchThread的名稱來看,顧名思義,它應該就是我們要 找尋的訊息分派者,每當系統的EventQueue被產生,一條對應的 EventDispatchThread便會同時被產生,並擔任這個EventQueue的 Dispatcher。我們可由new EventDispatchThread時所傳入的第二個 參數--this(也就是EventQueue本身)看出端倪。 接下來我們就直接到EventDispatchThread.java看個究竟吧。首先我 們發現此EventDispatchThread是繼承自Thread,這在意料之中,不過 既然是Thread物件,那當然就直攻run() method囉! 以下是部分 run() method的code: public void run() { while (doDispatch) { try { AWTEvent event = theQueue.getNextEvent(); //theQueue就是之前傳進來的EventQueue ... Object src = event.getSource(); if (src instanceof Component) { ((Component)src).dispatchEvent(event); } else if (src instanceof MenuComponent) { ((MenuComponent)src).dispatchEvent(event); ... } ... } catch(...) ... }//end while } 果然,如我們所料,EventDispatchThread會不斷向EventQueue拿Event (by theQueue.getNextEvent( );),接著再根據抓回的Event,判斷其 Source(也就是發生Event的來源物件),如果Source為Component物件, 則交給Component物件的dispatchEvent(event)來繼續dispatching的工 作;如果是MenuComponent物件,同理,交由MenuComponent的dispatchEvent (event)來繼續dispatching的工作。簡言之,EventDispatchThread的 角色就相當於Win32的Message Loop,不僅僅負責抓Message,同時也負 責Message Dispatching的工作。說到這裡,各位讀者是否對Java Event Model有更深一層的了解了呢?還是根本...愈聽愈模糊了呢? ^o^...沒 關係,喘口氣,我再用一張圖(圖3)來說明這整個流程,希望能加深各 位讀者的印象。 圖3 由圖3,各位應該可以清楚知道,究竟Event從何而來,往哪裡去了吧! 其實說穿了,Java雖然有自己的Event Model,但是一些系統的Message 還是得由底層的Native Window來捕捉(Ex: Mouse/Keyboard),在透過 Java Virtual Machine這一層的包裝後,才將Event送往Java的EventQueue ,以進入Java自己的Event Model。到目前為止,筆者已經解釋過了 Event從何而來? 但是Event往哪裡去了呢?,嚴格說起來,只提了一半 ,當EventQueue中的Event被EventDispatchThread dispatch出去之 後呢?接下來Event會如何被處理呢?其實到了這個階段以後便是Java這 一層的事情了,這部分我們將在文後說明。(PS: postEvent是EventQueue 所提供的method,程式中可以透過此method把Event丟到EventQueue中!) *EventQueue產生的時機? 以上筆者已經把Java Event Model的核心部分作了說明,但是眼尖的 讀者一定會覺得好奇,EventQueue既然如此重要,那它又是什麼時候 被產生的呢?是在Java這一層被new出來的,還是由Java Virtual Machine 產生的呢?如果以SUN的版本為例,因為筆者在Source中找不到EventQueue 被產生的code,所以在此大膽假設,EventQueue應該是由Java Virtual Machine所產生,例如:當我們以java.exe classname來執行Java程式 時,Java Virtual Machine會自動判別該Java程式是屬於Console Mode 或是GUI Mode程式,以決定是否要產生系統的EventQueue,當然這只 是我的初步假設,尚未實地去證明它,筆者才疏學淺\,如果各位讀者 清楚SUN對這一層的實作情形,請您務必來信指教,3Q3Q..。 筆者雖然對SUN的EventQueue實作不是很清楚,但是若就這樣矇混過去 ,實在也是有點……嗯…啊…我也不知道該怎麼講….^o^! 還好Java Virtual Machine不是只有SUN做得出來,不知道各位有沒有聽過Kaffe Virtual Machine(註1)這一號人物?這是一套Open Source的Java Virtual Machine,筆者就以Kaffe對EventQueue的實作情形來彌補一下吧! 在開始談Kaffe的做法前,我們先談一下SUN對AWT的實作方式,基本上, SUN 的AWT是採用”Peer-based”的機制,雖然在Java這一層可以輕易得 使用一些視窗元件,然而實際上,每個Java AWT所支援的元件(註2),都 會跟底層Native Window所支援的視窗元件做一個Mapping,例如: Java 中可能會new一個Frame,那麼底層就會有個對應的Window被產生。而這 個Mapping的機制,SUN採用了Peer-based的方式,每個正在執行的Java AWT元件都會有個對應的peer,透過這個peer介面來跟底層Native Window 要求服務(透過Native Method),所以各位可以看到每個Java AWT的元件 都會import一個對應的peer介面,Ex: Button.java中就會有”import java.awt.peer.ButtonPeer”,筆者不打算對此多做討論,在此只想讓 各位讀者先有個概念,再來我們要談的是Kaffe的做法。 Kaffe對於AWT的實作部分並沒有完全採用SUN的方式,它用的是比較直 接的機制,透過一個Toolkit類別中的Native Methods,直接跟底層 Native Window溝通,相對於SUN的”Peer-based”,Kaffe省去了一層 Peer的介面,效率自然會比較好,至於SUN為何要採用”Peer-based” 的方式,筆者猜測,這應該是為了”移植性”的考量。呼…. 各位一 定覺得,筆者怎麼講了一堆好像跟Event不相干的東西呢? 其實筆者想 表達的只有幾句話,那就是Kaffe的Toolkit類別中,存在了我們的目 標:EventQueue,而在Toolkit的Static Block中做了一些初始化的動 作,EventQueue就在此時會被產生。為什麼呢?為什麼EventQueue要在 Toolkit類別被載入時產生呢? 這個問題筆者也曾經思考過,各位不妨 也思考一下吧! 其實道理蠻簡單的,因為只要是Java程式(不管是不是 GUI程式),都一定得透過Java Virtual Machine來執行,如果執行的是 Console Mode的程式,自然不需要EventQueue,但如果是GUI Mode的程 式,那EventQueue就必須在程式動到GUI之前被產生,而之前提到Toolkit 中定義了所有會呼叫到底層Native Window的Native Methods,換言之 ,只要Java程式中要使用跟Window相關的Native Method時,Toolkit類 別就必須先被載入,而Toolkit類別的載入不就代表EventQueue被產生 了嗎! 透過這種方式,EventQueue自然就只有在Java程式為GUI程式時 才會出現。圖4用一個最簡單的例子(show一個Java Frame)來說明 EventQueue被產生的情況: 圖4 (略) (1)當MyFrame呼叫show() method時,此時Native System必須有一個對 應的Window被產生並且被顯示出來(PS:當執行new MyFrame( )時, 底層的Window並不會立即被產生)。 (2)因為此時必須呼叫Toolkit中所定義的Native Method(createNativeFrame) ,所以Toolkit類別會先被載入,而Toolkit被載入的過程中,EventQueue 就會被產生。 (3)EventQueue被產生時,相對的EventDispatchThread也會被產生。 (4)在Toolkit被載入完成後,開始呼叫createNativeFrame這個Native Method。 (5)最後才呼叫到底層的Native Window System,把對應的Window顯示出來。 講了那麼多有關EventQueue的東西,我們現在就來證明一下,順便瞧瞧在 Console Mode以及GUI Mode下跑的基本Threads有何不同。在此筆者將用 兩個Samples來測試,一個是Console Mode程式,另一個則是GUI Mode程 式,兩個程式會分別執行listAllThreads()這個Method(如下所示),來列 出目前程式中正在執行的Threads。 public static void showThreadGroup(ThreadGroup tg,String tab){ int threadNumber,groupNumber; if(tg==null) return; threadNumber=tg.activeCount(); groupNumber=tg.activeGroupCount(); Thread[] threads=new Thread[threadNumber]; ThreadGroup[] tgs=new ThreadGroup[groupNumber]; tg.enumerate(threads,false); tg.enumerate(tgs,false); System.out.println(tab+"ThreadGroup:"+tg.getName()); for(int i=0;i } for(int i=0;i } } public static void listAllThreads(){ ThreadGroup current,root,parent; current=Thread.currentThread().getThreadGroup(); root=current;parent=root.getParent(); //find root while(parent!=null){ root=parent; parent=parent.getParent(); } showThreadGroup(root,""); } ---------------------------------------------------------------------- 執行的結果如下: Console Mode | ThreadGroup:system | Thread[Signal dispatcher,5,system] | Thread[Reference Handler,10,system] | Thread[Finalizer,8,system] | null | ThreadGroup:main | Thread[main,5,main] --------------------------------------------------- GUI Mode | ThreadGroup:system | Thread[Signal dispatcher,5,system] | Thread[Reference Handler,10,system] | Thread[Finalizer,8,system] | null | null | null | null | ThreadGroup:main | Thread[main,5,main] | Thread[AWT-EventQueue-0,6,main] | Thread[SunToolkit.PostEventQueue-0,5,main] | Thread[AWT-Windows,5,main] --------------------------------------------------- 而此結果可以看出,GUI Mode程式的main ThreadGroup會比Console Mode 多了3個Threads,而Thread[AWT-EventQueue-0,6,main]這條Thread即為 EventDispatchThread,它的Priority會比一般的Thread多1。至於 SunToolKit這條Thread實際上跟sun.awt這個package以及peer-based的 機制有關,在此筆者則不多做討論。有關EventQueue的介紹就到此告一 段落了,接下來將繼續筆者之前只說了一半的主題”Event 往哪裡去了?” ,也就是Java Event在被EventDispatchThread送出後的Dispatch旅程。 (林錦陽) |