输入事件分发的源头在WindowManagerService.java中,它创建了一个线程从KeyInputQueue.java中读取输入事件并通过Binder分发给当前聚焦的Window:
// Retrieve next event, waiting only as long as the next repeat timeout. If the configuration has changed, then don't wait at all -- we'll report the change as soon as we have processed all events.
QueuedEvent ev = mQueue.getEvent(
(int)((!configChanged && curTime < nextKeyTime)
? (nextKeyTime-curTime) : 0));
读取到一个输入事件后,它会判断事件的类型,并根据类型调用相应的分发方法分发到Window。现在只支持三种类型:按键、轨迹球和触摸。例如,对于按键事件来说,它调用以下代码:
focus.mClient.dispatchKey(event);
在最底层,Android从Linux输入设备中读取真正的事件,相应的代码在EventHub.cpp中。对按键事件来说,Android通过一个按键键盘布局映射表文件把scan code转换成key code。OEM需要根据自己的设备更改这个键盘布局映射表文件。OEM使用下面的方法找到键盘布局映射表文件:
// a more descriptive name
ioctl(mFDs[mFDCount].fd, EVIOCGNAME(sizeof(devname)-1), devname);
devname[sizeof(devname)-1] = 0;
device->name = devname;
// replace all the spaces with underscores
strcpy(tmpfn, devname);
for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' '))
*p = '_';
// find the .kl file we need for this device
const char* root = getenv("ANDROID_ROOT");
snprintf(keylayoutFilename, sizeof(keylayoutFilename),
"%s/usr/keylayout/%s.kl", root, tmpfn);
bool defaultKeymap = false;
if (access(keylayoutFilename, R_OK)) {
snprintf(keylayoutFilename, sizeof(keylayoutFilename),
"%s/usr/keylayout/%s", root, "qwerty.kl");
defaultKeymap = true;
}
device->layoutMap->load(keylayoutFilename);
OEM可以在Android启动时得到键盘布局映射表文件,因为Android会在启动时打印它的名字。Java层对它的包装是KeyInputQueue.java,而KeyInputQueue.java是供WindowManagerService.java使用的。KeyInputQueue.java通过JNI调用EventHub.cpp。而com_android_server_KeyInputQueue.cpp是JNI实现。
当一个Activity启动时,ActivityManagerService.java调用ActivityThread.java创建activity:
activity.attach(appContext, this, getInstrumentation(), r.token, app,
r.intent, r.activityInfo, title, r.parent, r.embeddedID,
r.lastNonConfigurationInstance, config);
接下来,Activity.java创建一个代表这个activity的PhoneWindow.java实例。Activity中每一个PhoneWindow.java包含一个DecorView.java实例作为View树的根:
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
当activity被创建后,ActivityManagerService.java调用ActivityThread.java来resume这个activity。这时,ActivityThread.java调用WindowManagerImpl.java添加DecorView.java:
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;
wm.addView(decor, l);
WindowManagerImpl.java创建了一个ViewRoot.java实例。ViewRoot有一个对每个进程初始化一次的静态成员。通过它,WindowManagerService.java可以知道,现在有一个进程被连接了:
if (!mInitialized) {
try {
sWindowSession = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"))
.openSession(new Binder());
mInitialized = true;
} catch (RemoteException e) {
}
}
ViewRoot.java实例被创建后,WindowManagerImpl.java会调用它的setView为DecorView.java绑定ViewRoot.java:
// do this last because it fires off messages to start doing things
root.setView(view, wparams, panelParentView);
在setView中,ViewRoot.java最后会渲染DecorView.java,然后注册一个IWindow实例到WindowManagerService.java中:
res = sWindowSession.add(mWindow, attrs,
getHostVisibility(), mCoveredInsets);
接下来,WindowManagerService.java直接与ViewRoot.java中的IWindow实例通信。然后,ViewRoot.java调用View.java处理输入事件。例如,对按键事件来说,View中的dispatchKeyEvent会被调用:
public boolean dispatchKeyEvent(KeyEvent event) {
// If any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
return event.dispatch(this);
}
View.java检查是否有按键监听器注册到了这个view。如果有的话,按键事件会被监听器处理。否则,就调用onKeyDown/onKeyUp。
所有的按键监听器的实现都在/frameworks/base/core/java/android/text/method文件夹中:
MultiTapKeyListener.java:如果键盘是数字键盘的话,这个监听器可以把数字输入转化成字符。
QwertyKeyListener.java:如果键盘是QWERTY键盘的话,会使用这个监听器。