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

Android按键事件传递流程(二)的更多相关文章

  1. Android Touch事件传递机制 二:单纯的(伪生命周期)

    转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...

  2. Android Touch事件传递机制 二:单纯的(伪生命周期) 这个清楚一点

    转载于:http://blog.csdn.net/yuanzeyao/article/details/38025165 在前一篇文章中,我主要讲解了Android源码中的Touch事件的传递过程,现在 ...

  3. Android webkit 事件传递流程详解

    前言:基于android webview 上定制自己使用的可移植浏览器apk,遇到好多按键处理的问题.所以索性研究了一下keyevent 事件的传递流程. frameworks 层 keyevent ...

  4. Android webkit 事件传递流程通道分析

    前言:基于android webview 上定制自己使用的可移植浏览器apk,遇到好多按键处理的问题.所以索性研究了一下keyevent 事件的传递流程. frameworks 层 keyevent ...

  5. Android Touch事件传递机制 一: OnTouch,OnItemClick(监听器),dispatchTouchEvent(伪生命周期)

      ViewGroup View  Activity dispatchTouchEvent 有 有 有 onInterceptTouchEvent 有 无 无 onTouchEvent 有 有 有 例 ...

  6. Android touch 事件传递机制

    前言: (1)在自定义view的时候经常会遇到事件拦截处理,比如在侧滑菜单的时候,我们希望在侧滑菜单里面有listview控件,但是我们希望既能左右滑动又能上下滑动,这个时候就需要对触摸的touch事 ...

  7. Android Touch事件传递机制详解 下

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...

  8. Android Touch事件传递机制具体解释 下

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38025165 资源下载:http://download.csdn.net/detail/yu ...

  9. 【转】Android TouchEvent事件传递机制

    Android TouchEvent事件传递机制   事件机制参考地址: http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html ht ...

随机推荐

  1. Codeforces Round #336 (Div. 2) A. Saitama Destroys Hotel 模拟

    A. Saitama Destroys Hotel   Saitama accidentally destroyed a hotel again. To repay the hotel company ...

  2. linux c first

    创建文件夹mkdir 文件夹名称删除文件夹rm -rf 文件夹名称创建文件touch test.c删除文件rm -f test.c编译gcc -o test test.c在执行只语句后会生成一个*te ...

  3. oci.dll文件是用来干嘛的? 如果没有安装ORACLE客户端提示oci.dll未加载

    oracle数据库开发编程中,没有找到oci.dll,一般是系统的 path 设置有问题, 查找oci.dll, 然后加入到系统路径.oci.dll 可下载解压到系统盘的system32目录下.然后打 ...

  4. VCC、VDD、VEE、VSS等有关电源标注的区别

    Almost all integrated circuits (ICs) have at least two pins which connect to the power rails of the ...

  5. 更新SDK后ADT版本低不支持

    在android_sdk_windows/tools/lib下的plugin.prop文件里被变更为 # begin plugin.prop plugin.version=21.1.0 # end p ...

  6. 机器人学 —— 轨迹规划(Configuration Space)

    之前的轨迹规划中,我们只考虑了质点,没有考虑机器人的外形与结构.直接在obstacle map 中进行轨迹规划,然而世纪情况中,机器人有固定外形,可能会和障碍物发生碰撞.此情况下,我们针对机器人自由度 ...

  7. java读取package中的properties文件java.util.MissingResourceException

    文件结构: /build/classes/d914/Hello.class /build/classes/d914/mess.properties /build/classes/d914/mess_z ...

  8. spring springmvc mybatis 整合

    环境 apache-tomcat-8.0.33.jdk1.8.0_05 maven Dynamic Web Module 2.5 1.各个xml配置文件的配置 (1)pom.xml 配置清单文件 连接 ...

  9. linux下svn的常用代码【转】

    转自:http://www.2cto.com/os/201205/130192.html 1.将文件checkout到本地目录 svn checkout path(path是服务器上的目录)    例 ...

  10. linux中/etc/init.d [转]

    一.关于/etc/init.d 如果你使用过linux系统,那么你一定听说过init.d目录.这个目录到底是干嘛的呢?它归根结底只做了一件事情,但这件事情非同小可,是为整个系统做的,因此它非常重要.i ...