android 4.4 按键分析三

原创 2014年10月11日 12:21:57

.5         Android Framework层消息处理

 

3.5.1         基本介绍

关于Android消息处理机制的全面分析,可参考另外的文档,这里着重介绍事件处理相关问题,作为按键处理的基础知识。

 

 

3.5.2         Looper的事件交付

Looper维护线程的消息队列,它将消息队列里面的消息分发出去,它有两种分发消息的方式,优先使用native消息队列的事件分发方式,然后使用handler进行事件分发。

 

3.5.2.1                                  Handler分发

 

Loop是Looper功能的主要方法,它是一个无限消息循环,负责处理消息队列里面的消息,它先从消息队列取出消息,再使用消息数据里的handler,由handler完成对消息的处理,最后将消息释放。Loop的主要处理代码如下,

    public static void loop() {

        final Looper me = myLooper();

        final MessageQueue queue = me.mQueue;    

        for (;;) {

            Message msg = queue.next(); // might block

           msg.target.dispatchMessage(msg);

          msg.recycle();

        }

    }

 

3.5.2.2                                  Native分发

MessageQueue.next被looper.loop()调用,返回消息队列里面的消息,它里面也是一个消息循环。分析其代码,有三大部分组成,第一个是调用nativePollOnce,将native消息队列里面的消息发送出去,这个消息不经过loop处理,第二部分是将使用handler创建的异步消息获取到,返回给loop,由loop处理,具体过程见Looper的分析,第三部分是idle handler的处理,我们暂时不进行分析。

    final Message next() {

        for (;;) {

            if (nextPollTimeoutMillis != 0) {

                Binder.flushPendingCommands();

            }

            nativePollOnce(mPtr, nextPollTimeoutMillis); //将native消息队列里面的消息发送出去

                final long now = SystemClock.uptimeMillis();

                Message prevMsg = null;

                Message msg = mMessages;

                if (msg != null && msg.target == null) {

                    // Stalled by a barrier.  Find the next asynchronous message in the queue.

                    do {

                        prevMsg = msg;

                        msg = msg.next;

                    } while (msg != null && !msg.isAsynchronous());//遍历,获取到异步消息

                }

                if (msg != null) {

                    if (now < msg.when) { //判断当前异步消息是否准备好,

                        // Next message is not ready.  Set a timeout to wake up when it is ready.

                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

                    } else {

                        // Got a message. //消息可以处理,从队列取出,设置flag,并返回

                        mBlocked = false;

                        if (prevMsg != null) {

                            prevMsg.next = msg.next;

                        } else {

                            mMessages = msg.next;

                        }

                        msg.next = null;

                        if (false) Log.v("MessageQueue", "Returning message: " + msg);

                        msg.markInUse();

                        return msg; //返回正常处理的消息

                    }

                } else {

                    // No more messages.

                    nextPollTimeoutMillis = -1;

                }

…  //后面是idle handler的处理,暂不分析

}    

 

其中,nativePollOnce这个native方法调用android_os_MessageQueue.java类的方法android_os_MessageQueue_nativePollOnce,后者通过参数ptr,找到对应线程的NativeMessageQueue,再调用native消息队列的方法nativeMessageQueue->pollOnce,pollOnce方法最终调用到native Looper的pollOnce,Looper->pollOnce调用pollInner完成消息处理。

 

在pollInner 里mMessageEnvelopes是一个消息封装队列,从中逐个取出消息,使用消息携带的handler将消息发送出去,这点和java的消息队列实现逻辑是一样的。例如对于触屏事件,native Looper将消息发送给java的ViewRootImpl,

ViewRootImpl$WindowInputEventReceiver(InputEventReceiver).dispatchInputEvent(int, InputEvent) line: 179  

MessageQueue.nativePollOnce(int, int) line: not available [native method]      

MessageQueue.next() line: 128    

Looper.loop() line: 151 

 

从上面可以总结出,MessageQueue.next优先使用nativePollOnce处理native消息,然后再从java的消息队列取出消息返回给Looper.loop处理。

 

 

3.5.2.3                                  思考

普通的java层的消息的目的线程怎么被调度激活,并执行它?

物理事件对应的消息怎么传递到目的线程,线程怎么被调度执行?

Java消息和物理事件在同一个线程里面的执行时的关系?

线程的消息循环是怎样重启动的?(因为在某些情况下,消息循环会退出。)

 

 

3.5.3         Handler的事件交付

当looper通过handler进行事件分发时,根据如下代码,有三种分发方式:通过消息携带的callback,通过构造handler实例时初始的mCallback和handler实例自身的handleMessage方法。

     public void dispatchMessage(Message msg) {

        if (msg.callback != null) {

            handleCallback(msg);

        } else {

            if (mCallback != null) {

                if (mCallback.handleMessage(msg)) {

                    return;

                }

            }

            handleMessage(msg);

        }

    }

 

3.5.3.1                                  Callback方式

 

对于第一种,它调用handleCallback,里面则是调用callback对应实例的run方法来完成事件处理,run方法由每个对象自己实现。

     private static void handleCallback(Message message) {

        message.callback.run();

    }

 

这么说比较抽象,举例说明。

进入短信设置,在进入文本短信,最后到SIM卡的短信列表界面,点击,通过调试功能,可以得到下面的调用栈:

MessageListItem.onMessageListItemClick() line: 1387      

MessageListItem$3$1.run() line: 983   

Handler.handleCallback(Message) line: 808 

Handler.dispatchMessage(Message) line: 103      

Looper.loop() line: 193 

 

在Handler.handleCallback方法里的变量Message对应的数据如下,它属于MessageListItem类。

msg Message  (id=830034712544)     

       arg1 0    

       arg2 0    

       callback   MessageListItem$3$1  (id=830038883048)

 

通过调用栈和代码,我们可以看到, 点击事件经过handler的dispatchMessage ,使用Callback方式向上传递给MessageListItem类的run方法处理,这里有一个问题,就是Callback怎样和MessageListItem类关联起来的。

MessageListItem类在设置监听函数时,实现了一个Runnable对象,并用postDelayed的方式发送出去,

    setOnClickListener(new OnClickListener(){

            @Override

            public void onClick(View v) {

                mZoomerHandler.postDelayed(new Runnable(){                  

                    public void run() {

                        if(isLongClick) {

                            setIsLongClick(false);

                        return;

                        }

                        onMessageListItemClick();

                    }

                }, ViewConfiguration.getDoubleTapTimeout());

              }

           });*/

 

进一步查看postDelayed的相关代码,通过下面代码我们就可以知道,Callback就是MessageListItem类在设置监听函数时实现的Runnable对象的实例。

     public final boolean postDelayed(Runnable r, long delayMillis)

    {

        return sendMessageDelayed(getPostMessage(r), delayMillis);

    }

 

    private static Message getPostMessage(Runnable r) {

        Message m = Message.obtain();

        m.callback = r;

        return m;

    }

 

从上面我们可以总结出,handler进行事件分发时,优先判断消息是否是携带callback,如果是,则使用callback处理消息。Callback的设置则是通过getPostMessage来实现的。(其实,这个结论我们可以通过分析handler的代码直接得出,但我们举例之后,能更直观理解这个过程。)

 

3.5.3.2                                  mCallback方式

 

2)mCallback方式

 

 

3.5.3.3                                  handleMessage方式

 

handleMessage方式是我们最常用的方式,在activity、view等各种应用的实体类里面,创建一个handler对象,重载handleMessage方法,就可以处理发给这个handler的消息了。给handler发送消息使用sendMessage方法,

 

例如,加载Activity的过程,ActivityManagerService通过调用ApplicationThread类的scheduleLaunchActivity函数通知应用程序,它可以加载应用程序的默认Activity了,而ApplicationThread类的scheduleLaunchActivity函数最终把这个请求封装成一个消息,然后通过ActivityThread类的成员变量mH来把这个消息加入到应用程序的消息队列中去。现在要对这个消息进行处理了,于是就会调用H类的dispatchMessage函数进行处理。H类没有实现自己的dispatchMessage函数,但是它继承了父类Handler的dispatchMessage函数,,于是,就会调用Handler类的handleMessage函数来处理这个消息,由于H类在继承Handler类时,重写了handleMessage函数,因此,这里调用的实际上是H类的handleMessage函数,这个函数定义在frameworks/base/core/java/android/app/ActivityThread.java文件中:

        public void handleMessage(Message msg) {

            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));

            switch (msg.what) {

                case LAUNCH_ACTIVITY: {

                    /// M: enable profiling @{

                    if ( true == mEnableAppLaunchLog && !mIsUserBuild && false == mTraceEnabled ) {

                        try {

                            FileInputStream fprofsts_in = new FileInputStream("/proc/mtprof/status");

                            if ( fprofsts_in.read()== '3' ) {

                                Log.v(TAG, "start Profiling for empty process");

                                mTraceEnabled = true;

                                Debug.startMethodTracing("/data/data/applaunch"); //applaunch.trace

                            }

                        } catch (FileNotFoundException e) {

                            Slog.e(TAG, "mtprof entry can not be found", e);

                        } catch (java.io.IOException e) {

                            Slog.e(TAG, "mtprof entry open failed", e);

                        }

                    }

                    /// @}

                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER | Trace.TRACE_TAG_PERF, "activityStart"); /// M: add TRACE_TAG_PERF for performance debug

                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;

 

                    r.packageInfo = getPackageInfoNoCheck(

                            r.activityInfo.applicationInfo, r.compatInfo);

                    handleLaunchActivity(r, null);

                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER | Trace.TRACE_TAG_PERF); /// M: add TRACE_TAG_PERF for performance debug

                } break;

}

 

对于加载过程,执行LAUNCH_ACTIVITY case的代码。

 

 

 

 

 

3.6         InputManager

Android系统的键盘事件是由InputManager来监控的,我们先介绍和它相关的知识。

 

     在系统启动的时候,SystemServer会启动窗口管理服务WindowManagerServiceWindowManagerService会通过系统输入管理器InputManager来总负责监控键盘消息。这些键盘消息一般都是分发给当前激活的Activity窗口来处理的,因此,当前激活的Activity窗口在创建的时候,会到WindowManagerService中去注册一个接收键盘消息的通道,表明它要处理键盘消息,而当InputManager监控到有键盘消息时,就会分给给它处理。当当前激活的Activity窗口不再处于激活状态时,它也会到WindowManagerService中去反注册之前的键盘消息接收通道,这样,InputManager就不会再把键盘消息分发给它来处理。

 

我们先分析InputManager是如何启动的。

 

3.6.1         InputManager初始化

我们知道在Android系统中,Zygote进程负责启动系统服务进程SystemServer,而系统服务进程SystemServer负责启动系统中的各种关键服务,在4.4代码中,有几十种这样的服务,InputManager是其中一种,在SystemServer.java文件里面,当执行initAndLoop(),则会启动InputManager的服务,

            Slog.i(TAG, "Input Manager");

            inputManager = new InputManagerService(context, wmHandler);

 

            Slog.i(TAG, "Window Manager");

            wm = WindowManagerService.main(context, power, display, inputManager,

                    wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,

                    !firstBoot, onlyCore);

            ServiceManager.addService(Context.WINDOW_SERVICE, wm);

            ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

 

            ActivityManagerService.self().setWindowManager(wm);

 

            inputManager.setWindowManagerCallbacks(wm.getInputMonitor());

            inputManager.start();

 

            display.setWindowManager(wm);

            display.setInputManager(inputManager);

 

从代码可以看到,inputManager服务实际上是一个InputManagerService实例,在2.3代码中,是没有这样一个类的,这是在4.0之后对inputManager进行的代码框架上的修改。

可以看到在InputManagerService创建后,先和WMS关联起来,再设置,然后调用start()方法启动InputManagerService,最后还可以看到InputManagerService还和display关联起来了。具体的分析我们在后面逐步介绍。

 

首先,InputManagerService继承自IInputManager.Stub,那它就是一个Binder对象,这样它就便于通过RPC进行跨进程调用,也方便IPC进行进程间通信。

另外,InputManagerService实现中,使用了很多native方法,主要功能都是通过native方法完成,它将InputManager相关的native方法全部封装到这里,形成一个service层,避免app层的代码使用native方法,符合软件分层结构的设计方法,这也是和2.3代码相区别的地方。

在其构造方法里,新建了一个InputManagerHandler,并使用了nativeInit将自己和jni层关联起来,并初始化了jni层的InputManager。

     public InputManagerService(Context context, Handler handler) {

        this.mContext = context;

        this.mHandler = new InputManagerHandler(handler.getLooper());

        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

    }

 

在start()方法里,使用nativeStart()启动了jni层的事件处理方法,并给自己定义了一个广播接收器。

 

对于和WMS的关系,则在WMS服务创建时,将自身引用传递进去,赋值给WMS的成员变量mInputManager,mInputManager在WMS里有两处比较重要,

一个是在PointerEventDispatcher构造函数参数里调用nativeRegisterInputChannel注册InputChannel,

mPointerEventDispatcher = new PointerEventDispatcher(mInputManager.monitorInput(TAG));

一个是在addWindow方法里注册InputChannel,

mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);

 

 

顺便提一下InputManager,它在ContextImpl.java里注册了INPUT_SERVICE服务,并在Current.txt (frameworks\base\api):    field public static final java.lang.String INPUT_SERVICE = "input";声明服务,

         registerService(INPUT_SERVICE, new StaticServiceFetcher() {

                public Object createStaticService() {

                    return InputManager.getInstance();

                }});

 

InputManager使用静态方法InputManager.getInstance创建实例,其中用ServiceManager.getService获取服务,和前面的InputManagerService初始化时进行服务添加的ServiceManager.addService(Context.INPUT_SERVICE, inputManager);相对应,这样使用Binder的向上转型,就和IMS关联起来了,通过成员变量mIm,应用能够对WMS进行访问。这就是一个典型的RPC调用了。

     public static InputManager getInstance() {

        synchronized (InputManager.class) {

            if (sInstance == null) {

                IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);

                sInstance = new InputManager(IInputManager.Stub.asInterface(b));

            }

            return sInstance;

        }

    }

 

 

我们再进一步看一下nativeInit,函数定义在com_android_server_InputManager.cpp文件中,

 static jint nativeInit(JNIEnv* env, jclass clazz,

        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {

    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);

    if (messageQueue == NULL) {

        jniThrowRuntimeException(env, "MessageQueue is not initialized.");

        return 0;

    }

 

    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,

            messageQueue->getLooper());

    im->incStrong(0);

    return reinterpret_cast<jint>(im);

}

它获取一个jni层的消息队列messageQueue,并创建一个NativeInputManager实例,NativeInputManager构造函数里创建了一个EventHub实例,并且把这个EventHub作为参数来创建InputManager对象。这里的InputManager类是定义在C++层的,和前面在Java层的InputManager不一样,不过它们是对应关系。EventHub类是真正执行监控键盘事件操作的地方。

InputManager.cpp 文件中,InputManager类的构造函数里面,创建了一个InputDispatcher对象和一个InputReader对象,并且分别保存在成员变量mDispatchermReader中。InputDispatcher类是负责把键盘消息分发给当前激活的Activity窗口的,而InputReader类则是通过EventHub类来实现读取键盘事件的。需要注意mDispatcher是作为参数传递给InputReader的,这将在决定InputReader读取到消息后,是分发给InputDispatcher的。

 InputManager::InputManager(

        const sp<EventHubInterface>& eventHub,

        const sp<InputReaderPolicyInterface>& readerPolicy,

        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {

    mDispatcher = new InputDispatcher(dispatcherPolicy);

    mReader = new InputReader(eventHub, readerPolicy,mDispatcher);

    initialize();

}

创建了这两个对象后,还要调用initialize函数来执行其它的初始化操作。Initialize创建两个线程,进行事件读取和分发,分别对应InputDispatcherInputReader

void InputManager::initialize() {

    mReaderThread = new InputReaderThread(mReader);

    mDispatcherThread = new InputDispatcherThread(mDispatcher);

}

 

上面就完成了nativeInit的过程,我们再来看看nativeStart的过程。同理,nativeStart会一直调用到InputManager::start,这样线程就运行起来了。

 status_t InputManager::start() {

    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);

    if (result) {

        ALOGE("Could not start InputDispatcher thread due to error %d.", result);

        return result;

    }

 

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);

    if (result) {

        ALOGE("Could not start InputReader thread due to error %d.", result);

 

        mDispatcherThread->requestExit();

        return result;

    }

 

    return OK;

}

 

 

至此,我们对InputManager有了一个基本的认识了,它主要的工作如下

1)在Java层,创建了一个InputManager对象,它访问系统启动创建的一个InputManagerService服务,管理Android应用程序框架层的键盘消息处理;

2) 在C++层也相应地创建一个InputManager本地对象来负责监控键盘事件。

InputManager对象中,分别创建了一个InputReader对象和一个InputDispatcher对象,前者负责读取系统中的键盘消息,后者负责把键盘消息分发出去;

InputReader对象和一个InputDispatcher对象分别是通过InputReaderThread线程实例和InputDispatcherThread线程实例来实键盘消息的读取和分发的;

3)C++层的线程启动时通过Java层来触发的。

4)这个过程和相互关系可以用下面的图来表示。

 

3.6.2         InputManager运行

 

3.6.2.1                                  InputReaderThread

 

InputReaderThread定义在InputReader.cpp,其线程循环代码如下:

 bool InputReaderThread::threadLoop() {

    mReader->loopOnce();

    return true;

}

 

void InputReader::loopOnce() {

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

        if (count) {

            processEventsLocked(mEventBuffer, count);

        }

    // Send out a message that the describes the changed input devices.

    if (inputDevicesChanged) {

        mPolicy->notifyInputDevicesChanged(inputDevices);

    }

}

根据线程的特点,当调用了它们的run函数后,就会进入到它们的threadLoop函数中去,只要threadLoop函数返回true,函数threadLoop就会一直被循环调用。

 

线程调用它的loopOnce成员函数执行一次键盘事件的读取操作。 这里通过成员函数mEventHub来负责键盘消息的读取工作,如果当前有键盘事件发生或者有键盘事件等待处理,通过mEventHubgetEvents函数就可以得到这个事件,然后交给process函数进行处理,这个函数主要就是唤醒InputDispatcherThread线程,通知它有新的键盘事件发生了,;如果没有键盘事件发生或者没有键盘事件等待处理,那么调用mEventHubgetEvent函数时就会进入等待状态。

 

具体的处理流程我们在后面的事件处理流程里分析。

 

 

3.6.2.2                                  InputDispatcherThread

InputDispatcherThread和InputReaderThread类似,在其线程循环里,,调用它的dispatchOnce成员函数执行一次键盘消息分发的操作。它把键盘消息交给dispatchOnceInnerLocked函数来处理,然后调用mLooper->pollOnce函数等待下一次键盘事件的发生。

bool InputDispatcherThread::threadLoop() {

    mDispatcher->dispatchOnce();

    return true;

}

 

void InputDispatcher::dispatchOnce() {

    nsecs_t nextWakeupTime = LONG_LONG_MAX;

    { // acquire lock

        AutoMutex _l(mLock);

        mDispatcherIsAliveCondition.broadcast();

 

        // Run a dispatch loop if there are no pending commands.

        // The dispatch loop might enqueue commands to run afterwards.

        if (!haveCommandsLocked()) {

            dispatchOnceInnerLocked(&nextWakeupTime);

        }

    } // release lock

 

    // Wait for callback or timeout or wake.  (make sure we round up, not down)

    nsecs_t currentTime = now();

    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);

    mLooper->pollOnce(timeoutMillis);

}

 

 

4.       流程分析:APP&Frameworks&HAL

 

4.1         事件处理流程

InputManager启动以后,就开始负责监控键盘输入事件了。当InputManager监控到键盘输入事件时,要把这个键盘消息分发给当前激活的Activity窗口,所以,当前激活的Activity窗口需要主动注册一个键盘消息接收通道到InputManager中去,InputManager才能把这个键盘消息分发给它处理。那么,当前被激活的Activity窗口又是什么时候去注册这个键盘消息接收通道的呢?这些疑问后面逐步分析。

 

 

Android应用程序的启动过程时,当函数handleLaunchActivity调用performLaunchActivity函数来加载这个完毕应用程序的默认Activity后,再次回到handleLaunchActivity函数时,会调用handleResumeActivity函数来使这个Activity进入Resumed状态。在调用handleResumeActivity函数的过程中,ActivityThread会通过android.view.WindowManagerImpl类为该Activity创建一个viewRootImpl实例,并且会通过调用viewRootImpl类的setView成员函数把与该Activity关联的View设置到这个viewRootImpl中去,同时Activity通过ViewRootImpl.java类的setView成员函数来注册键盘消息接收通道的。

 

setView代码如下,对应事件处理,它分为四部分:

第一部分是重新布局,对于按键相关,它是激活为当前窗口,以便接收事件,需要注意的是这里激活的不是当前正在创建的窗口,当前激活的窗口会在Activity启动过程中后续调用requestLayout时被激活。这一点我们会在后续代码中分析到。

第二部分是新建InputChannel,包括java层的和C++层的,并进行注册。

第三部分是在viewRootImpl部分新建事件接收器,让native层的事件能传递到framework层。

第四部分是设置InputStage,它主要是一连串的输入处理节点,根据应用当前状态,找到合适的InputStage,将事件向上传递,如是否存在输入法等状态不同,处理事件的InputStage也会不同,我们在后面的分析再依次介绍。

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

                requestLayout();//第一部分

 

                if ((mWindowAttributes.inputFeatures//第二部分

                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {

                    mInputChannel = new InputChannel();//新建一个channel

                }

                try {

                    mOrigWindowType = mWindowAttributes.type;

                    mAttachInfo.mRecomputeGlobalAttributes = true;

                    collectViewAttributes();

                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

                            getHostVisibility(), mDisplay.getDisplayId(),

                            mAttachInfo.mContentInsets, mInputChannel);//1)向server注册

}

                if (view instanceof RootViewSurfaceTaker) {

                    mInputQueueCallback =

                        ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();

                }

                if (mInputChannel != null) {//第三部分

                    if (mInputQueueCallback != null) {

                        mInputQueue = new InputQueue();//新建一个队列

                        mInputQueueCallback.onInputQueueCreated(mInputQueue);

                    }

                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,

                            Looper.myLooper());//新建一个事件接收器

                }

..

                // Set up the input pipeline.//第四部分,设置InputStage处理

                CharSequence counterSuffix = attrs.getTitle();

                InputStage syntheticInputStage = new SyntheticInputStage();

                InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticInputStage);

                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;

                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;

}

 

 

4.1.1         设置激活窗口

第一部分是设置布局,便于后续激活当前窗口。

从其注释我们知道,这样做的目的是在窗口能接收事件前,我们要将窗口布局好。

 // Schedule the first layout -before- adding to the window

 // manager, to make sure we do the relayout before receiving

 // any other events from the system.

当再次调用时,就可以将当前窗口设置为激活窗口了,我们提前分析一下这个激活过程。

 

对于requestLayout,我们在其他篇幅分析过,这个函数调用了scheduleTraversals函数来进一步执行操作,在scheduleTraversals函数中,会通过sendEmptyMessage(DO_TRAVERSAL)发送一个消息到应用程序的消息队列中,这个消息最终由viewRootImpl的handleMessage函数处理,而viewRootImpl的handleMessage函数把这个消息交给viewRootImpl类的performTraversals来处理,在performTraversals函数中,又会调用viewRootImpl类的relayoutWindow函数来进一步执行操作,最后在relayoutWindow函数中,就会通过WindowManagerService内部类Session的远程接口sWindowSession的relayout函数来进入到WindowManagerService中。

再调用WindowManagerService的成员函数relayoutWIndow,这个函数又会继续调用mInputMonitor的updateInputWindowsLw成员函数来更新当前的输入窗口,mInputMonitor是WindowManagerService的成员变量,它的类型为InputMonitor。

在InputMonitor.updateInputWindowsLw方法中,如下,

public void updateInputWindowsLw(boolean force) {

        // Add all windows on the default display.

        final int numDisplays = mService.mDisplayContents.size();

        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {

            WindowList windows = mService.mDisplayContents.valueAt(displayNdx).getWindowList();

            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {

                final WindowState child = windows.get(winNdx);

                final InputChannel inputChannel = child.mInputChannel;

                final InputWindowHandle inputWindowHandle = child.mInputWindowHandle;

                if (inputChannel == null || inputWindowHandle == null || child.mRemoved) {

                    // Skip this window because it cannot possibly receive input.

                    continue;

                }

……

                if (child.mWinAnimator != universeBackground) {

                    addInputWindowHandleLw(inputWindowHandle, child, flags, privateFlags, type,

                            isVisible, hasFocus, hasWallpaper);

                }

            }//for

        }//for

        // Send windows to native code.

        mService.mInputManager.setInputWindows(mInputWindowHandles);

        // Clear the list in preparation for the next round.

        clearInputWindowHandlesLw();

}

代码中,通过mDisplayContents知道所有Display的数目,再获取到Display下窗口列表,然后得到每个列表下窗口的数目,对于每一个窗口,对应一个WindowState实例。

 

每个WindowState实例有一个mInputChannel和一个mInputWindowHandle

对于mInputChannel,它在方法setInputChannel()被初始化, setInputChannel被调用的关系依次是WindowManagerService.addWindow,再是Session.addToDisplay,再是ViewRootImpl.setView,这样就回到了setView的代码,因为Activity创建时,addToDisplayrequestLayout之后被调用,所以InputMonitor获取到的mInputChannel为空,即当前窗口还没用被添加到InputWindow里面。如果再次调用到这里,当前窗口会被添加。

对于mInputWindowHandle,它在WindowState构造函数里被创建,并有一个inputChannel成员变量,也是在setInputChannel()被初始化为与WindowState. mInputChannel相同的值。

 

addInputWindowHandleLw()的主要作用是生成一个mInputWindowHandles数组,便于将窗口添加到jnidispatcher,添加完后这个数组被释放。另4.4支持数组的元素个数为16.

 

对于mService.mInputManager.setInputWindows(mInputWindowHandles);mServiceWMSmInputManager是前面提到的IMS实例引用,setInputWindows就是IMS的方法了。它调用了native方法nativeSetInputWindows

这个函数将带有InputChannelActivity窗口都设置为InputManager的输入窗口,但是后面我们会看到,只有当前激活的窗口才会响应键盘消息。

 

Native层的代码见com_android_server_InputManager.cpp ,在NativeInputManager.setInputWindows首先将Java层的Window转换成C++层的InputWindow,然后放在windowHandle向量中,最后将这些输入窗口设置到InputDispatcher中去。

void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray) {

            sp<InputWindowHandle> windowHandle =

                    android_server_InputWindowHandle_getHandle(env, windowHandleObj);

     mInputManager->getDispatcher()->setInputWindows(windowHandles);

}

 

在InputDispatcher::setInputWindows,

void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {

        sp<InputWindowHandle> newFocusedWindowHandle;

        bool foundHoveredWindow = false;

        for (size_t i = 0; i < mWindowHandles.size(); i++) {

            const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);

            if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) {

                mWindowHandles.removeAt(i--);

                continue;

            }

            if (windowHandle->getInfo()->hasFocus) {

                newFocusedWindowHandle = windowHandle;

            }

            if (windowHandle == mLastHoverWindowHandle) {

                foundHoveredWindow = true;

            }

        }

        if (mFocusedWindowHandle != newFocusedWindowHandle) {           

            if (mFocusedWindowHandle != NULL) {

                sp<InputChannel> focusedInputChannel = mFocusedWindowHandle->getInputChannel();

                if (focusedInputChannel != NULL) {

                    CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,

                            "focus left window");

                    synthesizeCancelationEventsForInputChannelLocked(

                            focusedInputChannel, options);

                }

            }

mFocusedWindowHandle = newFocusedWindowHandle;

        }

    // Wake up poll loop since it may need to make new input dispatching choices.

    mLooper->wake();

}

这里InputDispatcher的成员变量mFocusedWindowHandle就代表当前激活的窗口的。它通过一个for循环检查当前的输入窗口中的哪一个窗口是获得焦点的,获得焦点的输入窗口即为当前激活的窗口。

获取到焦点窗口,再取其InputChannel,通过synthesizeCancelationEventsForInputChannelLocked映射到connection,最后通过startDispatchCycleLocked(),开始向新的connection里进行事件分发。

     这样,InputManager就把当前激活的Activity窗口保存在InputDispatcher中了,后面就可以把键盘消息分发给它来处理。

 

 

4.1.2         注册InputChannel

第二部分里,完成InputChannel创建和注册。

 

手机创建一个java层的InputChannel实例。

                 if ((mWindowAttributes.inputFeatures

                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {

                    mInputChannel = new InputChannel();

                }

setView方法根据窗口属性,创建一个InputChannel实例。

窗口属性为INPUT_FEATURE_NO_INPUT_CHANNEL只在PhoneWindowManager.java的enablePointerLocation();里被设置,而enablePointerLocation则在PolicyHandler的MSG_ENABLE_POINTER_LOCATION情况下被调用,对于一般的窗口,INPUT_FEATURE_NO_INPUT_CHANNEL是没有被设置的,故能创建到InputChannel实例。

 

 

从InputChannel的定义看,public final class InputChannel implements Parcelable;

它实现了Parcelable,那它就是一个数据封装,在RPC调用时提供数据的封装操作。

 

从代码实现看来,它构造函数为空,主要方法也都是调用native方法,所以实体功能都在jni层完成,这里只是提供了操作接口,没有实体作用。

     private native void nativeDispose(boolean finalized);

    private native void nativeTransferTo(InputChannel other);

    private native void nativeReadFromParcel(Parcel parcel);

    private native void nativeWriteToParcel(Parcel parcel);

    private native void nativeDup(InputChannel target);

   

    private native String nativeGetName();

 

    /**

     * Creates an uninitialized input channel.

     * It can be initialized by reading from a Parcel or by transferring the state of

     * another input channel into this one.

     */

    public InputChannel() {

    }

 

 

接着,在setView方法里面,通过addToDisplay()的参数传递给窗口添加InputChannel,addToDisplay是一个aidl方法,其实现在Session.java,最终调用了WMS的addWindow。

 

在WMS里的addWindow里和InputChannel相关的代码只有下面一段,outInputChannel是传入的InputChannel对象。

            if (outInputChannel != null && (attrs.inputFeatures

                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {

                /** M: [ALPS00044207] @{ */

                 try {

                    String name = win.makeInputChannelName();

                    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);

                    win.setInputChannel(inputChannels[0]);

                    inputChannels[1].transferTo(outInputChannel);//关联outInputChannel和inputChannels[1]

 

                    mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);

                    //将其设置到connection的成员变量,当Dispatcher分发事件时,会从里面取channel,使用mChannel->sendMessage将事件发送出去。

                }

            }

 

 

4.1.2.1                                  创建通道

 

InputChannel.openInputChannelPair函数来创建一对输入通道,第一个设置给WindowState,并使用InputManager进行注册,第二个通过outInputChannel参数返回到应用程序中。

其官方说明:“Creates a new input channel pair.  One channel should be provided to the input dispatcher and the other to the application's input queue.  server channel and should be used to publish input events.  The second channel  is designated as the client channel and should be used to consume input events.”(openInputChannelPair用来创建一对输入通道。一个通道指派给服务端的dispatcher,用来分发输入事件;另一个通道指派给客户端,用来消耗这个事件。)

 

openInputChannelPair调用了native方法nativeOpenInputChannelPair这个函数根据传进来的参数nameC++层分别创建两个InputChannel,一个作为Server端使用,一个作为Client端使用,这里的Server端即是指InputManager,而Client端即是指应用程序。这两个本地的InputChannel是通过InputChannel::openInputChannelPair函数创建的,创建完成后,再相应地在jni层创建相应的两个NativeInputChannel,用来连接javaC++,然后返回这个jniNativeInputChannel

在android_view_InputChannel.cpp文件中,完成上述功能。

 static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,

        jclass clazz, jstring nameObj) {

    sp<InputChannel> serverChannel;

    sp<InputChannel> clientChannel;

    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);

    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);

    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,

            new NativeInputChannel(serverChannel));

    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,

            new NativeInputChannel(clientChannel));

    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);

    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);

    return channelPair;

}

 

InputChannel::openInputChannelPair负责创建实际的物理通道,

status_t InputChannel::openInputChannelPair(const String8& name,

        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {

    int sockets[2];

    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {

}

    int bufferSize = SOCKET_BUFFER_SIZE;

    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));

    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));

    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

 

    String8 serverChannelName = name;

    serverChannelName.append(" (server)");

    outServerChannel = new InputChannel(serverChannelName,sockets[0]);

 

    String8 clientChannelName = name;

    clientChannelName.append(" (client)");

    outClientChannel = new InputChannel(clientChannelName, sockets[1]);

    return OK;

}

可以看到,InputChannel::openInputChannelPair创建了一个socket对,设置了socket的相关属性,然后为服务端和客户端各创建一个InputChannel事件,并将socket作为文件描述符传递进去,让InputChannel用文件的方式操作socket。

 

InputTransport.cpp文件中,创建了InputChannel实例,需要注意的是在4.4代码中,InputChannel使用的是socket方式,在2.3中,InputChannel使用的是pipe方式。详细的代码分析就不再介绍。

 

 

回到android_view_InputChannel_nativeOpenInputChannelPair,里面新建了两个NativeInputChannel实例,将C++层的InputChannel实例引用赋值给其成员变量mInputChannel,再通过android_view_InputChannel_createInputChannelSetObjectArrayElement,将NativeInputChannel转化为Java层能识别的channel,让java有能力对jni层的InputChannel进行读写。这样就建立起了通道。

 

4.1.2.2                                  注册通道

 

再回到WMS里的addWindow,

            if (outInputChannel != null && (attrs.inputFeatures

                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {

                 try {

                    String name = win.makeInputChannelName();

                    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);

                    win.setInputChannel(inputChannels[0]);

                    inputChannels[1].transferTo(outInputChannel);

 

                    mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);

                }

            }

 

对于客户端,将第一个NativeInputChannel赋值给WindowState,作为客户端的通道。

对于第二个NativeInputChannel,使用了inputChannels[1].transferTo(outInputChannel);,它将ViewRootImpl里创建的InputChannel通过nativeTransferTo()传递给jni

 

android_view_InputChannel.cpp,对应于android_view_InputChannel_nativeTransferTo

static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,

        jobject otherObj) {

    if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {

        jniThrowException(env, "java/lang/IllegalStateException",

                "Other object already has a native input channel.");

        return;

    }

 

    NativeInputChannel* nativeInputChannel =

            android_view_InputChannel_getNativeInputChannel(env, obj);

    android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);

    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);

}

android_view_InputChannel_getNativeInputChannel用来判断java层的InputChannel没有在jni映射到NativeInputChannel,后面再获取到在android_view_InputChannel_nativeOpenInputChannelPair里存放起来的NativeInputChannel,将它和java的InputChannel关联起来,并移除之前存放的键值对,这类似于hash表的操作。

这样就将Java层的ViewRootImpl和WMS和Jni关联起来了。

 

 

对于InputManager.registerInputChannel(),它携带了第一个NativeInputChannel和窗口mInputWindowHandle它通过调用本地方法nativeRegisterInputChannel来执行进一步的操作。

 static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz,

        jint ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {

    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

 

    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,

            inputChannelObj);

    sp<InputWindowHandle> inputWindowHandle =

            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);

 

    status_t status = im->registerInputChannel(

            env, inputChannel, inputWindowHandle, monitor);

}

nativeRegisterInputChannel先获得nativeInputChannel,再得到C++层的InputChannel对象,,接着调用NativeInputManagerregisterInputChannel来真正执行注册输入通道的操作。

 

 

之后InputDispatcher::registerInputChannel被调用到,

 status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,

        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {

    { // acquire lock

        AutoMutex _l(mLock);

 

        if (getConnectionIndexLocked(inputChannel) >= 0) {

            ALOGW("Attempted to register already registered input channel '%s'",

                    inputChannel->getName().string());

            return BAD_VALUE;

        }

 

        sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);

 

        int fd = inputChannel->getFd();

        mConnectionsByFd.add(fd, connection);

 

        if (monitor) {

            mMonitoringChannels.push(inputChannel);

        }

 

        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);

    } // release lock

 

    // Wake the looper because some connections have changed.

    mLooper->wake();

    return OK;

}

这个函数首先会通过getConnectionIndexLocked检查从参数传进来的InputChannel是否已经注册过了,如果没有创建,就会创建一个Connection对象,用起成员变量来保存即将要注册的inputChannelinputWindowHandle,并将通道的文件描述符和Connection保存在mConnectionsByFd数组中来进行管理最后将fd添加到mLooper中区,这样就完成了C++层的注册过程,这个注册过程实际上就是将WindowStateinputChannel保存到Connection对象中,当Dispatcher分发事件时,会从里面取channel,使用mChannel->sendMessage将事件发送出去。也就是将JavaWindowState通道对应的native通道0指向的C++通道socket0注册到Dispatcher(有点绕口,但能说明其关系)。

其中Connection 的作用是“Manages the dispatch state associated with a single input channel”(管理通道的分发状态),

 

对于mLooper->addFd(),mLooper是C++的Looper,

 int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {

    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;

        ssize_t requestIndex = mRequests.indexOfKey(fd);

        if (requestIndex < 0) {

            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);

            if (epollResult < 0) {

                ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);

                return -1;

            }

            mRequests.add(fd, request);

        } else {

            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);

            if (epollResult < 0) {

                ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);

                return -1;

            }

            mRequests.replaceValueAt(requestIndex, request);

        }

    } // release lock

    return 1;

}

mLooper->addFd是把这个Receive通道文件描述符 Fd添加到InputDispatcher的成员变量mLooper中去。对于一般情况,在Looper类内部,会创建一个管道,然后Looper会睡眠在这个管道的读端,等待另外一个线程来往这个管道的写端写入新的内容,从而唤醒等待在这个管道读端的线程。

除此之外,Looper还可以同时睡眠等待在其它的文件描述符上,本例就是这种情况,因为它是通过Linux系统的epoll机制来批量等待指定的文件有新的内容可读的。这些其它的文件描述符就是通过Looper类的addFd方法添加进去的,在添加的时候,还可以指定事件类型和回调函数,即当这个文件描述符所指向的文件有新的内容可读时,Looper就会调用这个handleReceiveCallback函数对应事件。(疑问:这里Looper的归属?)

 

我们还得明确知道mLooper的归属,要不然我们会被Looper弄混乱。通过代码分析,我们知道,这里的mLooper是在InputDispatcher构造函数里被创建的,那么它就是只属于InputDispatcher,并在dispatchOnce进行mLooper->pollOnce,我们就可以知道在注册时,InputDispatcher开始休眠在其通道上,等待唤醒。

 InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :…

{    mLooper = new Looper(false);

    mKeyRepeatState.lastKeyEntry = NULL;

    policy->getDispatcherConfiguration(&mConfig);

}

 

唤醒之后,用handleReceiveCallback处理事件,主要处理代码如下,可以看出它处理的事件是应用对它发出的事件的结束处理,表明事件处理完成了。应用的事件处理可见后面。关于handleReceiveCallback是如何被调用的,可以深入分析C++ Looper的实现过程。

int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {

            for (;;) {

                uint32_t seq;

                bool handled;

                status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);

                if (status) {

                    break;

                }

                d->finishDispatchCycleLocked(currentTime, connection, seq, handled);

                gotOne = true;

            }

            if (gotOne) {

                d->runCommandsLockedInterruptible();

                if (status == WOULD_BLOCK) {

                    return 1;

                }

            }

 }

 

 

至此,可以看出,应用程序为Activity窗口和InputManager之间创建一对按键消息接收通道,其中一个通道以Server端为主,Server端的InputChannel注册在由InputManager所管理的InputDispatcher中,连接InputDispatcherWindowState;另一个通道以Client端为主, Client端的InputChannel注册在由应用程序主线程的消息循环对象Looper中,连接LooperViewRootImpl。其大致关系如下图:

 

 

4.1.2.3                                  窗口事件接收器

 

第三部分是创建一个应用窗口事件接收器,

回头看setView与此相关的代码如下,

                if (mInputChannel != null) {

                    if (mInputQueueCallback != null) {

                        mInputQueue = new InputQueue();

                        mInputQueueCallback.onInputQueueCreated(mInputQueue);

                    }

                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,

                            Looper.myLooper());

                }

4.4代码中,如mInputQueueCallback不为空,则会新建一个InputQueue实例,再新建了一个WindowInputEventReceiver事件接收器,它继承自InputEventReceiver,其功能是“Provides a low-level mechanism for an application to receive input events”(提供一个低层机制,为应用接收到输入事件服务),就是说native会把输入事件发给它处理。

 

而在2.3代码里面,是使用InputQueue的方法registerInputChannel进行注册键盘消息接收通道,参数含义为:mInputChannel为我们在前面创建的Client端的InputChannelLooper.myQueue函数返回的便是应用程序主线程的消息队列;参数mInputHandler是一个回调对象,当有键盘事件发生时,这个mInputHandlerhandleKey函数就会被调用。我们就不继续关心这里的代码实现了。

 

 

InputEventReceiver的构造函数如下,可以看到它通过nativeInitJava层创建的事件通道mInputChannel和应用对应的消息队列mMessageQueue传递给jni层。

     public InputEventReceiver(InputChannel inputChannel, Looper looper) {

        mInputChannel = inputChannel;

        mMessageQueue = looper.getQueue();

        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),

                inputChannel, mMessageQueue);

 

        mCloseGuard.open("dispose");

    }

 

 

InputEventReceiver对应的jni方法在android_view_InputEventReceiver.cpp文件中,nativeInit的代码如下,

 static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,

        jobject inputChannelObj, jobject messageQueueObj) {

    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,

            inputChannelObj);//获取C++对应的inputChannel

    if (inputChannel == NULL) {

        jniThrowRuntimeException(env, "InputChannel is not initialized.");

        return 0;

    }

 

    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);

    if (messageQueue == NULL) {//获取jni对应的messageQueue

        jniThrowRuntimeException(env, "MessageQueue is not initialized.");

        return 0;

    }

 

    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,

            receiverWeak, inputChannel, messageQueue);//新建一个jni的事件receiver

    status_t status = receiver->initialize();//初始化receiver,发出一个ALOOPER_EVENT_INPUT事件

    if (status) {

        String8 message;

        message.appendFormat("Failed to initialize input event receiver.  status=%d", status);

        jniThrowRuntimeException(env, message.string());

        return 0;

    }

 

    receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object

    return reinterpret_cast<jint>(receiver.get());

}

可以看出,通过android_view_InputChannel_getInputChannel,可以得到之前创建的nativeInputChannel通道,再通过nativeInputChannel的成员编码获得实际的C++通道。

再得到一个C++对应的消息队列,然后新建一个NativeInputEventReceiver实例,其构造函数初始化了相关成员变量,包括将C++通道赋值给其成员变量mInputConsumer作为消耗事件的通道,最后发送ALOOPER_EVENT_INPUT事件初始化NativeInputEventReceiver。

 

发出ALOOPER_EVENT_INPUT事件是通过NativeInputEventReceiver::setFdEvents来完成的,

 void NativeInputEventReceiver::setFdEvents(int events) {

    if (mFdEvents != events) {

        mFdEvents = events;

        int fd = mInputConsumer.getChannel()->getFd();

        if (events) {

            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);

        } else {

            mMessageQueue->getLooper()->removeFd(fd);

        }

    }

}

可以看到,通过mInputConsumer可以得到C++的通道,进而得到通道描述符,然后通过Looper的addFd,这里将Looper休眠在Fd对应的通道上,当事件发生时,调用this指针的对象进行事件处理。

这样,就完成了Java层ViewRootImpl的InputChannel对应的native通道1指向的socket1和应用程序主线程Looper的消息队列的关联。也就是通道注册到Looper上了。

 

 

对于事件的处理,是通过NativeInputEventReceiver::handleEvent来处理的,他调用到NativeInputEventReceiver::consumeEvents,具体代码就不分析了。

 

另外,既然有Receiver,就会有Sender,在native层,其代码在android_view_InputEventSender.cpp,其代码分析会在后续展开。

 

以上是4.4代码,通过socket的方式完成通道初始过程。

 

对于2.3,通道是pipe完成。注册在InputDispatcher中的InputChannel由一个反向管道的读端和一个前向管道的写端组成,而注册在应用程序主线程的消息循环对象Looper中的InputChannel由这个前向管道的读端和反向管道的写端组成,这种交叉结构使得当有键盘事件发生时,InputDispatcher可以把这个事件通知给应用程序。

 

 

4.1.3         事件分发

事件分发的基本流程为:用户按下事件,InputManager中的InputReader读取到键盘事件的发生,它唤醒InputDispatcher进行事件分发,InputDispatcher将事件分发给应用程序的消息队列,应用程序在消息循环中处理事件。其基本流程如下:

 



事件分发整体流程如下图描述:





4.1.3.1                                  InputReader事件读取

从InputManager的分析过程可以知道,InputReaderThread线程启动后,调用threadLoop,再接着调用InputReader.loopOnce()来读入键盘事件,而实际的键盘事件读入操作是由EventHub.getEvents函数来进行的。如果当前没有键盘事件发生,InputReaderThread线程就会睡眠在EventHub.getEvents函数上,而当键盘事件发生后,EventHub.getEvents就会把事件封装成一个RawEvent对象,放进mEventBuffer缓冲,并返回到loopOnce函数中,让InputReader调用processEventsLocked进行事件分发:

 void InputReader::loopOnce() {

    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

        if (count) {

            processEventsLocked(mEventBuffer, count);

        }

        if (oldGeneration != mGeneration) {

            inputDevicesChanged = true;

            getInputDevicesLocked(inputDevices);

        }

    if (inputDevicesChanged) {

        mPolicy->notifyInputDevicesChanged(inputDevices);

    }

}

 

 

1)EventHub::getEvents

mEventHub是在创建InputReader时传入的,最终来源于NativeInputManager方法,是EventHub的引用。

 

EventHub::getEvents也是一个消息循环,它开始判断设备状态,然后读取通道里的数据,得到按键数据,然后逐条添加到缓冲,如果读取到事件,则跳出for循环,返回读取到的事件个数,如果没有事件,使用epoll机制等待事件。

 

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {

ALOGD("shah::getEvents0  \n");//添加打印

 

    for (;;) {

        if (mNeedToScanDevices) {//这里是设备扫描

            mNeedToScanDevices = false;

            scanDevicesLocked();

            mNeedToSendFinishedDeviceScan = true;

        }

//省略一段设备状态的判断代码

        while (mPendingEventIndex < mPendingEventCount) {

            const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];

..

            if (eventItem.data.u32 == EPOLL_ID_WAKE) {

                if (eventItem.events & EPOLLIN) {

                    ALOGV("awoken after wake()");

                    awoken = true;

                    char buffer[16];

                    ssize_t nRead;

                    do {

                        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));

                    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));

                } else {

                    ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",

                            eventItem.events);

                }

                continue;

            }

            if (eventItem.events & EPOLLIN) {

                int32_t readSize = read(device->fd, readBuffer,

                        sizeof(struct input_event) * capacity);//读事件

                   int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;

                    size_t count = size_t(readSize) / sizeof(struct input_event);

                    ALOGD("shah::getEvents8  count = '%d' \n", count);

                   

                    for (size_t i = 0; i < count; i++) {//添加事件到缓冲

                        event->type = iev.type;

                        event->code = iev.code;

                        event->value = iev.value;

                        event += 1;

                        capacity -= 1;

                    }

}

        // Return now if we have collected any events or if we were explicitly awoken.

        if (event != buffer || awoken) {//如果读取到事件,就跳出for循环返回事件

            break;

        }

        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);

}//for

    return event - buffer;//返回事件个数

}

 

对于其中的设备扫描,发生在getEvents第一次进入的时候,只有当设备扫描完成后,才能读取到设备事件。scanDevicesLocked调用scanDirLocked,后者调用openDeviceLocked打开设备。

 status_t EventHub::scanDirLocked(const char *dirname)

{

    char devname[PATH_MAX];

    char *filename;

    DIR *dir;

    struct dirent *de;

    dir = opendir(dirname);

    if(dir == NULL)

        return -1;

    strcpy(devname, dirname);

    filename = devname + strlen(devname);

    *filename++ = '/';

ALOGD("shah::scanDirLocked devname='%s' dirname = '%s' \n", devname,dirname);

 

    while((de = readdir(dir))) {

        if(de->d_name[0] == '.' &&

           (de->d_name[1] == '\0' ||

            (de->d_name[1] == '.' && de->d_name[2] == '\0')))

            continue;

        strcpy(filename, de->d_name);

        openDeviceLocked(devname);

    }

    closedir(dir);

    return 0;

}

 

根据log和代码分析,当前打开的输入设备如下,

01-13 02:48:08.765   723   723 I SystemServer: Input Manager

01-13 02:48:08.766   723   723 I InputManager: Initializing input manager, mUseDevInputEventForAudioJack=false

01-13 02:48:08.769   723   723 D InputReader: InputReader:: InputReader lock

01-13 02:48:08.769   723   723 D InputReader: InputReader:: InputReader unlock

01-13 02:48:08.769   723   723 I SystemServer: Window Manager

01-13 02:48:08.840   723   723 I InputManager: Starting input manager

01-13 02:48:08.840   723   766 D dalvikvm: threadid=30: interp stack at 0x61f54000

01-13 02:48:08.840   723   766 D dalvikvm: threadid=30: created from interp

01-13 02:48:08.840   723   744 D dalvikvm: start new thread

01-13 02:48:08.840   723   766 D dalvikvm: threadid=30: notify debugger

01-13 02:48:08.840   723   766 D dalvikvm: threadid=30 (callActivityManagerForStrictModeDropbox): calling run()

01-13 02:48:08.855   723   769 D EventHub: shah::getEvents0 

01-13 02:48:08.855   723   769 D EventHub: shah::scanDirLocked devname='/dev/input/' dirname = '/dev/input'

01-13 02:48:08.855   723   769 D EventHub: Opening device: /dev/input/event0

01-13 02:48:08.863   723   769 D EventHub: Created descriptor: raw=:2454:6500:, cooked=f0d2e427e7a05eb6d316f5e14800c5ac7b6aee79

01-13 02:48:08.863   723   769 D EventHub: add device 1: /dev/input/event0

01-13 02:48:08.863   723   769 D EventHub:   bus:        0019

01-13 02:48:08.863   723   769 D EventHub:   vendor      2454

01-13 02:48:08.863   723   769 D EventHub:   product     6500

01-13 02:48:08.863   723   769 D EventHub:   version     0010

01-13 02:48:08.863   723   769 D EventHub:   name:       "mtk-kpd"

01-13 02:48:08.863   723   769 D EventHub:   location:   ""

01-13 02:48:08.863   723   769 D EventHub:   unique id:  ""

01-13 02:48:08.863   723   769 D EventHub:   descriptor: "f0d2e427e7a05eb6d316f5e14800c5ac7b6aee79"

01-13 02:48:08.863   723   769 D EventHub:   driver:     v1.0.1

01-13 02:48:08.865   723   769 D EventHub: No input device configuration file found for device 'mtk-kpd'.

01-13 02:48:08.879   723   769 D EventHub: New device: id=1, fd=120, path='/dev/input/event0', name='mtk-kpd', classes=0x1, configuration='', keyLayout='/system/usr/keylayout/mtk-kpd.kl', keyCharacterMap='/system/usr/keychars/Generic.kcm', builtinKeyboard=false, usingSuspendBlockIoctl=true, usingClockIoctl=true

01-13 02:48:08.879   723   769 D EventHub: Opening device: /dev/input/event4

01-13 02:48:08.879   723   723 I SystemServer: Input Method Service

01-13 02:48:08.880   723   769 D EventHub: Created descriptor: raw=:0001:0001:, cooked=485d69228e24f5e46da1598745890b214130dbc4

01-13 02:48:08.880   723   769 D EventHub: add device 2: /dev/input/event4

01-13 02:48:08.880   723   769 D EventHub:   bus:        0019

01-13 02:48:08.880   723   769 D EventHub:   vendor      0001

01-13 02:48:08.880   723   769 D EventHub:   product     0001

01-13 02:48:08.880   723   769 D EventHub:   version     0100

01-13 02:48:08.880   723   769 D EventHub:   name:       "mtk-tpd-kpd"

01-13 02:48:08.880   723   769 D EventHub:   location:   ""

01-13 02:48:08.880   723   769 D EventHub:   unique id:  ""

01-13 02:48:08.880   723   769 D EventHub:   descriptor: "485d69228e24f5e46da1598745890b214130dbc4"

01-13 02:48:08.880   723   769 D EventHub:   driver:     v1.0.1

01-13 02:48:08.882   723   769 D EventHub: No input device configuration file found for device 'mtk-tpd-kpd'.

01-13 02:48:08.889   723   723 W InputMethodManagerService: Couldn't create dir.: /data/system/inputmethod

01-13 02:48:08.893   723   769 D EventHub: Unable to disable kernel key repeat for /dev/input/event4: Function not implemented

01-13 02:48:08.893   723   769 D EventHub: New device: id=2, fd=121, path='/dev/input/event4', name='mtk-tpd-kpd', classes=0x1, configuration='', keyLayout='/system/usr/keylayout/Generic.kl', keyCharacterMap='/system/usr/keychars/Generic.kcm', builtinKeyboard=false, usingSuspendBlockIoctl=true, usingClockIoctl=true

01-13 02:48:08.893   723   769 D EventHub: Opening device: /dev/input/mouse0

01-13 02:48:08.893   723   769 E EventHub: could not get driver version for /dev/input/mouse0, Not a typewriter

01-13 02:48:08.966   723   769 D EventHub: Opening device: /dev/input/event3

01-13 02:48:08.967   723   769 D EventHub: Created descriptor: raw=:0000:0000:name:mtk-tpd, cooked=84931e976ab60191371c1c95baf408538ca4c4c5

01-13 02:48:08.967   723   769 D EventHub: add device 3: /dev/input/event3

01-13 02:48:08.967   723   769 D EventHub:   bus:        0000

01-13 02:48:08.967   723   769 D EventHub:   vendor      0000

01-13 02:48:08.967   723   769 D EventHub:   product     0000

01-13 02:48:08.967   723   769 D EventHub:   version     0000

01-13 02:48:08.967   723   769 D EventHub:   name:       "mtk-tpd"

01-13 02:48:08.967   723   769 D EventHub:   location:   ""

01-13 02:48:08.967   723   769 D EventHub:   unique id:  ""

01-13 02:48:08.968   723   769 D EventHub:   descriptor: "84931e976ab60191371c1c95baf408538ca4c4c5"

01-13 02:48:08.968   723   769 D EventHub:   driver:     v1.0.1

01-13 02:48:08.969   723   769 D EventHub: No input device configuration file found for device 'mtk-tpd'.

01-13 02:48:08.976   723   769 D EventHub: New device: id=3, fd=93, path='/dev/input/event3', name='mtk-tpd', classes=0x15, configuration='', keyLayout='/system/usr/keylayout/Generic.kl', keyCharacterMap='/system/usr/keychars/Generic.kcm', builtinKeyboard=false, usingSuspendBlockIoctl=true, usingClockIoctl=true

01-13 02:48:08.976   723   723 D SettingsProvider: lookupValue table secure cache.fullyMatchesDisk() disabled_system_input_methods

01-13 02:48:08.976   723   769 D EventHub: Opening device: /dev/input/event2

01-13 02:48:08.976   723   723 V Provider/Settings: from db cache, name = disabled_system_input_methods , value = null

01-13 02:48:08.976   723   769 D EventHub: Created descriptor: raw=:0000:0000:name:hwmdata, cooked=9d975b2c55ae384780e225b252ab4af45f01c6c9

01-13 02:48:08.976   723   769 D EventHub: add device 4: /dev/input/event2

01-13 02:48:08.976   723   769 D EventHub:   bus:        0000

01-13 02:48:08.976   723   769 D EventHub:   vendor      0000

01-13 02:48:08.976   723   769 D EventHub:   product     0000

01-13 02:48:08.976   723   769 D EventHub:   version     0000

01-13 02:48:08.976   723   769 D EventHub:   name:       "hwmdata"

01-13 02:48:08.976   723   769 D EventHub:   location:   ""

01-13 02:48:08.976   723   769 D EventHub:   unique id:  ""

01-13 02:48:08.976   723   769 D EventHub:   descriptor: "9d975b2c55ae384780e225b252ab4af45f01c6c9"

01-13 02:48:08.976   723   769 D EventHub:   driver:     v1.0.1

01-13 02:48:08.977   723   769 D EventHub: No input device configuration file found for device 'hwmdata'.

01-13 02:48:08.977   723   769 D EventHub: Dropping device: id=4, path='/dev/input/event2', name='hwmdata'

01-13 02:48:09.016   723   723 D InputReader: InputReader:: getSwitchState lock

01-13 02:48:09.016   723   723 D InputReader: InputReader::getStateLocked:: return

01-13 02:48:09.019   723   741 D DisplayManagerService: Delivering display event: displayId=0, event=2

01-13 02:48:09.026   723   769 D EventHub: Opening device: /dev/input/event1

01-13 02:48:09.027   723   769 D EventHub: Created descriptor: raw=:0000:0000:name:ACCDET, cooked=1c78f7e0d16d4dbc8d3ab93943523f379203f90b

01-13 02:48:09.027   723   769 D EventHub: add device 5: /dev/input/event1

01-13 02:48:09.027   723   769 D EventHub:   bus:        0019

01-13 02:48:09.027   723   769 D EventHub:   vendor      0000

01-13 02:48:09.027   723   769 D EventHub:   product     0000

01-13 02:48:09.027   723   769 D EventHub:   version     0000

01-13 02:48:09.027   723   769 D EventHub:   name:       "ACCDET"

01-13 02:48:09.028   723   769 D EventHub:   location:   ""

01-13 02:48:09.028   723   769 D EventHub:   unique id:  ""

01-13 02:48:09.028   723   769 D EventHub:   descriptor: "1c78f7e0d16d4dbc8d3ab93943523f379203f90b"

01-13 02:48:09.028   723   769 D EventHub:   driver:     v1.0.1

01-13 02:48:09.029   723   769 D EventHub: No input device configuration file found for device 'ACCDET'.

01-13 02:48:09.037   723   769 D EventHub: Unable to disable kernel key repeat for /dev/input/event1: Function not implemented

01-13 02:48:09.037   723   769 D EventHub: New device: id=5, fd=123, path='/dev/input/event1', name='ACCDET', classes=0x1, configuration='', keyLayout='/system/usr/keylayout/ACCDET.kl', keyCharacterMap='/system/usr/keychars/Generic.kcm', builtinKeyboard=false, usingSuspendBlockIoctl=true, usingClockIoctl=true

01-13 02:48:09.037   723   769 D EventHub: Opening device: /dev/input/mice

01-13 02:48:09.037   723   769 E EventHub: could not get driver version for /dev/input/mice, Not a typewriter

01-13 02:48:09.038   723   723 D InputReader: InputReader:: getSwitchState lock

01-13 02:48:09.038   723   723 D InputReader: InputReader::getStateLocked:: return

 

 

根据log分析,除了看到扫描到的Input设备之外,还可以验证IMS的创建、初始化和启动过程(如蓝色文件标识的关键log过程)。

对于设备扫描,我们可以看到,共扫描出如下6种设备,他们都位于在设备目录/dev/input中,,其中,键盘事件就包含在event0设备文件中。


 

当打开设备时,函数首先根据文件名来打开这个设备文件,系统中所有输入设备文件信息都保存在成员变量mDevicesById中,因此,先在mDevicesById找到一个空位置来保存当前打开的设备文件信息,找到了空闲位置后,就为这个输入设备文件创建相应的device_t信息,同时,这个设备文件还会保存在数组mFDs中,我们在后面将会看到对数组的使用。

对于键盘,把结构体deviceclasses成员变量的INPUT_DEVICE_CLASS_KEYBOARD位置为1了,以表明这是一个键盘,还要继续设置键盘的布局等信息,正如我们在log里看到的,具体就不在这里分析代码了。

在运行过程中,会检查当前是否有输入设备被关闭,如果有,就返回一个设备移除的事件;检查当前是否有新的输入设备加入进来;还会检查检查是否需要结束监控输入事件。

 

 

再接着分析EventHub::getEvents,前面是对设备的处理,当对于事件时,检查当前是否有还未处理的输入设备事件发生,就用到了前面说的设备信息数组了,未处理的输入事件保存在成员变量mInputBufferData中,如果有的话,返回事件。(事件的来源暂不分析。)

如果输入设备信息里没有事件,就要通过epoll来等待输入设备上发生新的事件了。在epoll_wait里,使用了mEpollFd表示一个Epoll文件描述符,它是在EventHub的构造函数里被创建的:

mEpollFd = epoll_create(EPOLL_SIZE_HINT);

 

 

2)processEventsLocked

EventHub::getEvents接收到事件之后,调用processEventsLocked处理事件。

processEventsLocked会将按键事件同device事件区分开来,通过循环,调用processEventsForDeviceLocked逐个处理事件。

 

processEventsForDeviceLocked会根据事件携带的设备ID,选择合适的设备实例来处理按键。

void InputReader::processEventsForDeviceLocked(int32_t deviceId,

        const RawEvent* rawEvents, size_t count) {

    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);

   InputDevice* device = mDevices.valueAt(deviceIndex);

    device->process(rawEvents, count);

}

 

成员变量mDevices保存了一系列输入设备事件处理对象,例如负责处理键盘事件的KeyboardInputMapper对象、负责处理轨迹球事件的JoystickInputMapper对象,以及负责处理触摸屏事件对象,多点触摸MultiTouchInputMapper和单点触摸SingleTouchInputMapper这里设备对象都是在InputReader类的成员函数createDeviceLocked中创建的。

当前我们分析键盘的处理流程,查询到设备对象KeyboardInputMapper,其方法process在处理EV_KEY类型的事件时调用processKey(),这里就是设备对按键实际处理的地方,代码如下:

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,

        int32_t scanCode, uint32_t policyFlags) {

    if (down) {

        // Rotate key codes according to orientation if needed.

        if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {

            keyCode = rotateKeyCode(keyCode, mOrientation);

        }

 

        // Add key down.

        ssize_t keyDownIndex = findKeyDown(scanCode);

        if (keyDownIndex >= 0) {

            // key repeat, be sure to use same keycode as before in case of rotation

            keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;

        } else {

            // key down

            if ((policyFlags & POLICY_FLAG_VIRTUAL)

                    && mContext->shouldDropVirtualKey(when,

                            getDevice(), keyCode, scanCode)) {

                return;

            }

 

            mKeyDowns.push();

            KeyDown& keyDown = mKeyDowns.editTop();

            keyDown.keyCode = keyCode;

            keyDown.scanCode = scanCode;

        }

 

        mDownTime = when;

    } else {

        // Remove key down.

        ssize_t keyDownIndex = findKeyDown(scanCode);

        if (keyDownIndex >= 0) {

            // key up, be sure to use same keycode as before in case of rotation

            keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;

            mKeyDowns.removeAt(size_t(keyDownIndex));

        } else {

            // key was not actually down

            ALOGI("Dropping key up from device %s because the key was not down.  "

                    "keyCode=%d, scanCode=%d",

                    getDeviceName().string(), keyCode, scanCode);

            return;

        }

    }

    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,

            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,

            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);

    getListener()->notifyKey(&args);

}

 

这里会先对按键做相关处理,最后,通过getListener()->notifyKey(&args);,将事件发送出去。

getListener()定义如下,它通过成员变量mQueuedListener获取到监听器,

InputListenerInterface* InputReader::ContextImpl::getListener() {

    return mReader->mQueuedListener.get();

}

 

mQueuedListener由哪里来呢,它是在InputReader构造函数里面被赋值的,

InputReader::InputReader(const sp<EventHubInterface>& eventHub,

        const sp<InputReaderPolicyInterface>& policy,

        const sp<InputListenerInterface>& listener) :

        mContext(this), mEventHub(eventHub), mPolicy(policy),

        mGlobalMetaState(0), mGeneration(1),

        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),

        mConfigurationChangesToRefresh(0) {

    mQueuedListener = new QueuedInputListener(listener);

 

    { // acquire lock

        AutoMutex _l(mLock);

        ALOGD("InputReader:: InputReader lock " );

        refreshConfigurationLocked(0);

        updateGlobalMetaStateLocked();

    } // release lock

    ALOGD("InputReader:: InputReader unlock " );

}

 

对于InputReader实例创建,通过InputManager的介绍我们知道,在InputManager构造函数里面,我们会创建一个InputDispatcher实例mDispatcher,和一个InputReader实例,并将mDispatcher作为监听器listener参数传递给InputReader实例,

InputManager::InputManager(

        const sp<EventHubInterface>& eventHub,

        const sp<InputReaderPolicyInterface>& readerPolicy,

        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {

    mDispatcher = new InputDispatcher(dispatcherPolicy);

    mReader = new InputReader(eventHub, readerPolicy,mDispatcher);

    initialize();

}

 

所以getListener()获取到的就是InputDispatcher对象,notifyKey()就是InputDispatcher用来进行事件分发的方法。

 

 

4.1.3.2                                  InputDispatcher分发

 

notifyKey()的代码如下:

 void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {

    KeyEvent event;

    event.initialize(args->deviceId, args->source, args->action,

            flags, args->keyCode, args->scanCode, metaState, 0,

            args->downTime, args->eventTime);

 

    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);

        int32_t repeatCount = 0;

        KeyEntry* newEntry = new KeyEntry(args->eventTime,

                args->deviceId, args->source, policyFlags,

                args->action, flags, args->keyCode, args->scanCode,

                metaState, repeatCount, args->downTime);

 

        needWake = enqueueInboundEventLocked(newEntry);

    if (needWake) {

        mLooper->wake();

    }

}

它先对按键做判断,再处理按键组合等情况,然后先重新封装按键,调用interceptKeyBeforeQueueing做入队前的处理,再重新封装按键,放入事件队列。最后唤醒其Looper来处理事件,这样才将事件从InputReader交付给InputDispatcher。

 

事件加入队列过程如下,它主要是将事件加入mInboundQueue,并在队列为空或当前按键是切换键松开的时候才决定唤醒线程。

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {

    bool needWake = mInboundQueue.isEmpty();

    mInboundQueue.enqueueAtTail(entry);

    traceInboundQueueLengthLocked();

 

    switch (entry->type) {

    case EventEntry::TYPE_KEY: {

        // Optimize app switch latency.

        // If the application takes too long to catch up then we drop all events preceding

        // the app switch key.

        KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);

        if (isAppSwitchKeyEventLocked(keyEntry)) {

            if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {

                mAppSwitchSawKeyDown = true;

            } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {

                if (mAppSwitchSawKeyDown) {

                    mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;

                    mAppSwitchSawKeyDown = false;

                    needWake = true;

                }

            }

        }

        break;

    }…

}

 

事件来后,InputDispatcher被唤醒。

 

InputDispatcher的分发线程如下,在其线程循环里,,调用它的dispatchOnce成员函数执行一次键盘消息分发的操作。它把键盘消息交给dispatchOnceInnerLocked函数来处理,然后调用mLooper->pollOnce函数等待下一次键盘事件的发生。

 bool InputDispatcherThread::threadLoop() {

    mDispatcher->dispatchOnce();

    return true;

}

 

void InputDispatcher::dispatchOnce() {

    nsecs_t nextWakeupTime = LONG_LONG_MAX;

    { // acquire lock

        AutoMutex _l(mLock);

        mDispatcherIsAliveCondition.broadcast();

 

        // Run a dispatch loop if there are no pending commands.

        // The dispatch loop might enqueue commands to run afterwards.

        if (!haveCommandsLocked()) {

            dispatchOnceInnerLocked(&nextWakeupTime);

        }

    } // release lock

 

    // Wait for callback or timeout or wake.  (make sure we round up, not down)

    nsecs_t currentTime = now();

    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);

    mLooper->pollOnce(timeoutMillis);

}

 

1)事件处理

dispatchOnceInnerLocked对于按键的处理主要是如下代码完成,调用dispatchKeyLocked,

     case EventEntry::TYPE_KEY: {

        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);

        if (isAppSwitchDue) {

            if (isAppSwitchKeyEventLocked(typedEntry)) {

                resetPendingAppSwitchLocked(true);

                isAppSwitchDue = false;

            } else if (dropReason == DROP_REASON_NOT_DROPPED) {

                dropReason = DROP_REASON_APP_SWITCH;

            }

        }

        if (dropReason == DROP_REASON_NOT_DROPPED

                && isStaleEventLocked(currentTime, typedEntry)) {

            dropReason = DROP_REASON_STALE;

        }

        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {

            dropReason = DROP_REASON_BLOCKED;

        }

        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);

        break;

    }

 

dispatchKeyLocked使用findFocusedWindowTargetsLocked找到当前激活窗口,调用addWindowTargetLocked函数把当前激活的Activity窗口添加到InputDispatcher类的mCurrentInputTargets成员变量中去,其中target.inputChannel = windowInfo->inputChannel;语句也实行了通道的赋值。之后,依次调用下面的方法dispatchEventLocked,prepareDispatchCycleLocked,到达startDispatchCycleLocked,

 void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,

        const sp<Connection>& connection) {

    while (connection->status == Connection::STATUS_NORMAL

            && !connection->outboundQueue.isEmpty()) {

        DispatchEntry* dispatchEntry = connection->outboundQueue.head;

        dispatchEntry->deliveryTime = currentTime;

 

        // Publish the event.

        status_t status;

        EventEntry* eventEntry = dispatchEntry->eventEntry;

        switch (eventEntry->type) {

        case EventEntry::TYPE_KEY: {

            KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);

 

            // Publish the key event.

            status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,

                    keyEntry->deviceId, keyEntry->source,

                    dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,

                    keyEntry->keyCode, keyEntry->scanCode,

                    keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,

                    keyEntry->eventTime);

            break;

        }

}

这里就用到了前面通道注册时提到的connection,它是通过通道target.inputChannel从数组里取出的,我们通过其方法publishKeyEvent,获取到通道和相关数据,再通过向通道写数据,完成事件从InputDispatcher到应用的分发。

 status_t InputPublisher::publishKeyEvent(…){

    msg.body.key.metaState = metaState;

    msg.body.key.repeatCount = repeatCount;

    msg.body.key.downTime = downTime;

    msg.body.key.eventTime = eventTime;

    return mChannel->sendMessage(&msg);

}

status_t InputChannel::sendMessage(const InputMessage* msg) {

    size_t msgLength = msg->size();

    ssize_t nWrite;

    do {

        nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);

    } while (nWrite == -1 && errno == EINTR);

}

其中mFd是通道描述符,来源于InputChannel创建时传入的参数,就是前面创建通道时,创建的socket的名字,这里就是socket0的名字,即承载InputDispatcher对应通道的socket名字。

 

因为4.4是socket方式承载通道,所以事件的发生方式和2.3不一样,在2.3,使用了管道和匿名共享内存的方式来发生事件,具体就不再分析了。

 

 

2)线程等待

当处理完事件后,调用mLooper.pollOnce等待新的事件。成员变量mLooper的类型为Looper,它定义在C++层,

2.3代码里面,Looper.cppframeworks/base/libs/utils目录下面,而在4.4代码里,它在system/core/libutils,有时会由于目录变化的原因很难找到。

 

Looper.pollOnce代码中,有一个消息循环,里面调用pollInner等待按键产生,

 int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {

    int result = 0;

    for (;;) {

        result = pollInner(timeoutMillis);

    }

}

 

pollInner使用epoll机制,用接口epoll_wait()等待按键传递,,如果没有内容可读,那么当前线程就会进入到空闲等待状态;当有键盘事件发生时,然后使用awoken唤醒InputDispatcher线程,去处理新的按键。

 

 

4.1.3.3                                  应用部分事件分发

从前面通道注册的过程可以知道,NativeInputEventReceiver的Looper睡眠在Socket1上,当它被唤醒时,NativeInputEventReceiver的handleEvent会被调用,为了验证这一点,我们在上下文各处添加打印语句,得到的结果下,可以确认的确如此。


 


NativeInputEventReceiver::handleEvent会调用consumeEvents处理事件,(可对比分析代码里的打印语句和上面捕获的打印语句验证流程),唤醒到事件接收、处理这一过程事件处理还比较复杂,待分析(见log)。

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {

#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

    }

ALOGD("shah:event:channel '%s' handleEvent.    "

          "events=0x%x", getInputChannelName(), events);

 

    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;

    }

    ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "

            "events=0x%x", getInputChannelName(), events);

    return 1;

}

 

consumeEvents里面用到了gInputEventReceiverClassInfo.dispatchInputEvent和gInputEventReceiverClassInfo.dispatchBatchedInputEventPending来完成事件的处理,

 status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,

        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {

    for (;;) {

        uint32_t seq;

        InputEvent* inputEvent;

        status_t status = mInputConsumer.consume(&mInputEventFactory,

                consumeBatches, frameTime, &seq, &inputEvent);

….

                    env->CallVoidMethod(receiverObj.get(),

                            gInputEventReceiverClassInfo.dispatchBatchedInputEventPending);

                env->CallVoidMethod(receiverObj.get(),

                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);

        if (skipCallbacks) {

            mInputConsumer.sendFinishedSignal(seq, false);

        }

    }

}

根据jni技术,gInputEventReceiverClassInfo对应于ViewRootImpl里面的WindowInputEventReceiver,这样就完成了事件从jni到Java层的传送。

 

 

对于Java层frameworks的事件处理流程分析如下。

 

为了便于分析应用部分的事件分发过程,我们使用两个调用栈来分析,通过对比以及有针对性的分析,我们能明确事件在应用部分的分发机制。(我们用颜色标识出事件在应用各层分发的转折点,按键事件分发基本上可以经过如下几层,先是通过消息循环,再经过输入法层(可选),再经过View通用组件实现ViewRootImpl,然后分发PhoneWindow,再给activity通用组件或ViewGroup通用组件,最后给实际的窗口activity。)

 

 

第一个栈是在短信编辑界面,按下侧音量键,获取到如下函数调用栈(基于4.4的代码)。

ComposeMessageActivity.onKeyDown(int, KeyEvent) line: 5200        

KeyEvent.dispatch(KeyEvent$Callback, KeyEvent$DispatcherState, Object) line: 2706   

ComposeMessageActivity(Activity).dispatchKeyEvent(KeyEvent) line: 2456         

PhoneWindow$DecorView.dispatchKeyEvent(KeyEvent) line: 2150    

ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl$QueuedInputEvent) line: 4289 

ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl$QueuedInputEvent) line: 4257  

ViewRootImpl$ViewPostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 3861

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) ViewRootImpl$NativePostImeInputStage(ViewRootImpl$AsyncInputStage).forward(ViewRootImpl$QueuedInputEvent) line: 3937     

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$QueuedInputEvent, int) line: 3838    

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$AsyncInputStage).apply(ViewRootImpl$QueuedInputEvent, int) line: 3994   

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 3861  

ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$QueuedInputEvent, int) ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) ViewRootImpl$ImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) ViewRootImpl$ImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) line: 3830

ViewRootImpl$ImeInputStage(ViewRootImpl$AsyncInputStage).forward(ViewRootImpl$QueuedInputEvent)ViewRootImpl$ImeInputStage.onFinishedInputEvent(Object, boolean) line: 4139 

InputMethodManager$PendingEvent.run() line: 2057        

InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager$PendingEvent, boolean) InputMethodManager.finishedInputEvent(int, boolean, boolean) line: 1725 

InputMethodManager$ImeInputEventSender.onInputEventFinished(int, boolean) line: 2034    

InputMethodManager$ImeInputEventSender(InputEventSender).dispatchInputEventFinished(int, boolean) MessageQueue.nativePollOnce(int, int) line: not available [native method]      

MessageQueue.next() line: 138      

Looper.loop() line: 150

ActivityThread.main(String[]) line: 5327

Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]     

Method.invoke(Object, Object...) line: 515       

ZygoteInit$MethodAndArgsCaller.run() line: 824     

ZygoteInit.main(String[]) line: 640

NativeStart.main(String[]) line: not available [native method]     

 

 

第二个栈是在短信列表按侧键,应用捕获到事件后,逐层向上传递,在ListView处理事件的时候,发现activity并未注册相关监听器,直接返回。

ListView(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1422

RelativeLayout(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1420

FrameLayout(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1420   

ActionBarOverlayLayout(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1420  

PhoneWindow$DecorView(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1420        

ViewRootImpl$ViewPreImeInputStage.processKeyEvent(ViewRootImpl$QueuedInputEvent) line: 4088  

ViewRootImpl$ViewPreImeInputStage.onProcess(ViewRootImpl$QueuedInputEvent) line: 4081    

ViewRootImpl$ViewPreImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) ViewRootImpl$NativePreImeInputStage(ViewRootImpl$InputStage).onDeliverToNext()line: 3861  

ViewRootImpl$NativePreImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) ViewRootImpl$NativePreImeInputStage(ViewRootImpl$AsyncInputStage).forward(ViewRootImpl$) line: 3937

ViewRootImpl$NativePreImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$QueuedInputEvent, int) ViewRootImpl$NativePreImeInputStage(ViewRootImpl$AsyncInputStage).apply(ViewRootImpl$, int) line: 3994

ViewRootImpl$NativePreImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) ViewRootImpl.deliverInputEvent(ViewRootImpl$QueuedInputEvent) line: 6126   

ViewRootImpl.doProcessInputEvents() line: 6106    

ViewRootImpl.enqueueInputEvent(InputEvent, InputEventReceiver, int, boolean) line: 6060    

ViewRootImpl$WindowInputEventReceiver.onInputEvent(InputEvent) line: 6256 

ViewRootImpl$WindowInputEventReceiver(InputEventReceiver).dispatchInputEvent(int, InputEvent) line: 185

MessageQueue.nativePollOnce(int, int) line: not available [native method]    

MessageQueue.next() line: 138      

Looper.loop() line: 150

ActivityThread.main(String[]) line: 5327

Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]     

Method.invoke(Object, Object...) line: 515       

ZygoteInit$MethodAndArgsCaller.run() line: 824     

ZygoteInit.main(String[]) line: 640

NativeStart.main(String[]) line: not available [native method]      

 

我们对上面两个栈进行对比分析,总结如下,

 

1)消息队列的处理

这一部分是相同的,都是应用程序的Looper被唤醒,处理InputChannel传递过来的消息。根据前面及相关文档对Looper和消息队列的分析,知道MessageQueue.nativePollOnce直接处理物理事件,将事件交付给ViewRootImpl,由其事件监听器WindowInputEventReceiver来处理。对于编辑器存在的情况,有点不一样,我们另外分析。

 

2)输入法处理

暂不分析。(经过后续的分析,我们可以了解到,输入法只是InputStage的一个部分,故暂也不单独分析)

 

 

 

 

3)ViewRootImpl部分实现

3.1)编辑器存在

暂不分析

 

 

 

 

3.1)编辑器不存在

当编辑器不存在时,如第二个栈的情况,由于WindowInputEventReceiver实例创建时将自身传递给Jni,所以底层事件事件上报时,会交付给ViewRootImpl.WindowInputEventReceiver来处理,它调用enqueueInputEvent,后者将event使用obtainQueuedInputEvent的节点封装,然后调用doProcessInputEvents循环处理事件缓冲里的事件,处理函数为deliverInputEvent,

    private void deliverInputEvent(QueuedInputEvent q) {

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");

        try {

            if (mInputEventConsistencyVerifier != null) {

                mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);

            }

 

            InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage :mFirstInputStage;

            if (stage != null) {

                stage.deliver(q);

            } else {

                finishInputEvent(q);

            }

        } finally {

            Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        }

    }

deliverInputEvent的主要处理部分为stage.deliver,通过栈我们知道stage为NativePreImeInputStage的引用,通过代码我们分析mFirstPostImeInputStage和 mFirstInputStage赋值的地方,可知在setView里,mFirstInputStage被赋值为NativePreImeInputStage的引用,这就是我们前面在SetView分析提到的第四部分的一部分代码的使用。

SetView:

                // Set up the input pipeline.

                CharSequence counterSuffix = attrs.getTitle();

                InputStage syntheticInputStage = new SyntheticInputStage();

                InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticInputStage);

                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 = newNativePreImeInputStage(viewPreImeStage,

                        "aq:native-pre-ime:" + counterSuffix);

 

                mFirstInputStage = nativePreImeStage;

                mFirstPostImeInputStage = earlyPostImeStage;

                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;

 

这样,就进入了NativePreImeInputStage,它的功能是Delivers pre-ime input events to a native activity. Does not support pointer events.,这里调用祖父类InputStage的deliver,再调用父类AsyncInputStage的apply,

        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));

            }

        }

 

因为onProcess()返回值为参数,还需要先执行自己的onProcess方法,第一次进入会返回FORWARD,

        protected int onProcess(QueuedInputEvent q) {

            if (mInputQueue != null && q.mEvent instanceof KeyEvent) {

                mKeyEventStatus = INPUT_DISPATCH_STATE_NATIVE_PRE_IME_STAGE;

                mInputQueue.sendInputEvent(q.mEvent, q, true, this);

                return DEFER;

            }

            return FORWARD;

        }

 

所以在InputStage的apply里面会进入forward分支,表示事件刚被转发进来,等待后续处理。

        protected void apply(QueuedInputEvent q, int result) {

            if (result == FORWARD) {

                forward(q);

            } else if (result == FINISH_HANDLED) {

                finish(q, true);

            } else if (result == FINISH_NOT_HANDLED) {

                finish(q, false);

            } else {

                throw new IllegalArgumentException("Invalid result: " + result);

            }

        }

 

根据调用栈AsyncInputStage.forward的line号为3937,可知在AsyncInputStage.forward执行时,碰到mQueueHead为空而进入父类的forward,进而调用到onDeliverToNext,

         protected void onDeliverToNext(QueuedInputEvent q) {

            if (mNext != null) {

                mNext.deliver(q);

            } else {

                finishInputEvent(q);

            }

        }

面对上面的代码,我们需要十分小心,代码看似简单,如果没有仔细查看上下文代码,则容易走入代码分析的歧途,走入错误的代码流程进行分析。幸好我们借助于函数调用栈,发现mNext的真正类型是ViewPreImeInputStage,而不是NativePreImeInputStage,这点很重要!

 

如章回小说里所说,花开两支,单表一朵。这里我们先分析mNext的由来,再分析事件后续流程。

 

mNext的初始实际上也简单,如果留心就能注意到,它是在NativePreImeInputStage实例创建时,有一个ViewPreImeInputStage实例的引用作为参数传递进去,通过逐层调用父类的构造函数,将ViewPreImeInputStage引用赋值给InputStage的mNext。对于ViewPreImeInputStage,其实例在之前提到的SetView的第四部分创建,我们如果再仔细分析一下SetView的第四部分的代码,发现其后续实例都是用前面实例的应用作为参数,以此形成一个事件分发通道。

这个事件分发通道总结如下:

 

 

再回到事件处理流程上来,代码走到ViewPreImeInputStage.deliver,执行ViewPreImeInputStage.onProcess,再调用processKeyEvent,通过view的dispatchKeyEventPreIme将事件分发出去,完成本阶段的事件处理。

        private int processKeyEvent(QueuedInputEvent q) {

            final KeyEvent event = (KeyEvent)q.mEvent;

            if (mView.dispatchKeyEventPreIme(event)) {

                return FINISH_HANDLED;

            }

            return FORWARD;

        }

ViewRootImpl类的成员变量mView的类型为DecorView,它是由ActivityThread类第一次Resume当前的Activity窗口时创建的,具体可以参考ActivityThread类的handleResumeActivity成员函数。

 

 

4)ViewGroup的事件处理

上面提到,ViewRootImpl通过View实例,将事件用dispatchKeyEventPreIme分发出来,ViewGroup重写了该方法,所以事件会传递到ViewGroup进行处理。

 

为了便于分析,我们将栈2的部分内容再次呈现在下面,

ListView(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1422

RelativeLayout(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1420

FrameLayout(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1420   

ActionBarOverlayLayout(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1420  

PhoneWindow$DecorView(ViewGroup).dispatchKeyEventPreIme(KeyEvent) line: 1420        

 

ViewGroup. dispatchKeyEventPreIme代码如下,由当前焦点view负责按键处理。由于view的嵌套关系,焦点由ActionBarOverlayLayout,变为FrameLayout,RelativeLayout,ListView,

    public boolean dispatchKeyEventPreIme(KeyEvent event) {

        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))

                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {

            return super.dispatchKeyEventPreIme(event);

        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)

                == PFLAG_HAS_BOUNDS) {

            return mFocused.dispatchKeyEventPreIme(event);

        }

        return false;

    }

 

最后由ListView调用View. dispatchKeyEventPreIme,由方法名字可以知道,这个方法功能是在输入法之前先处理事件,“Dispatch a key event before it is processed by any input method   associated with the view hierarchy”,当前view不处理该事件,返回false表示将事件给下一个接受者继续处理。这样完成了在ViewPreImeInputStage这一个输入状态下的按键处理。

 

 

5)新的一轮,下一个输入状态

5.1)栈2的下一个状态

如上节,因为mView.dispatchKeyEventPreIme返回false,所以ViewPreImeInputStage. processKeyEvent返回FORWARD,这样就进入forward方法,一个新的调用栈就产生了,

 

InputMethodManager.sendInputEventOnMainLooperLocked(InputMethodManager$PendingEvent) line: 1695  

InputMethodManager.dispatchInputEvent(InputEvent, Object, InputMethodManager$FinishedInputEventCallback, Handler) line: 1651 

ViewRootImpl$ImeInputStage.onProcess(ViewRootImpl$QueuedInputEvent) line: 4115        

ViewRootImpl$ImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) line: 3811

ViewRootImpl$ViewPreImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$) line: 3861

ViewRootImpl$ViewPreImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$) line: 3830  

ViewRootImpl$ViewPreImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$, int) line: 3838

ViewRootImpl$ViewPreImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$) line: 3811    

ViewRootImpl$NativePreImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$)line: 3861

 

从调用栈从可以看到新的ImeInputStage被引入了,其初始化过程和ViewPreImeInputStage是使用了相同的方法,调用过程了相似。其onProcess调用了输入法管理器的方法dispatchInputEvent处理事件,

         protected int onProcess(QueuedInputEvent q) {

            if (mLastWasImTarget && !isInLocalFocusMode()) {

                InputMethodManager imm = InputMethodManager.peekInstance();

                if (imm != null) {

                    mKeyEventStatus = INPUT_DISPATCH_STATE_IME_STAGE;

                    final InputEvent event = q.mEvent;

                    if (DEBUG_IMF || DEBUG_INPUT || DEBUG_KEY) {

                        Xlog.v(TAG, "Sending input event to IME: " + event + ", this = " + this);

                    }

                    int result = imm.dispatchInputEvent(event, q, this, mHandler);

                    if (result == InputMethodManager.DISPATCH_HANDLED) {

                        return FINISH_HANDLED;

                    } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {

                        /// M : should return FORWARD instead of FINISH_NOT_HANDLED

                        return FORWARD;

                    } else {

                        return DEFER; // callback will be invoked later

                    }

                }

            }

            return FORWARD;

        }

 

在本例中输入法使用异步处理方式并且当前界面并无输入法窗口,所以返回DEFER,之后逐步退栈,完成本轮的事件处理。注意本轮按键处理是事件按下处理,keyDown事件。

这里的处理4.4和2.3也很不一样,在2.3代码里面,

1.   imm.dispatchKeyEvent(mView.getContext(), seq, event,  

2.                       mInputMethodCallback);  

InputMethodManager处理完这个键盘事件后,再回调用这里的mInputMethodCallback对象的finishedEvent成员函数来把键盘事件分发给当前激活的Activity窗口处理。

在4.4代码,则是使用了连串的事件状态处理通道进行处理的。

 

 

5.2)Activity处理事件

鉴于栈二的事件不能传递到Activity,我们再借用栈一来分析事件是怎么传递到Activity的。

ComposeMessageActivity.onKeyDown(int, KeyEvent) line: 5200        

KeyEvent.dispatch(KeyEvent$Callback, KeyEvent$DispatcherState, Object) line: 2706   

ComposeMessageActivity(Activity).dispatchKeyEvent(KeyEvent) line: 2456         

PhoneWindow$DecorView.dispatchKeyEvent(KeyEvent) line: 2150    

ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl$QueuedInputEvent) line: 4289 

 

可以看出,通过ViewPostImeInputStage的processKeyEvent,调用到mView.dispatchKeyEvent(event),mView是DecorView,故将事件传递给DecorView. dispatchKeyEvent,下面代码中getCallback函数返回的是当前应用程序的激活的Activity窗口的Window.Callback接口,一般它指向activity引用,本例是ComposeMessageActivity,因此,这个函数会调用Activity类的dispatchKeyEvent来处理这个键盘事件。

         public boolean dispatchKeyEvent(KeyEvent event) {

            if (!isDestroyed()) {

                final Callback cb = getCallback();

                final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)

                        : super.dispatchKeyEvent(event);

                if (handled) {

                    return true;

                }

            }

}

 

对于getCallback,再补充说明一下,它是Window类的一个方法,它和setCallback配对使用,

    public void setCallback(Callback callback) {

        mCallback = callback;

    }

 

    public final Callback getCallback() {

        return mCallback;

    }

对于Activity,它存放的是Activity的引用,setCallback在Activity.attach中被调用,可以看到Activity的this指针被传递进去,所以在按键流程处理中我们知道按键该怎么处理了。

         mWindow = PolicyManager.makeNewWindow(this);

        mWindow.setCallback(this);

 

 

在Activity的dispatchKeyEvent中,调用KeyEvent. Dispatch()处理事件,根据其参数可以看出,事件会传递给receiver接收,处理事件时会根据事件分发状态进行,根据一个键是按下(ACTION_DOWN)、还是松开(ACTION_UP)或者是一个相同的键被多次按下和松开(ACTION_MULTIPLE)等不同事件类型来分别调用Activity的onKeyDown、onKeyUp和onKeyMultiple函数了。

对于Key Down事件,代码如下,因为ComposeMessageActivity重写了onKeyDown,所以先执行自身以及父类的onKeyDown方法,再根据输入状态,看是否要处理长按等事件。

对于没有重写onKeyDown的Activity,则会执行Activity类的onKeyDown方法。

    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;

            }

}

 

 

6)事件处理完成

Activity完成Key  Down事件处理后,调用栈逐步返回,在InputStage及相关子类里调用finish,完成本轮事件的处理。

具体流程是这样,当前InputStage完成OnProcess()方法处理,如果事件已经被处理过,首先通过自己的apply方法进入其finish,finish设置事件的flag,再用forward转发事件到候选的InputStage,直到后续的InputStage为空,调用finishInputEvent,Deliver返回到上一个InputStage的onDeliverToNext,再由上一个的apply方法进入其finish,依次规则执行,完成所有InputStage的处理,也因此完成了在

ViewRootImpl的事件处理,最终完成事件的处理。

 

Apply的代码如下,事件被OnProcess处理后进入finish。

         protected void apply(QueuedInputEvent q, int result) {

            if (result == FORWARD) {

                forward(q);

            } else if (result == FINISH_HANDLED) {

                finish(q, true);

            } else if (result == FINISH_NOT_HANDLED) {

                finish(q, false);

            } else {

                throw new IllegalArgumentException("Invalid result: " + result);

            }

        }

 

因为栈一对应界面含有编辑器,ImeInputStage会产生DEFER退出当前事件处理,我们以栈二为例说明,事件被OnProcess处理后,由ViewPostImeInputStage通过自己的apply进入finish,finish设置事件的flag,再用forward转发事件,

        protected void finish(QueuedInputEvent q, boolean handled) {

            q.mFlags |= QueuedInputEvent.FLAG_FINISHED;

            if (handled) {

                q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;

            }

            forward(q);

        }

 

Forward会进入下一个InputStage节点SyntheticInputStage,SyntheticInputStage处理完后,调用 ViewRootImpl.finishInputEvent,调用栈如下:

ViewRootImpl.finishInputEvent(ViewRootImpl$QueuedInputEvent) line: 6141     

ViewRootImpl.access$1400(ViewRootImpl, ViewRootImpl$QueuedInputEvent) line: 106      

ViewRootImpl$SyntheticInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 3863

ViewRootImpl$SyntheticInputStage.onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 4481       

ViewRootImpl$SyntheticInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) line: 3830     

ViewRootImpl$SyntheticInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$QueuedInputEvent) line: 3807       

ViewRootImpl$ViewPostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 3861  

ViewRootImpl$ViewPostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$QueuedInputEvent) line: 3830        

 

ViewRootImpl.finishInputEvent主要代码如下,

     private void finishInputEvent(QueuedInputEvent q) {

        boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;

            mPreviousKeyEvent = mCurrentKeyEvent;

            mPreviousKeyEventFinishTime = System.currentTimeMillis();

            mCurrentKeyEvent = null;

            stage = mKeyEventStatus;

            mKeyEventStatus = INPUT_DISPATCH_STATE_FINISHED;

        if (q.mReceiver != null) {

            //boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;

            q.mReceiver.finishInputEvent(q.mEvent, handled);

        } else {

            q.mEvent.recycleIfNeededAfterDispatch();

        }

 

        recycleQueuedInputEvent(q);

    }

}

它对事件简单处理后,使用q.mReceiver.finishInputEvent进行事件的事件处理,mReceiver就是ViewRootImpl$WindowInputEventReceiver,这个我们就不陌生了,就是我们之前注册的事件接收器,其方法finishInputEvent则是其父类方法,里面将事件从消息队列移除出去,并使用native方法nativeFinishInputEvent完成事件的处理。

 

 nativeFinishInputEvent功能有几点,一是通知其它系统,InputDispatcher完成了一次键盘事件的处理; 二是调用相应的connection对象的内部对象inputPublisher来的reset函数来回收一些资源三是调用InputDispatcherstartNextDispatchCycleLocked函数来处理下一个键盘事件。

 

至此,Key  Down的事件处理算是完成了。

 

 

7)Key Up流程

上面的流程还只是Key  Down的一个大致流程,对于按键行为,总是会产生两个事件,按下和释放,我们来分析Key Up事件。

Key Up事件的个人经历比较丰富,它经历了InputMethodManager、ImeInputStage 、EarlyPostImeInputStage 、NativePostImeInputStage 、ViewPostImeInputStage等一系列的检验,辗转来到Activity,通过dispatchKeyEvent进行事件传递。具体的代码就不再分析了。

由此我们可以看到一个事件从底层传递到上层是多么的不容易,真可以说是跋山涉水,历经坎坷。

 

调用栈:

ConversationList(Activity).dispatchKeyEvent(KeyEvent) line: 2456     

PhoneWindow$DecorView.dispatchKeyEvent(KeyEvent) line: 2150    

ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl$QueuedInputEvent) line: 4289 

ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl$QueuedInputEvent) line: 4257  

ViewRootImpl$ViewPostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$) line: 3811  

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext($) line: 3861       

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$) line: 3830       

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$AsyncInputStage).forward(ViewRootImpl$) line: 3937

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$, int) line: 3838     

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$AsyncInputStage).apply(ViewRootImpl$, int) line: 3994

ViewRootImpl$NativePostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$) line: 3811

ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).onDeliverToNext($) line: 3861         

ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$) line: 3830

ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).apply(ViewRootImpl$, int) line: 3838       

ViewRootImpl$EarlyPostImeInputStage(ViewRootImpl$InputStage).deliver(ViewRootImpl$) line: 3811  

ViewRootImpl$ImeInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$) line: 3861 

ViewRootImpl$ImeInputStage(ViewRootImpl$InputStage).forward(ViewRootImpl$) line: 3830      

ViewRootImpl$ImeInputStage(ViewRootImpl$AsyncInputStage).forward(ViewRootImpl$) line: 3970     

ViewRootImpl$ImeInputStage.onFinishedInputEvent(Object, boolean) line: 4139 

InputMethodManager$PendingEvent.run() line: 2057        

InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager$, boolean) line: 1734      

InputMethodManager.finishedInputEvent(int, boolean, boolean) line: 1725   

InputMethodManager$ImeInputEventSender.onInputEventFinished(int, boolean) line: 2034    

InputMethodManager$ImeInputEventSender(InputEventSender).dispatchInputEventFinished(int,) line: 141       

MessageQueue.nativePollOnce(int, int) line: not available [native method]    

 

之后,它执行这个流程,进行按键音播放。

AudioManager.handleKeyUp(KeyEvent, int) line: 578      

PhoneWindow.onKeyUp(int, int, KeyEvent) line: 1730     

PhoneWindow$DecorView.dispatchKeyEvent(KeyEvent) line: 2190 

 

并在之后走完SyntheticInputStage这个状态。

ViewRootImpl$SyntheticInputStage(ViewRootImpl$InputStage).onDeliverToNext(ViewRootImpl$QueuedInputEvent) line: 3863      

 

 

4.1.3.4                                  Native事件完成处理

 

前面介绍了应用部分事件完成处理,事件处理完成后,要通知给native层,进行收尾处理,这里暂不做分析。

 

 

4.1.3.5                                  事件分发总结

1.键盘事件发生,InputManager中的InputReader被唤醒,此前InputReader睡眠在/dev/input/event0这个设备文件上;

        2.对于和底层交互的方向, InputReader被唤醒后,它接着调用InputDispatcher,它是通过InputDispatcher注册在InputReader的监听器中,直接被调用;对于和应用的交互方向,InputDispatcher睡眠在InputManager所运行的线程中的Looper对象里面的通道的读端上,并准备读取应用发过来的事件;

        3. InputDispatcher收到事件后,它接着唤醒NativeInputEventReceiver来处理这个键盘事件,Receiver里关联到应用程序的主线程的Looper和消息队列,此前NativeInputEventReceiver睡眠在InputChannel1的读端上;

        4.应用程序处理处理完键盘事件之后,它接着唤醒InputDispatcher来执行按键流程的收尾工作,此前InputDispatcher睡眠在ServerInputChannel0的读端上。

这个事件分发流程如下图描述:

这个图和前面的代码分析表达的意思是一样的,事件处理流程如上面的红色箭头所示,表示按键从Driver层过来,到达HAL层,经过InputReader,传递给InputDispatcher,通过socket,发送出去,NativeInputEventReceiver监听到socket事件,通过Jni方法,将事件传递给应用层,应用经过ViewRootImpl的复杂处理,传递给界面Activity,当事件处理完成后,ViewRootImpl再通过Jni返回事件处理完成通知,NativeInputEventSender通过Socket告知InputDispatcher这一状况,最终完成整个事件的处理。

 

4.2         取消注册

 Activity窗口创建时,它会向InputManager注册键盘消息接收通道,而当Activity窗口销毁时,它就会向InputManager注销前面注册的键盘消息接收通道了。

 

当我们按下键盘上的Back键时,当前激活的Activity窗口就会被失去焦点,但是这时候它还没有被销毁,它的状态被设置为Stopped;当新的Activity窗口即将要显示时,它会通知WindowManagerService,这时候WindowManagerService就会处理当前处理Stopped状态的Activity窗口了,要执行的操作就是销毁它们了,在销毁的时候,就会注销它们之前所注册的键盘消息接收通道。

 

我们从ActvitiyRecordwindowsVisible函数开始分析。注意,这里的ActivityRecord是新的Activity窗口在ActivityManangerService的代表,而那些处于Stopped状态的Activity窗口

会放在ActivityStack类的一个等待可见的mWaitingVisibleActivities列表里面,事实于,对于那些Stopped状态的Activity窗口来说,它们是等待销毁,而不是等待可见。

   public void windowsVisible() {

        synchronized(service) {

            mStackSupervisor.reportActivityVisibleLocked(this);

            if (!nowVisible) {

                nowVisible = true;

                lastVisibleTime = SystemClock.uptimeMillis();

                if (!idle) {

                    // Instead of doing the full stop routine here, let's just

                    // hide any activities we now can, and let them stop when

                    // the normal idle happens.

                    mStackSupervisor.processStoppingActivitiesLocked(false);

                } else {

                    // If this activity was already idle, then we now need to

                    // make sure we perform the full stop of any activities

                    // that are waiting to do so.  This is because we won't

                    // do that while they are still waiting for this one to

                    // become visible.

                    final int N = mStackSupervisor.mWaitingVisibleActivities.size();

                    if (N > 0) {

                        for (int i=0; i<N; i++) {

                            ActivityRecord r = mStackSupervisor.mWaitingVisibleActivities.get(i);

                            r.waitingVisible = false;

                            if (ActivityManagerService.DEBUG_SWITCH) Log.v(

                                    ActivityManagerService.TAG,

                                    "Was waiting for visible: " + r);

                        }

                        mStackSupervisor.mWaitingVisibleActivities.clear();

                        mStackSupervisor.scheduleIdleLocked();

                                }                }

                service.scheduleAppGcsLocked();

            }       }    }

 

应用程序中的每一个ActivityActivityManagerService都有一个代表ActivityRecord,它们以堆栈的形式组织在ActivityManaerService中的ActivityStack中。一个即将要显示,但是还没有显示的Activity,它在ActivityManagerService中的ActivityRecord的成员变量nowVisiblefalse,而成员变量idleture,表示这个即将要显示的Activity窗口处于空闲状态。

当用户按下键盘上的Back键时,当前激活的Activity记录就被放在ActivityStack对象stack的成员变量mStackSupervisor中了,这时候就要对它进行处理了。首先是将它们的Activity记录的waitingVisible设置为false,然后就把它们从ActivityStack对象stack的成员变量mWaitingVisibleActivities清空,最后向ActivityStackSupervisor对象发送一个IDLE_NOW_MSG消息。这个消息最终是由ActivityStackSupervisor类的activityIdleInternal函数来处理的。

 

activityIdleInternal首先会调用processStoppingActivitiesLocked函数把所有处于Stopped状态的Activity取回来,然后逐个分析它们,如果它们的ActivityRecord中的finishing成员变量为true,就说明这个Activity需要销毁了,于是,就调用ActivityStack finishCurrentActivityLocked函数来销毁它们。之后会调用到destroyActivityLocked

 

每一个应用程序进程在ActivityManagerService中,都ProcessRecord记录与之对应,而每一个Activity,都是运行在一个进程上下文中,因此,在ActivityManagerService中,每一个ActivityRecordapp成员变量都应该指向一个ProcessRecord记录,在ProcessRecord类中,有一个成员变量thread,它的类型为IApplicationThread

每一个应用程序在启动的时候,它都会在内部创建一个ActivityThread对象,而在这个ActivityThread对象中,有一个成员变量mAppThread,它的类型为ApplicationThread,这是一个Binder对象,专门用来负责在应用程序和ActivityManagerService之间执行进程间通信工作的。应用程序在启动的时候,就会将这个Binder对象传递给ActivityManagerService,而ActivityManagerService就会把它保存在相应的ProcessRecord记录的thread成员变量中。因此,ProcessRecord记录的thread成员变量其实就是ApplicationThread对象的远程接口,所以r.app.thread对应ApplicationThread类,scheduleDestroyActivity也是ApplicationThread类的方法。

 

ApplicationThread.scheduleDestroyActivity调用外部类ActivityThreadqueueOrSendMessage函数来往应用程序的消息队列中发送一个H.DESTROY_ACTIVITY消息,这个消息最终由ActivityThread类的handleDestroyActivity函数来处理。

handleDestroyActivity先调用performDestroyActivity来执行一些销毁Activity的操作,期间就会调用ActivityonDestroy函数让Activity本身有机会执行一些销毁前的工作了。这里通过r.activity.getWindowManager函数返回的是一个LocalWindowManager对象,而通过r.activity.mDecor得到的是一个DecorView对象,这些都是在Activity启动的时候设置好的。函数最后调用LocalWindowManager对象wmremoveViewImmediate函员来从LocalWindowManager移除这个DecorView对象。

 

之后在dispatchDetachedFromWindow方法里,会调用InputQueueunregisterInputChannel函数来注销注册在应用程序这一侧的ClientInputChannel,然后再调用sWindowSessionremove函数来注销注册在InputManager这一侧的ServerInputChannel,这个逻辑是和前面介绍应用程序注册键盘消息接收通道的逻辑相对应的,前面分别注册了这两个InputChannel,现在Activity要销毁了,当然就要把它们注销了。

 

注销注册在应用程序这一侧的ClientInputChannel,使用InputQueue.unregisterInputChannel,它调用本地方法nativeUnregisterInputChannel来执行具体的操作。真正的注销工作就是这里实现的了,它首先是将在之前创建的Connection对象从NativeInputQueue中的mConnectionByReceiveFd向量中删除,然后再把这个ClientInputChannel的通道的读端文件描述符从应用程序主线程中的Looper对象中删除,这样,这个Activity窗口以后就不会接收到键盘事件了。最后将Connection对象中的回调对象inputHandlerOjbGlobal对象删除。

 

注销注册在InputManager这一侧的ServerInputChannel,使用WindowManagerService.Session.remove,前面注册键盘消息通道的过程时,WindowManagerService为这个即将要激活的Activity窗口创建了一个WindowState对象win,把Server端的InputChannel保存了在这个WindowState对象的成员变量mInputChannel中。创建的时候,使用了从ViewRoot中传过来的两个参数,分别是一个Session对象session和一个IWindow对象client。在这个函数中,ViewRoot传过来的两个参数sessionclient和上面说的两个参数是一致的,因此,这个函数首先通过参数sessionclient得到一个WindowState对象win,然后调用removeWindowLocked来把它从WindowManagerService删除。注销的操作就是调用windisposeInputChannel进行的了,调用mInputManager对象的unregisterInputChannel函数来把它注销掉了。

 

InputManager.unregisterInputChannel调用本地方法nativeUnregisterInputChannel,在android_view_InputChannel_getInputChannel函数根据Java层的InputChannel对象找到C++层的InputChannel对象,然后调用NativeInputManagerunregisterInputChannel函数来执行注销的操作,主要是调用InputDispatcher对象的unregisterInputChannel函数来执行真正注销的操作。它从InputDispatcher中把Server端的InputChannel注销掉。首先是根据传进来的参数inputChannel找到它在InputDispatcher中对应的Connection对象在mConnectionsByReceiveFd中的索引,然后把它从mConnectionsByReceiveFd中删除,还需要把这个InputChannel中的通道读端文件描述符从InputDispatcher的内部对象mLooper中删除。

 

4.3         键盘布局

这里简单介绍一下android的键盘布局。

 

当有按键事件时,内核传给ANDROID的是scancodeANDROIDscancode经配置表(qwerty.kl)找到扫描码对应的是哪个字符(keycode标识符),然后由内部表KEYCODES列表找到keycode的数字值,再由keycode的数字值经字符配置表(如qwerty.kcm)找到对应的字符值(即一个字符码可能代码很多含义,使用合适的配置表并找到合适的含义)。

键布局映射文件通常放在/system/usr/keylayout/data/usr/keylayout,对于每一个键盘设备xxx,设置系统属性android.keylayout.xxx,如果没有为自己的硬件设备创建一个特定的设备,Android将去读/system/usr/keylayout/qwerty.kl这个配置文件。

http://www.kandroid.org/android_pdk/build_new_device.html

键字符映射位于:/system/usr/keychars/data/usr/keychars,比如对于一个特定的设备xxx,设置android.keychar.xxx系统属性,用全路径表示去描述所需的键字符映射。如果你没有描述任何一个键字符映射,系统将默认使用/system/usr/keychar/qwerty.kl

 

 

前面在分析按键处理时,讲到事件映射转换。在InputReader.cpp文件里,键盘处理函数KeyboardInputMapper::process调用EventHub::mapKey完成按键映射。

 void KeyboardInputMapper::process(const RawEvent* rawEvent) {

    switch (rawEvent->type) {

    case EV_KEY: {

        int32_t scanCode = rawEvent->code;

        int32_t usageCode = mCurrentHidUsage;

        mCurrentHidUsage = 0;

 

        if (isKeyboardOrGamepadKey(scanCode)) {

            int32_t keyCode;

            uint32_t flags;

            if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {

                keyCode = AKEYCODE_UNKNOWN;

                flags = 0;

            }

            processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);

        }

        break;

 

在EventHub.cpp的EventHub::mapKey里,先找到设备,再找到字符映射表,最后找到按键layout,详细的过程可以结合log和代码分析。

 status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,

        int32_t* outKeycode, uint32_t* outFlags) const {

    AutoMutex _l(mLock);

    Device* device = getDeviceLocked(deviceId);

 

    if (device) {

        // Check the key character map first.

        sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();

        if (kcm != NULL) {

            if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {

                *outFlags = 0;

                return NO_ERROR;

            }        }

 

        // Check the key layout next.

        if (device->keyMap.haveKeyLayout()) {

            if (!device->keyMap.keyLayoutMap->mapKey(

                    scanCode, usageCode, outKeycode, outFlags)) {

                return NO_ERROR;

            }        }

    }

 

    *outKeycode = 0;

    *outFlags = 0;

    return NAME_NOT_FOUND;

}

会优先加载/system/usr/keylayout/输入设备名称.kl

: /sys/class/input/input1/name = keypad,则会加载/system/usr/keylayout/keypad.kl,如果该文件不存在,则加载默认文件/system/usr/keylayout/qwerty.kl

 

按键映射:

先加载配置文件,再将读取到的按键码进行转换,是以如下方式对配置文件进行解析的:

BEGIN: 如果第一个关键字是key,则转入SCANCODE,否则退出。

SCANCODE: 将第二个关键字转为数字,即扫描码scancode,转向KEYCODE

KEYCODE: 将第三个关键字与KEYCODES列表配对,找出关键码keycode,转向FLAG

FLAG:如果第四个关键字是key,则保存刚扫描的键码,然后转向SCANCODE,否则将该关键字与FLAGS列表配对,找出flags值,然后转向BEGIN

 

 

文件格式:
键映射文件通常以UTF8文本文件格式存储于设备,通常有如下特性:
注释:用#表示,以#开头的内容都将被忽略。
空白:所有的空行被忽略
键定义:键定义遵循如下格式key SCANCODE KEYCODE [FLAGS...],当扫描码是一个数字,键码定义在你描述的布局文件android.keylayout.xxx,另外可以设置相关的FLAGS:
SHIFT:
当按下,自动加上SHIFT键值
ALT:
当按下,自动加上ALT
CAPS:
当按下,自动带上CAPS大写
WAKE:
当按下,当设备进入睡眠的时候,按下这个键将唤醒,而且发送消息给应用层。
WAKE_DROPPED:
当按下,且设备正处于睡眠,设备被唤醒,但是不发送消息给应用层。

键盘映射文件示例:

qwerty.kl 的部分配置:

#  scancode    keycode      flags

key 399   GRAVE

key 2     1

key 3     2

key 4     3

key 5     4

key 6     5

key 7     6

key 8     7

key 9     8

key 10    9

key 11    0

key 158   BACK              WAKE_DROPPED

key 230   SOFT_RIGHT        WAKE

key 60    SOFT_RIGHT        WAKE

 

文件格式:
键字符映射文件以二进制减少加载时间的形式存储于设备中,键字符映射文件有如下特征:
注释:以#开始为注释
空行:所有的空行被忽略
列定义:当一个事件来临的时候按下组合键。这个事通常是MODIFIER_SHIFT,MODIFIER_CTRL,MODIFIER_ALT的组合。
O                                         no modifiers
S                                         MODIFIER_SHIFT
C                                         MODIFIER_CONTROL
L                                         MODIFIER_CAPS_LOCK
A                                         MODIFIER_ALT
键值定义:键值定义遵循如下规则:
扫描码字符[....]
扫描码和字符通常是一个十进制的值或者是UTF8字符,可以通过strtol的解析。

键字符文件的示例:
下面这个文件来自于android/src/device/product/generic/tuttle2.kcm,代表了一个完整的键字符文件。
type开始的语句描述了你所要描述键盘的类型,大体分为三种
1
NUMERIC,12键的数字键盘
2
Q14:键盘包括所有的字符,但是可以一键多个字符。
3
QWERTY键盘包括了所有可能的字符和数字,类似于全键盘。
下面是一个QWERTY全键盘的定义示例,因为android主要用于手机,手机一般是全键。
# Copyright 2007 The Android Open Source Project

[type=QWERTY]

# keycode   base    caps    fn      caps_fn number  display_label

A           'a'     'A'     '%'     0x00    '%'     'A'
B           'b'     'B'     '='     0x00    '='     'B'
C           'c'     'C'     '8'     0x00E7  '8'     'C'
D           'd'     'D'     '5'     0x00    '5'     'D'
E           'e'     'E'     '2'     0x0301  '2'     'E'
F           'f'     'F'     '6'     0x00A5  '6'     'F'
G           'g'     'G'     '-'     '_'     '-'     'G'
H           'h'     'H'     '['     '{'     '['     'H'

 

资源二进制格式

上面所描述的一段通过makekcharmap工具转换成下面的格式,用户可以通过mmap这个文件,用于进程之间共享大概4K数据,可以节省加载时间。
Offset                          Size (bytes)                          Description
0x00-0x0b                                                         The ascii value "keycharmap1" including the null character
0x0c-0x0f                                                         padding
0x10-0x13                                                         The number of entries in the modifiers table (COLS)
0x14-0x17                                                         The number of entries in the characters table (ROWS)
0x18-0x1f                                                         padding
                                4*COLS                                Modifiers table. The modifier mask values that each of the columns inthe characters table correspond to.
                                                                        padding to the next 16 byte boundary
                                4*COLS*ROWS                 Characterstable. The modifier mask values that each of the columns correspond to.

 

字符映射

KeyCharacterMap::load先查看hw.keyboards.%u.devname的属性,若存在则打开/system/usr/keychars/%s.kcm.bin文件,否则打开默认文件:/system/usr/keychars/qwerty.kcm.bin

在代码里设置了KeyCharacterMap::load hw.keyboards.%u.devname所需要的值,即设备名称,如: /sys/class/input/input1/name = keypad,则会加载/system/usr/keychars/keypad.kcm.bin

 

例:

/sys/class/input/input1/name = keypad

则要打开的文件路径是:

/system/usr/keylayout/keypad.kl

/system/usr/keychars/keypad.kcm.bin

若以上文件不存在,则使用默认文件:

/system/usr/keylayout/qwerty.kl

/system/usr/keychars/qwerty.kcm.bin

 

 

 

 

上述结果可以用log来验证:



表明这个GPIO键盘设备的节点名字为/dev/input/event4,在节点列表中id为2,并使用的Keylayout和KeyChars设置如下,

New device: id=2, fd=121, path='/dev/input/event4', name='mtk-tpd-kpd', classes=0x1, configuration='', keyLayout='/system/usr/keylayout/Generic.kl', keyCharacterMap='/system/usr/keychars/Generic.kcm', builtinKeyboard=false, usingSuspendBlockIoctl=true, usingClockIoctl=true

 

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

开发故事中的各种小虫(持续更新)

开发故事之吃掉的小虫子

注释 Android Framework 中的一堆 debug 信息

1. VDO_LOG, 位置:kernel-310/include/linux/vcodec/user 2.
  • zhbpd
  • zhbpd
  • 2017年02月17日 09:57
  • 278

Android源码解析(二十九)-->应用程序返回按键执行流程

从这篇文章中我们开始分析android系统的事件分发流程,其实网上已经有了很多关于android系统的事件分发流程的文章,奈何看了很多但是印象还不是很深,所以这里总结一番。 android系统的事件分...

Android 按音量键的调用栈

01-25 16:17:51.875 I/AudioManager( 2837): java.lang.Exception 01-25 16:17:51.875 I/AudioManager( 28...
  • ttzixi
  • ttzixi
  • 2014年07月22日 09:43
  • 695

android 4.4 按键分析一

在分析按键处理过程中,我们要先准一些基础知识,才能看懂事件处理流程。实际分析时,也可以先不用详细看这些知识,等需要时再回头分析。 3.1         Input子系统 3.1.1     ...

Android中的Parcel机制

一.先从Serialize说起          我们都知道JAVA中的Serialize机制,译成串行化、序列化……,其作用是能将数据对象存入字节流当中,在需要时重新生成对象。主要应用是利用外部存...

android 4.4 按键分析二

.5         Android Framework层消息处理   3.5.1         基本介绍 关于Android消息处理机制的全面分析,可参考另外的文档,这里着重介绍事件...

SOCKET DUP 使用心得

1、客户端绑定端口的问题 对于UDP,客户端不需要调用BIND绑定本机地址和端口,即便是绑定了本机地址,服务器也不能从这个绑定的端口发送过来信息,因为到路由器就会被丢弃,本机是根本接不到的,那客户端...

C和C++混合编程中点点滴滴

C和C++混合编程 在很多情况下,C系统中会用到C++的代码,而C++中也会用到C代码。所以在Linux系统下充斥中.h .c .cpp后缀的代码文件。这样就引入了一个问题:如何让这些代码很好的...
  • herobox
  • herobox
  • 2012年11月18日 01:25
  • 1134

android 4.4 按键分析四--键盘添加

。   5.       Driver 5.1         KeyPad 5.1.1         基本介绍 这部分简单介绍Keypad的基本知识。   对于输入设备, 一般支持...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:android 4.4 按键分析三
举报原因:
原因补充:

(最多只允许输入30个字)