5 應用層如何從Framework層接收按鍵事件
由3.2和4.5.4節可知,當InputDispatcher通過服務端管道向socket文件描述符發送消息后,epoll機制監聽到了I/O事件,epoll_wait就會執行返回發生事件的個數給eventCount,主線程開始執行epoll_wait后面的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
}
} else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
|
fd是客戶端socket文件描述符,不是mWakeReadPipeFd,因此if語句不成立,進入else子句。mRequests不為空(3.4.2.2節中已經把Request保存在了mRequests中),pushResponse函數把request取出來賦給response,再放到mResponses容器中保存。
mMessageEnvelopes被初始化為空,也沒有加入數據,依然為空,這句:
1
|
while (mMessageEnvelopes.size() != 0) {
|
不成立,跳過while循環。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
if (response.request.ident == POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
this, response.request.callback.get(), fd, events, data);
#endif
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd);
}
// Clear the callback reference in the response structure promptly because we
// will not clear the response vector itself until the next poll.
response.request.callback.clear();
result = POLL_CALLBACK;
}
|
mResponses不為空,for循環取出里面的response對象,然后執行:
1
|
int callbackResult = response.request.callback->handleEvent(fd, events, data);
|
fd就是服務端socket文件描述符,events是發生的事件,data為空,response.request.callback就是addFd中的第4個實參NativeInputEventReceiver對象也是LooperCallback對象,這句話就是回調主線程中的NativeInputEventReceiver對象的handleEvent函數
5.1 NativeInputEventReceiver的handleEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
#if DEBUG_DISPATCH_CYCLE
// This error typically occurs when the publisher has closed the input channel
// as part of removing a window or finishing an IME session, in which case
// the consumer will soon be disposed as well.
ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. "
"events=0x%x", getInputChannelName(), events);
#endif
return 0; // remove the callback
}
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? 1 : 0;
}
if (events & ALOOPER_EVENT_OUTPUT) {
for (size_t i = 0; i < mFinishQueue.size(); i++) {
const Finish& finish = mFinishQueue.itemAt(i);
status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
if (status) {
mFinishQueue.removeItemsAt(0, i);
if (status == WOULD_BLOCK) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Sent %u queued finish events; %u left.",
getInputChannelName(), i, mFinishQueue.size());
#endif
return 1; // keep the callback, try again later
}
ALOGW("Failed to send finished signal on channel '%s'. status=%d",
getInputChannelName(), status);
if (status != DEAD_OBJECT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
String8 message;
message.appendFormat("Failed to finish input event. status=%d", status);
jniThrowRuntimeException(env, message.string());
mMessageQueue->raiseAndClearException(env, "finishInputEvent");
}
return 0; // remove the callback
}
}
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Sent %u queued finish events; none left.",
getInputChannelName(), mFinishQueue.size());
#endif
mFinishQueue.clear();
setFdEvents(ALOOPER_EVENT_INPUT);
return 1;
}
ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
"events=0x%x", getInputChannelName(), events);
return 1;
}
|
先對events事件進程必要的檢查,如果包含ALOOPER_EVENT_ERROR或ALOOPER_EVENT_HANGUP表示管道關閉,這種情況下丟棄事件
1
2
3
4
5
6
|
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? 1 : 0;
}
|
如果為ALOOPER_EVENT_INPUT事件,調用consumeEvents繼續執行;如果為ALOOPER_EVENT_OUTPUT表示客戶端已經收到數據,需要發送一個完成信號給服務端
5.2 NativeInputEventReceiver的consumeEvents
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%lld.",
getInputChannelName(), consumeBatches ? "true" : "false", frameTime);
#endif
if (consumeBatches) {
mBatchedInputEventPending = false;
}
if (outConsumedBatch) {
*outConsumedBatch = false;
}
ScopedLocalRef<jobject> receiverObj(env, NULL);
bool skipCallbacks = false;
for (;;) {
uint32_t seq;
InputEvent* inputEvent;
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
if (status) {
if (status == WOULD_BLOCK) {
if (!skipCallbacks && !mBatchedInputEventPending
&& mInputConsumer.hasPendingBatch()) {
// There is a pending batch. Come back later.
if (!receiverObj.get()) {
receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
if (!receiverObj.get()) {
ALOGW("channel '%s' ~ Receiver object was finalized "
"without being disposed.", getInputChannelName());
return DEAD_OBJECT;
}
}
mBatchedInputEventPending = true;
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
getInputChannelName());
#endif
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchBatchedInputEventPending);
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching batched input events.");
mBatchedInputEventPending = false; // try again later
}
}
return OK;
}
ALOGE("channel '%s' ~ Failed to consume input event. status=%d",
getInputChannelName(), status);
return status;
}
assert(inputEvent);
if (!skipCallbacks) {
if (!receiverObj.get()) {
receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
if (!receiverObj.get()) {
ALOGW("channel '%s' ~ Receiver object was finalized "
"without being disposed.", getInputChannelName());
return DEAD_OBJECT;
}
}
jobject inputEventObj;
switch (inputEvent->getType()) {
case AINPUT_EVENT_TYPE_KEY:
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Received key event.", getInputChannelName());
#endif
inputEventObj = android_view_KeyEvent_fromNative(env,
static_cast<KeyEvent*>(inputEvent));
break;
case AINPUT_EVENT_TYPE_MOTION: {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());
#endif
MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
*outConsumedBatch = true;
}
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
break;
}
default:
assert(false); // InputConsumer should prevent this from ever happening
inputEventObj = NULL;
}
if (inputEventObj) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName());
#endif
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching input event.");
skipCallbacks = true;
}
env->DeleteLocalRef(inputEventObj);
} else {
ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName());
skipCallbacks = true;
}
}
if (skipCallbacks) {
mInputConsumer.sendFinishedSignal(seq, false);
}
}
}
|
1
2
|
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
|
在for循環中調用InputConsumer對象的consume函數從socket客戶端獲取InputDispatcher發送來的事件保存到inputEvent對象中,先分析consume方法,然后返回來再看后面的代碼
在consume函數中有這句:
1
|
status_t result = mChannel->receiveMessage(&mMsg);
|
receiveMessage的源碼中有這句:
1
2
3
|
do {
nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
} while (nRead == -1 && errno == EINTR);
|
在3.2節通過send向服務端socket發送了數據,那么在此處通過recv從客戶端socket上獲取的消息並保存到mMsg中,如果成功,再根據事件類型選擇case語句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
switch (mMsg.header.type) {
case InputMessage::TYPE_KEY: {
KeyEvent* keyEvent = factory->createKeyEvent();
if (!keyEvent) return NO_MEMORY;
initializeKeyEvent(keyEvent, &mMsg);
*outSeq = mMsg.body.key.seq;
*outEvent = keyEvent;
#if DEBUG_TRANSPORT_ACTIONS
ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
mChannel->getName().string(), *outSeq);
#endif
break;
}
|
把消息保存到KeyEvent對象中,再付給outEvent,如果一切成功,返回OK到5.2節這句mInputConsumer.consume的后面繼續執行:
1
2
3
4
5
6
7
8
|
if (!receiverObj.get()) {
receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
if (!receiverObj.get()) {
ALOGW("channel '%s' ~ Receiver object was finalized "
"without being disposed.", getInputChannelName());
return DEAD_OBJECT;
}
}
|
mReceiverWeakGlobal就是傳遞過來的WindowInputEventReceiver對象的本地引用
1
2
3
4
5
6
7
8
9
|
jobject inputEventObj;
switch (inputEvent->getType()) {
case AINPUT_EVENT_TYPE_KEY:
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Received key event.", getInputChannelName());
#endif
inputEventObj = android_view_KeyEvent_fromNative(env,
static_cast<KeyEvent*>(inputEvent));
break;
|
如果讀取所有事件成功,再根據事件的類型選擇相應執行語句,如果是按鍵事件,就調用android_view_KeyEvent_fromNative把按鍵事件傳遞給java層KeyEvent對象並初始化,然后返回該對象賦給變量inputEventObj,繼續執行到這句:
1
2
|
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
|
receiverObj.get()得到WindowInputEventReceiver對象,gInputEventReceiverClassInfo.dispatchInputEvent就是待調方法的id號,CallVoidMethod函數的作用就是調用第一個參數WindowInputEventReceiver對象的dispatchInputEvent方法,后面兩個實參會傳遞給該方法,如果這一切都成功,就把按鍵事件傳遞到java層處理。
由於WindowInputEventReceiver中沒有實現dispatchInputEvent,因此直接調用父類InputEventReceiver的dispatchInputEvent方法
5.3 InputEventReceiver的dispatchInputEvent
1
2
3
4
|
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
|
onInputEvent在WindowInputEventReceiver中已經實現,就調用WindowInputEventReceiver中的onInputEvent方法,onInputEvent調用了enqueueInputEvent
5.4 ViewRootImpl的enqueueInputEvent
1
|
enqueueInputEvent(event, this, 0, true);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
|
obtainQueuedInputEvent方法把輸入事件封裝成QueuedInputEvent對象並放入到QueuedInputEvent對象池中,然后取出一個QueuedInputEvent事件后按照次序排列,再調用doProcessInputEvents方法從隊列中循環取出事件發送給輸入法或者應用程序等不同階段進行處理。
doProcessInputEvents的調用過程:
doProcessInputEvents —-> deliverInputEvent —-> stage.deliver(q)
1
2
3
4
5
6
7
8
9
|
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q));
}
}
|
當前事件還沒有處理,因此不包含FLAG_FINISHED標致,if語句不成立;正常情況下不會丟棄當前事件,第一個else子句也不成立,執行最后一個else子句。apply的第二個參數是onProcess方法,ViewRootImpl中有8個onProcess方法,具體調用哪個?
在setView方法最后創建了很多InputStage對象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
|
InputStage是抽象基類,ViewPostImeInputStage,EarlyPostImeInputStage,ViewPreImeInputStage等對象都是為了處理輸入事件在不同階段而創建的,比如:ViewPostImeInputStage表示發送輸入事件給view樹進行處理,這些輸入事件都是在輸入法處理之后的。ViewPreImeInputStage表示輸入事件必須在輸入法處理之前發送給view樹處理。
ViewPreImeInputStage表示在輸入法之前處理,ImeInputStage表示進入輸入法處理,ViewPostImeInputStage表示發送給視圖。如果有輸入法窗口,就先傳輸給ViewPreImeInputStage處理,如果沒有,傳輸給ViewPostImeInputStage,一般情況下,都是傳給ViewPostImeInputStage。
此處會調用ViewPostImeInputStage的onProcess來處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
// If delivering a new non-key event, make sure the window is
// now allowed to start updating.
handleDispatchDoneAnimating();
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
|
if語句成立,調用processKeyEvent
5.5 ViewPostImeInputStage的processKeyEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (event.getAction() != KeyEvent.ACTION_UP) {
// If delivering a new key event, make sure the window is
// now allowed to start updating.
handleDispatchDoneAnimating();
}
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
// If the Control modifier is held, try to interpret the key as a shortcut.
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.isCtrlPressed()
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())) {
if (mView.dispatchKeyShortcutEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
}
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
int direction = 0;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (event.hasNoModifiers()) {
direction = View.FOCUS_LEFT;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (event.hasNoModifiers()) {
direction = View.FOCUS_RIGHT;
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
if (event.hasNoModifiers()) {
direction = View.FOCUS_UP;
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (event.hasNoModifiers()) {
direction = View.FOCUS_DOWN;
}
break;
case KeyEvent.KEYCODE_TAB:
if (event.hasNoModifiers()) {
direction = View.FOCUS_FORWARD;
} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
direction = View.FOCUS_BACKWARD;
}
break;
}
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
// do the math the get the interesting rect
// of previous focused into the coord system of
// newly focused view
focused.getFocusedRect(mTempRect);
if (mView instanceof ViewGroup) {
((ViewGroup) mView).offsetDescendantRectToMyCoords(
focused, mTempRect);
((ViewGroup) mView).offsetRectIntoDescendantCoords(
v, mTempRect);
}
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
return FINISH_HANDLED;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
return FINISH_HANDLED;
}
} else {
// find the best view to give focus to in this non-touch-mode with no-focus
View v = focusSearch(null, direction);
if (v != null && v.requestFocus(direction)) {
return FINISH_HANDLED;
}
}
}
}
return FORWARD;
}
|
processKeyEvent方法非常重要了,做了很多任務
1
2
3
4
|
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
|
mView是DecorView,所有view的根,這段話就是把按鍵事件傳給view處理,從此處開始就正式轉交給應用層,在第6節將進行詳細分析
1
2
3
4
5
6
7
8
9
10
11
12
|
// If the Control modifier is held, try to interpret the key as a shortcut.
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.isCtrlPressed()
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())) {
if (mView.dispatchKeyShortcutEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
}
|
處理Ctrl組合按鍵
1
2
3
4
|
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
|
處理所有回退按鍵事件,主要是一些還沒有處理的特殊按鍵。比如相機拍照、撥號按鍵等。如果特殊按鍵沒有在PhoneWindowManager、view樹、窗口中處理,就傳到此處
1
2
3
|
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
......
|
處理方向鍵和TAB鍵,找到獲得焦點的view並把這幾個按鍵傳遞過去,如果沒有view有焦點,就找一個最合適的view並把按鍵傳遞過去
小結:
應用程序客戶端通過NativeInputEventReceiver的InputConsumer方法從客戶端管道InputChannel中獲取事件消息,經過過濾,轉化成應用層按鍵類型,再把按鍵事件傳遞到輸入法窗口,應用層
6. 應用層接收到按鍵事件后如何傳遞
由5.5節這句:
1
2
3
4
|
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
|
可知,按鍵事件傳遞到了mView的dispatchKeyEvent方法,mView就是PhoneWindow內部類DecorView對象,因此,應用層按鍵事件就從DecorView的dispatchKeyEvent方法開始
也可以這樣理解,InputDispatcher先找到當前獲得焦點的窗口,把事件發送給該窗口,窗口在啟動activity時會創建,按鍵事件就傳遞到了獲得焦點的窗口對應的所有view的根類DecorView,也可以說傳遞給了獲得焦點的窗口對應的Activity對象。
Q10 mView是什么時候創建的?如何傳遞的?見6.6節
6.1 DecorView的dispatchKeyEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
if (isDown && (event.getRepeatCount() == 0)) {
// First handle chording of panel key: if a panel key is held
// but not released, try to execute a shortcut in it.
if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
boolean handled = dispatchKeyShortcutEvent(event);
if (handled) {
return true;
}
}
// If a panel is open, perform a shortcut on it without the
// chorded panel key
if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
return true;
}
}
}
if (!isDestroyed()) {
final Callback cb = getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
|
前面兩個if語句是快捷按鍵的處理
1
2
3
4
5
6
7
8
|
if (!isDestroyed()) {
final Callback cb = getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
|
getCallback返回的是CallBack對象cb,cb對象代表一個Activity或Dialog對象,一般情況下不為空。本文主要討論Activity,不再使用“Activity或Dialog對象”這樣的術語;
mFeatureId:代表應用程序的特征標識或者整個屏幕的標識,如果是應用程序,就為-1,具體賦值過程為:
Activity的onCreate —-> setContentView —-> PhoneWindow的setContentView —-> installDecor() —->
generateDecor() —-> new DecorView(getContext(), -1)
如果Activity對象不為空,mFeatureId為-1,調用Activity對象的dispatchKeyEvent方法,將在6.2節分析;
如果為空,就調用super.dispatchKeyEvent(event)即父類ViewGroup的dispatchKeyEvent,將在6.2.1節分析。
如果返回結果為true,表明已經消耗,按鍵事件不再往后傳遞,否則執行到:
1
2
|
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
|
這一步傳遞到PhoneWindow的onKeyDown、onKeyUp方法,將在6.4節分析。
Q11 cb對象為什么是Activity?
在這篇文章:啟動Activity的流程(Launcher中點擊圖標啟動)
過程18中創建Activity對象后,調用了Activity對象的attach進行初始化,在attach中有:
1
2
|
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
|
PolicyManager類的靜態方法makeNewWindow —-> Policy的makeNewWindow —-> new PhoneWindow(context)
makeNewWindow 最終創建了一個PhoneWindow對象,setCallback方法把該this對象即Activity傳過去賦值給mCallback,然后getCallback返回mCallback即該Activity對象
通過這段話可知,當啟動某個應用的Activity時,系統會創建一個PhoneWindow對象與之對應並擁有一個該對象引用,在PhoneWindow對象中通過該對象引用回調Activity的方法,比如dispatchKeyEvent。
6.2 Activity的dispatchKeyEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
// Let action bars open menus in response to the menu key prioritized over
// the window handling it
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
|
Activity的dispatchKeyEvent起到攔截按鍵作用,如果這一步不處理,將分發給view或viewGroup處理。
1
|
onUserInteraction();
|
如果有按鍵、觸摸、軌跡球事件分發給Activity時,在具體事件處理之前,會回調onUserInteraction,一般情況下,用戶需要自行實現該方法,與onUserLeaveHint一起配合使用輔助Activity管理狀態欄通知。在按鍵按下、抬起時都會觸發該方法回調;但在觸摸時,只有觸摸按下時被回調,觸摸移動、抬起時不會回調。
1
2
3
4
|
if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
|
如果有Menu鍵且狀態欄ActionBar消耗了該鍵,就直接返回true;否則繼續往下處理
1
2
3
4
|
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public boolean superDispatchKeyEvent(KeyEvent event) {
// Give priority to closing action modes if applicable.
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mActionMode.finish();
}
return true;
}
}
return super.dispatchKeyEvent(event);
}
|
獲得當前Activity對應的Window對象,也就是PhoneWindow,把按鍵事件傳遞給PhoneWindow對象,主要對Back按鍵松開的特殊處理,如果沒有消耗,連同其它按鍵事件一起傳遞到super.dispatchKeyEvent即父類ViewGroup,具體處理過程在6.2.1節;
1
2
3
|
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this);
|
如果父類ViewGroup也沒有處理,傳遞到KeyEvent的dispatch方法,具體處理過程在6.3節。
6.2.1 ViewGroup的dispatchKeyEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
|
1
2
3
|
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
|
對按鍵事件進行一致性檢查,這種檢查防止同樣的錯誤多次發生,如果有錯誤,日志會打印出來
1
2
3
4
5
6
7
8
9
10
11
|
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
|
PFLAG_FOCUSED表示獲得焦點,PFLAG_HAS_BOUNDS表示大小、邊界被確定了,如果ViewGroup本身有焦點且其大小已確定,就調用該ViewGroup的父類即View的dispatchKeyEvent來處理,具體在6.2.1.1中分析
mFocused是ViewGroup內部包含的獲得焦點的子view,如果該子view獲得焦點且大小、邊界已確定,就調用該子view的dispatchKeyEvent處理。子view既可以是ViewGroup、LinearLayout、RelativeLayout等布局也可以是view、TextView、Button、ListView等,第二個if語句就是遞歸傳遞到所有布局和view中
假如某Activity的布局是自定義一個LinearLayout,稱為A,其內部包含一個LinearLayout,稱為B,B中包含一個TextView C,C有焦點。dispatchKeyEvent傳遞流程是:ViewGroup —-> A —-> B —-> C
傳遞到view時的具體情況在6.2.2節
6.2.2 view的dispatchKeyEvent
1
2
3
4
5
|
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
|
ListenerInfo類專門描述所有view相關監聽器信息的類,比如OnFocusChangeListener、OnScrollChangeListener、OnClickListener、mOnTouchListener等。
當某個view比如button、imageView設置了某個監聽器時,在setOnXXXListener方法中就會調用getListenerInfo方法創建ListenerInfo對象並賦值給mListenerInfo變量,因此,該變量肯定不為空,如果沒有設置監聽器,那就為空,假設有監聽器,下面這條語句成立:
1
|
if (li != null && li.mOnKeyListener != null
|
如果view設置了監聽器,且enable屬性為true,會優先調用OnKeyListener的onKey方法處理,如果沒有設置該監聽器或者該監聽器沒有消耗掉,按鍵繼續傳遞到KeyEvent的dispatch
1
2
3
|
if (event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
|
6.2.2.1 KeyEvent的dispatch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ": " + this);
boolean res = receiver.onKeyDown(mKeyCode, this);
if (state != null) {
if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
if (DEBUG) Log.v(TAG, " Start tracking!");
state.startTracking(this, target);
} else if (isLongPress() && state.isTracking(this)) {
try {
if (receiver.onKeyLongPress(mKeyCode, this)) {
if (DEBUG) Log.v(TAG, " Clear from long press!");
state.performedLongPress(this);
res = true;
}
} catch (AbstractMethodError e) {
}
}
}
return res;
}
case ACTION_UP:
if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
+ ": " + this);
if (state != null) {
state.handleUpEvent(this);
}
return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE:
final int count = mRepeatCount;
final int code = mKeyCode;
if (receiver.onKeyMultiple(code, count, this)) {
return true;
}
if (code != KeyEvent.KEYCODE_UNKNOWN) {
mAction = ACTION_DOWN;
mRepeatCount = 0;
boolean handled = receiver.onKeyDown(code, this);
if (handled) {
mAction = ACTION_UP;
receiver.onKeyUp(code, this);
}
mAction = ACTION_MULTIPLE;
mRepeatCount = count;
return handled;
}
return false;
}
return false;
}
|
receiver:既可能是Activity對象,又可能是view對象,當前是view對象,因為是view的dispatchKeyEvent傳過來的,在6.3節,傳遞過來的是Activity對象
state:通過getKeyDispatcherState返回KeyEvent內部類DispatcherState對象,該對象用來進行高級別的按鍵事件處理,如長按事件等;在getKeyDispatcherState方法內部,mAttachInfo是AttachInfo對象,當view關聯到窗口時的一系列信息,AttachInfo類用來描述、跟蹤這些信息,一般情況下不為空
第三個參數target,實參是this,既是Activity或view對象,也是KeyEvent的內部類CallBack類型
case語句先處理按鍵按下ACTION_DOWN動作:
1
2
|
mFlags &= ~FLAG_START_TRACKING
boolean res = receiver.onKeyDown(mKeyCode, this);
|
先清掉FLAG_START_TRACKING標記,再調用view的onKeyDown方法,此方法在6.2.2.2節進行分析
如果onKeyDown返回true,表明已經消耗,res為true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
if (state != null) {
if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
if (DEBUG) Log.v(TAG, " Start tracking!");
state.startTracking(this, target);
} else if (isLongPress() && state.isTracking(this)) {
try {
if (receiver.onKeyLongPress(mKeyCode, this)) {
if (DEBUG) Log.v(TAG, " Clear from long press!");
state.performedLongPress(this);
res = true;
}
} catch (AbstractMethodError e) {
}
}
}
|
如果view的onKeyDown已經消耗掉,且是第一次按下,mFlags包含FLAG_START_TRACKING,就調用startTracking跟蹤按鍵事件,這樣做便於判斷是否是長按事件的條件,如果有長按事件,並且正在跟蹤當前按鍵,就調用view的onKeyLongPress處理:
1
2
3
|
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return false;
}
|
源碼總是返回false,不作任何處理,用戶可以根據需求重寫該方法來實現長按
case語句處理按鍵松開ACTION_UP動作:
1
|
return receiver.onKeyUp(mKeyCode, this);
|
回調view的onKeyUp方法,mKeyCode是按鍵碼值,this是KeyEvent對象,此方法也在6.2.2.2節分析,此時,dispatch主要作用是回調view的onKeyDown、onKeyUp,注意與6.3節的區別。
6.2.2.2 view的onKeyDown、onKeyUp
view的onKeyDown:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean result = false;
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
// Long clickable items don't necessarily have to be clickable
if (((mViewFlags & CLICKABLE) == CLICKABLE ||
(mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
(event.getRepeatCount() == 0)) {
setPressed(true);
checkForLongClick(0);
return true;
}
}
return result;
}
|
主要針對KEYCODE_DPAD_CENTER、KEYCODE_ENTER事件進行處理:
如果當前按鍵事件包含KEYCODE_DPAD_CENTER、KEYCODE_ENTER,並且view設置了DISABLED屬性,直接返回true,說明該view已經被按下;
如果view設置了單擊CLICKABLE或長按狀態LONG_CLICKABLE,就調用setPressed把該view設置為PRESSED狀態,同時更新其繪制狀態(顯示狀態,比如更換了背景圖片、顏色等);如果僅是長按狀態,系統在500秒后執行下面幾種情形:
a. 如果有長按監聽器OnLongClickListener,就回調onLongClick,如果成功,系統會發出一個觸覺反饋;
b. 如果沒有長按監聽器,就顯示一個菜單。
如果按鍵事件不包含KEYCODE_DPAD_CENTER、KEYCODE_ENTER,onKeyDown不作任何處理,直接return false
view的onKeyUp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
setPressed(false);
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
return performClick();
}
}
}
return false;
}
|
在onKeyUp方法中,setPressed(false)去除view的pressed狀態,同時更新其繪制狀態(顯示狀態)
1
2
3
4
5
|
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
return performClick();
}
|
如果在onKeyDown中沒有處理長按事件,那么mHasPerformedLongPress為false,就把長按事件對象從消息的隊列中移除。最后,調用performClick:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
|
如果設置了OnClickListener監聽器,就回調onClick方法。
由此可知,當遙控器按鍵(KEYCODE_DPAD_CENTER、KEYCODE_ENTER)松開時,如果設置了OnClickListener監聽器,會調用onClick方法。
6.3 keyEvent的dispatch
該方法已經在6.2.2節分析過,只不過receiver對象已經不再是view,而是Activity,因為是從Activity的dispatchKeyEvent傳遞而來,此時,dispatch主要作用是回調Activity的onKeyDown、onKeyUp
6.3.1 Activity的onKeyDown,onKeyUp
如果Activity里面的任何view、布局都沒有處理按鍵,就會傳遞到Activity的onKeyDown,onKeyUp。比如,當在EditText中輸入文字時,Activity的onKeyDown,onKeyUp不會接收到按鍵事件,因為EditText有自己的處理按鍵事件的方法,如果此時把焦點從EditText移走,onKeyDown,onKeyUp就會接收到按鍵事件。
onKeyDown源碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) {
event.startTracking();
} else {
onBackPressed();
}
return true;
}
if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
return false;
} else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
Window w = getWindow();
if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
return true;
}
return false;
} else {
// Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
boolean clearSpannable = false;
boolean handled;
if ((event.getRepeatCount() != 0) || event.isSystem()) {
clearSpannable = true;
handled = false;
} else {
handled = TextKeyListener.getInstance().onKeyDown(
null, mDefaultKeySsb, keyCode, event);
if (handled && mDefaultKeySsb.length() > 0) {
// something useable has been typed - dispatch it now.
final String str = mDefaultKeySsb.toString();
clearSpannable = true;
switch (mDefaultKeyMode) {
case DEFAULT_KEYS_DIALER:
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
case DEFAULT_KEYS_SEARCH_LOCAL:
startSearch(str, false, null, false);
break;
case DEFAULT_KEYS_SEARCH_GLOBAL:
startSearch(str, false, null, true);
break;
}
}
}
if (clearSpannable) {
mDefaultKeySsb.clear();
mDefaultKeySsb.clearSpans();
Selection.setSelection(mDefaultKeySsb,0);
}
return handled;
}
}
|
1
2
3
4
5
6
7
8
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) {
event.startTracking();
} else {
onBackPressed();
}
return true;
}
|
這段話需要結合onKeyUp來看:
如果在Android2.1之前的版本(不包含),按下BACK鍵后調用onBackPressed直接退出Activity;
如果在Android 2.1(包含)之后的版本,先調用startTracking方法把mFlags置為FLAG_START_TRACKING,系統會跟蹤按鍵傳遞過程,直到松開按鍵進入到onKeyUp方法時才會調用onBackPressed,可以重寫該方法退出Activity,也就是說,只有松開BACK按鍵時才會退出Activity,如果不松開不會退出Activity。
隨后根據按鍵模式mDefaultKeyMode決定做哪些事情,此處不是重點,本文不作分析
onKeyUp源碼
1
2
3
4
5
6
7
8
9
10
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
}
return false;
}
|
if語句就是配合onKeyDown使用的,如果在Android 2.1(包含)之后的版本,松開按鍵時才會退出Activity;其他按鍵直接返回false,一般重寫onKeyUp實現自己的需求。
如果onKeyDown,onKeyUp沒有消耗掉按鍵事件,就逆向返回到KeyEvent的dispatch中處理,如果dispatch也沒有消耗掉,就返回到Activity —-> DecorView —-> PhoneWindow,進入到 PhoneWindow中處理。
6.4 PhoneWindow的onKeyDown、onKeyUp
經過上述方法處理,如果返回false,說明所有應用程序層的view、viewGroup、Activity都沒有消耗掉,按鍵事件傳遞到了當前窗口window中進行處理
1
2
|
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
|
onKeyDown源碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
/* ****************************************************************************
* HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
*
* If your key handling must happen before the app gets a crack at the event,
* it goes in PhoneWindowManager.
*
* If your key handling should happen in all windows, and does not depend on
* the state of the current application, other than that the current
* application can override the behavior by handling the event itself, it
* should go in PhoneFallbackEventHandler.
*
* Only if your handling depends on the window, and the fact that it has
* a DecorView, should it go here.
* ****************************************************************************/
final KeyEvent.DispatcherState dispatcher =
mDecor != null ? mDecor.getKeyDispatcherState() : null;
//Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
// + " flags=0x" + Integer.toHexString(event.getFlags()));
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN: {
int direction = keyCode == KeyEvent.KEYCODE_VOLUME_UP ? AudioManager.ADJUST_RAISE
: AudioManager.ADJUST_LOWER;
// If we have a session send it the volume command, otherwise
// use the suggested stream.
if (mMediaController != null) {
mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
} else {
MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
mVolumeControlStreamType, direction,
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
}
return true;
}
case KeyEvent.KEYCODE_VOLUME_MUTE: {
getAudioManager().handleKeyDown(event, mVolumeControlStreamType);
return true;
}
// These are all the recognized media key codes in
// KeyEvent.isMediaKey()
case KeyEvent.KEYCODE_MEDIA_PLAY:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_NEXT:
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
case KeyEvent.KEYCODE_MEDIA_REWIND:
case KeyEvent.KEYCODE_MEDIA_RECORD:
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
if (mMediaController != null) {
if (mMediaController.dispatchMediaButtonEvent(event)) {
return true;
}
}
return false;
}
case KeyEvent.KEYCODE_MENU: {
onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
return true;
}
case KeyEvent.KEYCODE_BACK: {
if (event.getRepeatCount() > 0) break;
if (featureId < 0) break;
// Currently don't do anything with long press.
if (dispatcher != null) {
dispatcher.startTracking(event, this);
}
return true;
}
}
return false;
}
|
onKeyDown/onKeyUp方法主要針對當前獲得焦點的窗口對一些特殊按鍵進行處理,包括音量+/-,多媒體控制按鍵,MENU,BACK
注意:PhoneFallbackEventHandler中也是對特殊按鍵進行處理,但是那是針對所有所有的窗口,包括當前獲得焦點的窗口,而PhoneWindow只針對當前獲得焦點的窗口
6.5 小結
應用層按鍵事件傳遞時涉及到很多情況,大概傳遞流程:
a. 應用層按鍵事件傳遞到view樹根DecorView后分為兩步:view樹內部;view樹外部(獲得焦點的窗口)
如果view樹內部沒有消耗,就傳遞到view樹外部,即傳遞給獲得焦點的窗口的onKeyDown/onKeyUp;
b. view樹內部一般先傳遞到當前Activity對象,如果沒有消耗,傳遞到Activity的onKeyDown/onKeyUp;
c. Activity對象內部先分發給ViewGroup,viewGroup如果本身有焦點就傳遞給其父類view;
如果viewGroup本身沒有焦點,就傳遞給其獲得焦點的子view。子view分為兩種情況:
如果子view是LinearLayout等常見布局,就遞歸傳遞過去,最后傳遞給獲得焦點的view視圖;
如果子view是純粹的view視圖,就傳遞給該視圖;
d. view視圖內部,如果設置了OnKeyListener監聽器,就傳遞給OnKey;
如果沒有OnKeyListener監聽器,就分發給KeyEvent的dispatch,dispatch主要回調view的onKeyDown/onKeyUp;
e. 在view的onKeyDown/onKeyUp中,如果是DPAD_CENTER,KEYCODE_ENTER,直接處理;
否則,更新繪制狀態、執行長按處理、執行onClick方法等。
該小結沒有考慮所有條件,只是大概給出傳遞流程,因為很多時候重寫某個方法返回true不再傳遞下去,因此也就沒有過多步驟。
6.6 mView的創建過程
在啟動Activity的流程(Launcher中點擊圖標啟動)這篇文章中的過程18的handleResumeActivity方法中,有這段語句:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
|
r.activity就是新啟動的目標Activity對象,getWindow返回mWindow對象,mWindow的創建過程:
1
|
mWindow = PolicyManager.makeNewWindow(this);
|
PolicyManager對象的makeNewWindow —-> sPolicy.makeNewWindow(context)
sPolicy是Policy對象,程序調到了Policy的makeNewWindow:
1
2
3
|
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
|
創建了一個PhoneWindow對象並返回給mWindow,再賦值給r.window
r.window.getDecorView()方法調用PhoneWindow對象的getDecorView方法
1
2
3
4
5
6
|
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
|
如果mDecor為空,就調用installDecor方法新創建一個DecorView對象,否則,直接返回該對象。
r.window.getDecorView() —-> PhoneWindow的installDecor方法 —-> mDecor = generateDecor() —->
return new DecorView(getContext(), -1)
在PhoneWindow中創建了一個DecorView對象並返回給decor變量
通過這兩句可知,啟動一個新的Activity時,系統會創建一個對應的widow窗口對象(實際是PhoneWindow對象),這是一對一關系;同時,如果已經有了DecorView就復用之,否則,新創建一個DecorView對象(DecorView最終繼承於View,也是一個View對象),這是多對一關系。
1
|
ViewManager wm = a.getWindowManager();
|
getWindowManager返回mWindowManager變量,mWindowManager的賦值語句為:
1
|
mWindowManager = mWindow.getWindowManager();
|
已經可知mWindow是一個PhoneWindow對象,這樣就調到了PhoneWindow的getWindowManager方法,PhoneWindow中沒有實現getWindowManager,直接調用父類Window的getWindowManager:
1
2
3
4
|
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
|
WindowManagerImpl繼承了WindowManager,createLocalWindowManager方法源碼:
1
2
3
|
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
|
創建了一個與Activity對應的WindowManagerImpl對象。
1
|
wm.addView(decor, l);
|
調用WindowManagerImpl對象的addView方法,源碼:
1
2
3
4
|
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
|
mGlobal是WindowManagerGlobal的單例對象,addView方法中有:
1
|
root.setView(view, wparams, panelParentView);
|
root是ViewRootImpl對象,調用其setView方法把DecorView對象傳遞過去並賦值給mView
小結:mView就是與Activity對應的DecorView對象,在創建PhoneWindow對象時創建的。
7 特殊按鍵如何處理
特殊按鍵處理方法主要有:
interceptKeyBeforeQueueing
interceptKeyBeforeDispatching
PhoneWindow的onKeyDown/onKeyUp
PhoneFallbackEventHandler的dispatchKeyEvent
每個方法用在不同的時刻
7.1 interceptKeyBeforeQueueing
在2.2節提到,NativeInputManager傳遞過來后賦給了mPolicy變量,interceptKeyBeforeQueueing在NativeInputManager中也實現了,interceptKeyBeforeQueueing用以處理系統級按鍵,比如HOME、TVSOURCE等
7.1.1 com_android_server_input_InputManagerService.cpp的interceptKeyBeforeQueueing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
uint32_t& policyFlags) {
// Policy:
// - Ignore untrusted events and pass them along.
// - Ask the window manager what to do with normal events and trusted injected events.
// - For normal events wake and brighten the screen if currently off or dim.
if (mInteractive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
if ((policyFlags & POLICY_FLAG_TRUSTED)) {
nsecs_t when = keyEvent->getEventTime();
JNIEnv* env = jniEnv();
jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
jint wmActions;
if (keyEventObj) {
wmActions = env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptKeyBeforeQueueing,
keyEventObj, policyFlags);
if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
wmActions = 0;
}
android_view_KeyEvent_recycle(env, keyEventObj);
env->DeleteLocalRef(keyEventObj);
} else {
ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
wmActions = 0;
}
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
} else {
if (mInteractive) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
}
}
}
|
1
2
3
|
if (mInteractive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
|
設置按鍵標志為活動狀態
1
|
jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) {
jobject eventObj = env->CallStaticObjectMethod(gKeyEventClassInfo.clazz,
gKeyEventClassInfo.obtain,
nanoseconds_to_milliseconds(event->getDownTime()),
nanoseconds_to_milliseconds(event->getEventTime()),
event->getAction(),
event->getKeyCode(),
event->getRepeatCount(),
event->getMetaState(),
event->getDeviceId(),
event->getScanCode(),
event->getFlags(),
event->getSource(),
NULL);
if (env->ExceptionCheck()) {
ALOGE("An exception occurred while obtaining a key event.");
LOGE_EX(env);
env->ExceptionClear();
return NULL;
}
return eventObj;
}
|
android_view_KeyEvent_fromNative方法中,通過調用JNI接口的CallStaticObjectMethod方法獲得第二個參數gKeyEventClassInfo.obtain返回的值並把值賦給eventObj,gKeyEventClassInfo.obtain是什么?在register_android_view_KeyEvent函數中通過findclass得知,gKeyEventClassInfo.clazz就是java層KeyEvent類的類本地引用,gKeyEventClassInfo.obtain就是KeyEvent中的obtain方法在本地的method id號;CallStaticObjectMethod函數調用KeyEvent中obtain方法,返回值是java層KeyEvent對象,因此,android_view_KeyEvent_fromNative返回值為java層的KeyEvent對象在本地的引用,賦給keyEventObj
1
2
3
|
wmActions = env->CallIntMethod(mServiceObj,
gServiceClassInfo.interceptKeyBeforeQueueing,
keyEventObj, policyFlags);
|
由1.1.1和1.1.2節可知,mServiceObj就是傳遞過來的InputManagerService對象,gServiceClassInfo.interceptKeyBeforeQueueing保存了InputManagerService對象中interceptKeyBeforeQueueing方法的method id號,CallIntMethod方法調用InputManagerService中interceptKeyBeforeQueueing方法並把返回值賦給wmActions變量
1
|
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
|
1
2
3
4
5
6
7
8
9
10
|
void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when,
uint32_t& policyFlags) {
if (wmActions & WM_ACTION_PASS_TO_USER) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
} else {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("handleInterceptActions: Not passing key to user.");
#endif
}
}
|
根據返回值wmActions決定特殊按鍵的走向,如果wmActions為WM_ACTION_PASS_TO_USER即1,那么把policyFlags設為POLICY_FLAG_PASS_TO_USER,意思是說該按鍵應該傳輸給應用程序處理,不在framework層處理,比如,HOME按鍵不應該傳給應用程序,而應在framework層處理,不針對某一個應用程序,針對的整個系統,在任何應用程序界面下按下HOME都能起作用。
7.1.2 InputManagerService的interceptKeyBeforeQueueing
1
2
3
4
|
// Native callback.
private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
}
|
mWindowManagerCallbacks是InputMonitor對象,在1.4節提到過,該對象在InputManagerService對象創建后通過其setWindowManagerCallbacks傳遞過去,便於回調InputMonitor對象中的方法
7.3 InputMonitor的interceptKeyBeforeQueueing
1
2
3
4
5
6
|
/* Provides an opportunity for the window manager policy to intercept early key
* processing as soon as the key has been read from the device. */
@Override
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);
}
|
mPolicy是WindowManageService對象在初始化時創建的PhoneWindowManager對象,因此,最終調到了PhoneWindowManager的interceptKeyBeforeQueueing
interceptKeyBeforeQueueing作用:當按鍵事件從設備中讀取后,對按鍵進行最早期攔截預處理,因為某些特殊按鍵直接影響設備狀態,比如,電源鍵、喚醒鍵,此外,還包括撥號鍵、掛號鍵、音量鍵等
7.2 interceptKeyBeforeDispatching
在3.1節提到過doInterceptKeyBeforeDispatchingLockedInterruptible函數,這也是攔截按鍵方法
7.2.1 InputDispatcher的doInterceptKeyBeforeDispatchingLockedInterruptible
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
CommandEntry* commandEntry) {
KeyEntry* entry = commandEntry->keyEntry;
KeyEvent event;
initializeKeyEvent(&event, entry);
mLock.unlock();
nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
&event, entry->policyFlags);
mLock.lock();
if (delay < 0) {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
} else if (!delay) {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
} else {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
entry->interceptKeyWakeupTime = now() + delay;
}
entry->release();
}
|
在1.1.5節提到,NativeInputManager傳遞過來后賦給了mPolicy變量,所以:
1
|
mPolicy->interceptKeyBeforeDispatching
|
調到了NativeInputManager中的interceptKeyBeforeDispatching,經過與7.1節interceptKeyBeforeQueueing類似的調用過程,interceptKeyBeforeDispatching函數最終會調用到PhoneWindowManager中同名方法。
interceptKeyBeforeDispatching作用:在input dispatcher thread把按鍵分發給窗口之前攔截,根據某種策略決定如何處理按鍵事件。
具體策略為:
如果返回值delay為-1,interceptKeyResult設置為INTERCEPT_KEY_RESULT_SKIP,表示按鍵事件已經消耗掉,不需要傳給應用程序;
如果delay等於0,interceptKeyResult設置為INTERCEPT_KEY_RESULT_CONTINUE,表示按鍵事件沒有特殊處理,繼續傳遞給應用程序;
如果delay大於0,interceptKeyResult設置為INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,表示按鍵事件需要重新執行一次,同時設置按鍵下一次執行時間為now() + delay
interceptKeyBeforeDispatching中攔截的特殊按鍵有:HOME, MENU, SEARCH, SOURCE通道, 亮度調節等
7.3 PhoneWindow的onKeyDown/onKeyUp
在6.4節已經分析過,針對當前獲得焦點的窗口進行處理
7.4 PhoneFallbackEventHandler的dispatchKeyEvent
在5.5節,有這樣一句:
1
2
3
4
|
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
|
mFallbackEventHandler就是PhoneFallbackEventHandler對象,這是最后攔截特殊按鍵進行處理的方法,與 PhoneWindow區別的是,PhoneFallbackEventHandler針對所有窗口的
7.5 小結
按鍵事件的特殊處理可用邏輯執行順序簡單表達:
PhoneWindowManager的interceptKeyBeforeQueueing —->PhoneWindowManager的interceptKeyBeforeDispatching —-> PhoneWindow的onKeyDown/onKeyUp —-> PhoneFallbackEventHandler的dispatchKeyEvent
這個邏輯不是必須的,主要看按鍵是否消耗、按鍵的具體功能需求,有時候只需要在PhoneWindowManager中處理即可
8 總結
8.1 按鍵事件傳遞流程總結
a. InputReaderThread獲取輸入設備事件后過濾,保存到InboundQueue隊列中
b. InputDispatcherThread從InboundQueue取出數據,先進行特殊處理,然后找到獲得焦點的窗口,
再把數據臨時放到OutboundQueue中,然后取出數據打包后發送到服務端socket
c. 應用程序主線程中的WindowInputEventReceiver從客戶端socket上讀取按鍵數據,再傳遞給應用層
d. 應用層獲取到事件后先分發給view樹處理,再處理回退事件
8.2 輸入事件核心組件
InputManagerService:輸入事件的服務端核心組件,直接創建看門狗Watchdog、NativeInputManager對象,間接創建EventHub、InputManager以及InputDispatcherThread、InputReaderThread線程,通過InputManager方法間接啟動接收器線程、分發器線程;對系統特殊按鍵的處理;注冊服務端管道
NativeInputManager:本地InputManager,創建c++層InputManager、EventHub對象
C++ 層InputManager:創建InputDispatcher、InputReader對象,創建並啟動InputDispatcherThread、InputReaderThread線程
Java層InputManager:提供輸入設備信息、按鍵布局等
InputReader:輸入事件接收者,從EventHub中獲得原始輸入事件信息
InputDispatcher:分發事件給應用層,發送事件的服務端
EventHub:事件的中心樞紐,收集了所有輸入設備的事件,包括虛擬仿真設備事件。
InputEventReceiver:接收輸入事件的客戶端
InboundQueue:InputReaderThread讀取事件后保存到InboundQueue中等到InputReaderThread接收
outboundQueue:InputDispatcherThread從InboundQueue中取出數據放到outboundQueue中等待發送,然后從outboundQueue取出數據發送到服務端InputChannel等待應用層(或者稱為客戶端)接收
c++層InputChannel:一個輸入通道,包含一對本地unix socket對象,服務端InputDispatcherThread向socket對象寫入數據,客戶端InputEventReceiver從客戶端socket讀取數據
8.3 按鍵分類
一般按鍵:一般會傳遞到應用程序中進行處理,比如,在app中經常調用菜單鍵彈出菜單,按下方向鍵使得焦點移動等;主要有數字鍵,方向鍵,確認OK鍵,返回BACK鍵,菜單MENU鍵等;
特殊按鍵:一般不傳遞到應用程序中,直接在framework層處理,不與某個應用有直接的關聯,應用范圍適用整個系統,比如,HOME鍵,按下HOME就返回到桌面,不限制於某個應用;音量鍵,控制系統音量,不特別針對某個應用;主要有電源POWER鍵,HOME鍵,音量VOLUME鍵,靜音MUTE鍵等;
TV一般按鍵:在TV的應用中處理的鍵,比如,在DTV的Activity中通過EPG調用節目信息,通過SUBTITLE調用字幕界面等;包含INFO信息鍵,喜愛節目FAV鍵,EPG鍵,字幕SUBTITLE,音軌TRACK鍵,多媒體播放鍵等;
TV特殊按鍵:不傳遞到TV的應用中,在framework層進行處理,比如SOURCE通道鍵,在任何界面下按下SOURCE都能夠調出通道界面切換通道。
在Android手機上,常見的一般按鍵是MENU、BACK,特殊按鍵包括電源鍵,HOME鍵,音量鍵等
在TV上,特殊按鍵主要指HOME鍵,SOURCE鍵,電源鍵,音量鍵等
8.4 工作記錄
a. 如果主界面包括epg,channellist, TV多個窗口,需要在這些小界面上跳動焦點,如何實現
重寫Activity的dispatchkeyEvent和onKeyDown或onKeyUp,控制某個小界面獲得焦點
b. 如果app中按鍵不響應,通過getEvent命令檢查是否有輸入設備,是否能夠獲得按鍵事件,如果能,可以確保底層沒問題,問題出現在應用層或Framework層,檢查PhoneWindowManager, dispatchKeyEvent;如果getEvent檢查不到設備,檢查驅動(前提是確保硬件等正常);如果有設備,但getEvent獲取不到事件,檢查系統能否接收到紅外信號,同時確定kl文件是否配對。
c. 如要在任何app下都可以啟動、殺死某個應用,可在PhoneWindowManager或PhoneFallbackEventHandler中對按鍵進行特殊處理,采用該應用的包名、類名,直接控制該應用
轉自:feeyan