现在的位置: 首页 > 综合 > 正文

StevGuo系列文章翻译之Android中的输入事件如何分发

2013年05月17日 ⁄ 综合 ⁄ 共 3982字 ⁄ 字号 评论关闭

原文:http://blog.csdn.net/a345017062/article/details/6083063

输入事件分发的源头在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键盘的话,会使用这个监听器。

抱歉!评论已关闭.