您的位置:首页 > 移动开发 > Android开发

Android键盘事件处理流程

2017-03-21 15:54 363 查看
——-可跳过:键值映射关系start—–

通过cat /proc/bus/input/devices,查看系统中的所有输入设备,例如:

$ cat /proc/bus/input/devices

I: Bus=0018 Vendor=0019 Product=0001 Version=0001

N: Name=”Smart IR Receiver”

P: Phys=/dev/ir

S: Sysfs=/devices/virtual/rc/rc0/input0

U: Uniq=

E: Enabled=0

H: Handlers=kbd event0

B: PROP=0

B: EV=13

B: KEY=3ffe000000000000 0 1000 e880000000000 1e80000 fc100 210285000000000 0 2100004 80000040000800 1e16c00180001f 6880000010000ffc

B: MSC=10

……

假如我们关注第一个输入设备,通过I: Bus=0018 Vendor=0019 Product=0001 Version=0001,我们知道这个输入设备使用的映射文件是:/system/usr/keylayout/Vendor_0019_Product_0001.kl,这个文件定义了从输入设备中读入的扫描码,与我们自定义的字符间的映射关系,可以这么说,扫描码是输入设备能读懂的,而转换后的字符是给Android上层看的。

这个映射关系,将在EventHub读取到输入设备的按键事件后,将键值转换时使用到。

——-可跳过:键值映射关系end—–

简略流程:

1 zygote在系统系统时启动WindowManagerService

2 WindowManagerService进程启动后,会new InputManager() 、Native InputManager(), Native InputManager将会创建EventHub()实例, 并且启动两个线程:InputReaderThread、InputDispatcherThread,其中InputReaderThread线程负责从EventHub中不断读取按键事件,InputDispatcherThread负责向活动的Activity窗口分发按键事件。

2 Activity注册按键事件监听:通过ViewRoot.setView(),通过WMS将window注册到native InputManager InputDispatcher中,InputManager保存着所有具有InputManager Channel的window,并通过判定是否具有focus来确定活动窗口。

3 InputReaderThread线程:通过EventHub.getEvent()读取按键事件,此时EventHub读取输入设备/dev/input/*,并通过map转换为kl中定义的Android字符串,返回给native InputManager。

4(InputDispatcher.cpp)按键事件发生后,通过InputDispatcher.notifyKey将key封装为KeyEntry,插入到mInboundQueue中,并且放在mPendingEvent变量中(TYPE_KEY)。

5 (InputTransport.cpp)将该Key通过channel(匿名共享内容),唤醒 channel client (通过发送signal)。

6 最终通过jni 交给mInputChanel(ViewRootImpl)处理

7 ViewRootImpl处理:

//ViewRootImpl.java
final class ViewPostImeInputStage extends InputStage {
......
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
}
......
}
}

private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
......
}


这里的mView,即为DecorView

8 DecorView.dispatchKeyEvent()

//DecorView.java  dispatchKeyEvent()
......
if (!mWindow.isDestroyed()) {
final Window.Callback cb = mWindow.getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}


其中,Window.Callback cb可以是Activity或者Dialog的cb,Activity是在attach的时候设置了该cb为自己:

//Activity.java
......
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
......
}


因此,cb一般不为空,此时调用:Activity.dispatchKeyEvent()

//Activity.java
public boolean dispatchKeyEvent(KeyEvent event) {
......
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);
}


这里,win则是PhoneWindow,跟踪可知:

PhoneWindow.superDispatchKeyEvent()

-》DecorView.superDispatchKeyEvent()

return super.dispatchKeyEvent(event);


DecorView并没有处理,而是交给了父类,也就是ViewGroup去处理。

ViewGroup.dispatchKeyEvent()

//ViewGroup.java

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
......
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;
}
}
......
return false;
}


这里关注两个if,第一个if判断当前ViewGroup是否拿着焦点,或者有固定大小的边界,如果为真,则交给父类(即View类)处理;如果为假,或者父类不处理,走第二个if:当前ViewGroup是否包含拿到焦点,并且有固定大小的View,如果有,则直接交给该焦点View处理。

/* mPrivateFlags– view标志位, 可以标记view当前的多种状态:

PFLAG_CANCEL_NEXT_UP_EVENT:是否cancle下一次up事件

PFLAG_DRAW_ANIMATION:view是否包含动画

PFLAG_INVALIDATED:当前view已请求invalidate

PFLAG_DRAWN:

PFLAG_FORCE_LAYOUT:强制layout,该位置位表示需要重新测量onMeasure

PFLAG_FOCUSED:当前view是否拿到焦点

PFLAG_IS_ROOT_NAMESPACE:

PFLAG_HAS_BOUNDS:是否有确定大小的边界

……

*/

通常,第一个if返回为真,那么,关注View.dispatchKeyEvent():

//View.java

public boolean dispatchKeyEvent(KeyEvent event) {
......
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}

if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
......
return false;


从代码可以看到,如果当前View注册了OnKeyListener,那么首先会将key事件分发到l.onKey(),如果onKey返回false,则继续分发给event.dispatch()

//KeyEvent.java

public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
...
boolean res = receiver.onKeyDown(mKeyCode, this);
...
return res;
}
case ACTION_UP:
......
}
return false;
}


keyevent会将key事件分类,分别回调View的onKeyDown等方法(判断是否点击等,不分析,假设此时返回false)。

回到之前的两个if,因为此时是DecorView是第一次走这个流程,通常第一个if就返回false,那么,分发流程就会走第二个if:

} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}


假如此时mFocused是ViewGroup,那么,又将重新走一遍流程:如果是当前ViewGroup拿到的焦点(而不是所包含的view拿到的焦点),那么,就看该ViewGroup是否重写了onKeyDown()等;如果是包含的View拿到的焦点,那么就循环,直到找到处理key事件的View。当然,如果均没有处理,则返回到最开始,由PhoneWindow收尾处理。

总结下:

1 树结构上层(有焦点或者固定大小)的View0(或ViewGroup0)注册的onKeyListener(重写onKey())优先级 > View0(或ViewGroup0)重写onKeyDown()等 优先级 > 树结构下层 (有焦点或着固定大小)的 View1(或ViewGroup1)注册的onKeyListener(重写onKey())优先级 > View1(或ViewGroup1)重写onKeyDown()等 优先级 > 有焦点并且有固定大小的viewGroup2 注册的onKeyListener(重写onKey())优先级 > ViewGroup2重写onKeyDown()等 优先级 > 有焦点并且有固定大小的view3 注册的onKeyListener(重写onKey())优先级 > View3重写onKeyDown()等 优先级 > Activity重写的onKeyDown()

2 ViewGroup是靠找焦点来完成Key事件的分发循环的

以上为个人见解,如果错误,请指正
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android