Java Focus實現紀要二
1. 在Jre1.7版本中,KeyboardFocusManager,DefaultKeyboardFocusManager這兩個類,與Component, WComponentPeer類一起完成了focus的主要邏輯實現。
DefaultKeyboardFocusManager是前者的系統(tǒng)默認實現。其單例注冊在appcontext中,如果需要,程序員可以替代它,以擴展focus的邏輯實現。
appcontext.put(KeyboardFocusManager.class, new SelfKeyboardFocusManager());
2. Component中提供了requestFocus方法。而各個組件在初始化時都會安裝默認的Listener。當這些Listener收到適當的事件通知后(比如mouse_press)即會調用這個方法。該方法首先判斷該組件是否focusable,組件所依托窗口是否focusable,當前聚焦組件的InputVerifier是否驗收輸入等等,判斷通過后請求重量級組件容器的peer.requestFocus。WComponentPeer中提供該requestFocus方法。該方法首先調用native processSynchronousLightweightTransfer,其會調用KeyboardFocusManager .processSynchronousLightweightTransferr,作用是如果當前request組件的重量級組件容器正對應當前底層系統(tǒng)的聚焦組件,而且當前沒有任何切換焦點的heavyweightRequests,這時將直接切換focus變量KeyboardFocusManager.focusOwner。
如果上述調用沒有順利完成并返回true,則會調用native _requestFoucs。該方法會調用KeyboardFocusManager .shouldNativelyFocusHeavyweight,其作用就是完成request登記,并在登記時間戳以正確緩存處理后續(xù)進入EDT的Keyevent處理。
Request登記的結構為KeyboardFocusManager.heavyweightRequests=
LinkedList< HeavyweightFocusRequest >
-- HeavyweightFocusRequest{
Component heavyweight;
LinkedList<LightweightFocusRequest> lightweightRequests,登記方式分為3種:
a. 如果發(fā)出requestFocus的組件的重量級組件容器正對應當前底層系統(tǒng)的聚焦組件,而且當前沒有任何切換焦點的heavyweightRequest,則增加一個heavyweightRequest并向Post-Qqeue post focus-event。
b. 如果發(fā)出requestFocus組件的重量級組件容器不對應當前底層系統(tǒng)的聚焦組件,而且當前沒有任何切換焦點的heavyweightRequest;或者當前存在切換焦點的heavyweightRequest,而最后一個heavyweightRequest. Heavyweight!=當前request組件的重量級組件容器,則要增加一個heavyweightRequest,并同步通知底層系統(tǒng)進行重量級對等組件的focus切換。
c. 如果當前存在切換焦點的heavyweightRequest,而且最后一個heavyweightRequest. Heavyweight==當前requestFocus的組件的重量級組件容器,則直接在request.lightweightRequests追加一個LightweightFocusRequest。
3. EDT在逐個處理AWTEvent時,委托給EventQueue.dispatchEvent,繼而委托給Component. dispatchEventImpl,該方法順序執(zhí)行下面的代碼片段:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·
/* focusManagerIsDispatching標志了該event;如果==true意味著該event不會交由KeyboardManager進行retarget及dispatch。而這兩個動作主要完成的功能就是刷新java的全局focus變量。因此可以想象focusManagerIsDispatching==true的Focus_event是focus發(fā)生切換后的event,而focusManagerIsDispatching==false的是PrepareFocusEvent。*/
if (!e.focusManagerIsDispatching) {//----------PrepareFocusEvent
// Invoke the private focus retargeting method which provides
// lightweight Component supportF
/*通過retargetFocus,處理之前注冊的request請求,最終激發(fā)出合適的CausedFocusEvent,交給下面的dispatch.
*/
if (e.isPosted) {
e = KeyboardFocusManager.retargetFocusEvent(e);
e.isPosted = true;
}
// Now, with the event properly targeted to a lightweight
// descendant if necessary, invoke the public focus retargeting
// and dispatching function
/*通過dispatch給注冊的DefaultKeyboardFocusManager,最終更新了java的全局focus變量
*/
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().
dispatchEvent(e))
{
return;
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~·
4. 總之:
a. 輕量級組件的Mouse_Press Listener會requestFocus。request通過必要條件檢查后會在KeyboardFocusManager.heavyweightRequests緩存列表登記,同時在一個列表中登記一個時間戳marker=當前系統(tǒng)時間。
b. 每一個KeyEvent都有一個發(fā)生時間when,這個發(fā)生時間認為是AWT-Windows loop底層event形成KeyEvent的時間。EDT在調用KeyboardFocusManager 。dispatchEvent處理一個KeyEvent時只要發(fā)現when晚于時間戳緩存列表中登記的第一個時間戳,就充分說明這是在某焦點切換請求發(fā)出后發(fā)生的鍵盤事件,則不應該將這個KeyEvent target到當前全局focus變量,因而這時暫將此KeyEvent緩存在另一個列表。
c. 根據前面的分析,在requestFocus時有3種情況,一種是新增heavyweightRequest,同時post給post-queue一個FocusEvent,一種是新增heavyweightRequest,同時因為要求底層系統(tǒng)切換重量級對等體而awt-loop到一個FocusEvent,這兩種情況的request都在列表中期待對應FocusEvent到來再切換焦點。從登記時間戳開始,被awt-loop到的KeyEvent進入EDT時都將緩存下來,而一段時間后當期待的FocusEvent從post-queue進入event-queue并要在EDT中處理時,有充分的條件可以判斷出此后再進入EDT的KeyEvent,都至少是request登記時間戳后發(fā)生的,則這時可以完成此request-刪除該heavyweightRequest緩存條目,做focus實際切換,并將緩存的KeyEvent 及時間戳記錄處理掉。而第3種情況是在requestFocus時可以在最末一個heavyweightRequest上直接追加LightWeightReuquest,那么當該heavyweightRequest期待的FocusEvent到來時,按前面所述處理完該request,再將后續(xù)LightWeightRequest保存引用到一個全局變量KeyboardFocusManager.currentLightweightRequests,再將此刻為止awt-loop至的post-queue的所有event完全flush到event-queue,再把一個要求循環(huán)處理所有currentLightweightRequests指向的LightWeightRequests的InvocationEvent post 到event-queue之后。這樣當EDT開始處理該InvocationEvent時,有充分的條件可以判斷出此后再進入EDT的KeyEvent,都至少是最后一個后續(xù)LightWeightRequest登記時間戳后發(fā)生的,則這時只需按該InvocationEvent執(zhí)行即可,及逐個清理LightWeightRequest完成focus切換及處理時間戳和緩存KeyEvent。如果在循環(huán)處理過程中發(fā)生對某一個組件requestFocus調用,這時會根據處理之初currentLightweightRequests中是否只有單獨1個request來確定能否processSynchronousLightweightTransferr,即如果有多個,則這時禁止processSynchronousLightweightTransferr以防止破壞了切換焦點的順序。
d. 重量級組件不需要在Mouse_Press Listener request Focus,當被進行Mouse Press時,底層系統(tǒng)分發(fā)一個Focus Event,當進入EDT處理時,在jre1.7中通過KeyboardFocusManager.retargetUnexpectedFocusEventretarget,首先逐個剔除request后進行期待匹配(針對可能的底層分發(fā)-post-queue-event-queue中間環(huán)節(jié)Event的遺漏等例外情形),如果最后沒有一個request匹配,則直接形成CausedFocusEvent交給后繼dispatch完成焦點切換。更確切地說,對于jre1.7而言組件聚焦應該都通過requestFocus完成切換,不通過該方式的聚焦切換在retarget時將歸屬到Unexpected被處理,而重量級組件的這種聚焦正好通過unexpected完成。
5. 最后,個人認為jre1.7中存在一個可能的問題:每次dispatchEvent時都會在retargetFocusEvent里processCurrentLightweightRequests,這樣不久破壞了4-c分析的時機邏輯了么?為什么要這樣呢?