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

来源:互联网 发布:阿里妈妈app 淘宝客 编辑:程序博客网 时间:2024/05/16 01:50


输入事件分发的源头在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);

在最底层,AndroidLinux输入设备中读取真正的事件,相应的代码在EventHub.cpp中。对按键事件来说,Android通过一个按键键盘布局映射表文件把scan code转换成key codeOEM需要根据自己的设备更改这个键盘布局映射表文件。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.cppJNI实现。

 

当一个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创建一个代表这个activityPhoneWindow.java实例。Activity中每一个PhoneWindow.java包含一个DecorView.java实例作为View树的根:

mWindow = PolicyManager.makeNewWindow(this);

        mWindow.setCallback(this);

activity被创建后,ActivityManagerService.java调用ActivityThread.javaresume这个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会调用它的setViewDecorView.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键盘的话,会使用这个监听器。