Android 事件

来源:互联网 发布:python re 匹配域名 编辑:程序博客网 时间:2024/05/18 14:46

事件分为按键事件分发,触摸事件分发,还有轨迹球事件,轨迹球已经被淘汰,按键事件分发主要是在TV上,使用遥控器做按键操作。触摸事件分发及pointerEvent则是触摸屏设备的触摸点分发,此处主要讨论keyEvent事件分发。

分发过程:

第一步,WindowManagerServiceWMS) 中有一个KeyInputQueue的子类,该类内部有一个thread去调用native方法读取用户的按键,触摸消息,然后把消息保存到QueueEvent的消息队列中,然后WMS中有一个对应的hander来处理这个输入消息队列InputDispatcherThread,它内部有一个线程对这些消息就行处理,然后分发给对应的窗口(至于怎么匹配对应的窗口这里不说)。

第二步:wms通过IPCBinder机制把消息转给对应窗口PhoneWindow,它是actiivty 的成员变量mWindow,PhoneWindow是一个window的子类,mWindow里面有一个成员变量mWindowManager,而mWindowManager是WindowMangerImpl类实例的引用,另外WindowMangerImpl里面包含ViewRoot,这个Viewroot就是Actvity与通信的一个handler对象,ViewRoot对应一个ViewRootImpl,其实现了一个InputHandler,ViewRoot拿到消息后调用InputHandler去调用handleKey函数,然后该函数再调用ViewRott的dispatchKey函数,会发送一个DISPATCH_KEY消息,然后调用deliverKeyEvent函数然后分三步走:

    1.dispatchKeyEventPreIme  (分发给输入法给一个机会去处理)

      2.输入法去响应,如果输入法窗口处理了这个消息则直接返回,否则走第三步

      3. deliverKeyEventPostIme(event, sendDone);

下面是这三步的源码:

1. private void deliverKeyEvent(KeyEvent event, boolean sendDone) {  

2.         if (ViewDebug.DEBUG_LATENCY) {  

3.             mInputEventDeliverTimeNanos = System.nanoTime();  

4.         }  

5.   

6.         if (mInputEventConsistencyVerifier != null) {  

7.             mInputEventConsistencyVerifier.onKeyEvent(event, 0);  

8.         }  

9.   

10.         // If there is no view, then the event will not be handled.  

11.         if (mView == null || !mAdded) {  

12.             finishKeyEvent(event, sendDone, false);  

13.             return;  

14.         }  

15.   

16.         if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);  

17.   

18.         // Perform predispatching before the IME.  

19.         if (mView.dispatchKeyEventPreIme(event)) {  

20.             finishKeyEvent(event, sendDone, true);  

21.             return;  

22.         }  

23.   

24.         // Dispatch to the IME before propagating down the view hierarchy.  

25.         // The IME will eventually call back into handleFinishedEvent.  

26.         if (mLastWasImTarget) {  

27.             InputMethodManager imm = InputMethodManager.peekInstance();  

28.             if (imm != null) {  

29.                 int seq = enqueuePendingEvent(event, sendDone);  

30.                 if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="  

31.                         + seq + " event=" + event);  

32.                 imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);  

33.                 return;  

34.             }  

35.         }  

36.   

37.         // Not dispatching to IME, continue with post IME actions.  

38.         deliverKeyEventPostIme(event, sendDone);  

39.     }  

第三步:我们不考虑输入法窗口拦截key事件的操作,直接进入第三步deliverKeyEventPostIme,从里面进去有这么一段关键代码    

1.   if (mView.dispatchKeyEvent(event)) {  

2.             finishKeyEvent(event, sendDone, true);  

3.             return;  

4.         }  

解析:mViewPhoneWindow.DecorView对象,DecorViewPhoneWindow的子类,继承于FramwLayout,其实这个DecorView就是我们看到的activity上对应的界面,它里面有一个title对应状态栏,还有一个contentview,对应activity里面的setContentView方法。DecorView是定义在PhoneWindow里面,我们看这个内部类的dispatchKeyEvent对应的源码:

 public DecorView(Context context, int featureId) {
            super(context);
            mFeatureId = featureId;
        }


        public void setBackgroundFallback(int resId) {
            mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
            setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
        }


        @Override
        public void onDraw(Canvas c) {
            super.onDraw(c);
            mBackgroundFallback.draw(mContentRoot, c, mContentParent);
        }


        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            final int keyCode = event.getKeyCode();
            final int action = event.getAction();
            final boolean isDown = action == KeyEvent.ACTION_DOWN;


            if (isDown && (event.getRepeatCount() == 0)) {
                // First handle chording of panel key: if a panel key is held
                // but not released, try to execute a shortcut in it.
                if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
                    boolean handled = dispatchKeyShortcutEvent(event);
                    if (handled) {
                        return true;
                    }
                }


                // If a panel is open, perform a shortcut on it without the
                // chorded panel key
                if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
                    if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
                        return true;
                    }
                }
            }

   //activity一次处理dispatchKeyEvent的机会,比如按下菜单键,没消费的话最后还是调用getWindow.superDispatchKeyEvent回调了DecorViewsuper.dispatch方法中去  了,也就是ViewGroupdispatch方法
if (!isDestroyed()) {
final Callback cb = getCallback();
     final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event);
     super.dispatchKeyEvent(event);
      if (handled) {
                    return true;
      }
   return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
                    : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
        }

}

源码注释的很清楚,首先处理系统快捷键,然后调用Window.callback的dispatchKeyEvent()(cb.dispatchKeyEvent(event),cb是一个window.callback的接口实现,这里就行接口回调,实现window.Callback的主要是activity),所以就走到了Activity里面的dispatchKeyEvent方法里面去了,下面我们看看Activity里面改方法做了啥:

    public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();

        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
            return true;
        }

        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    }

首先是win.superDispatchKeyEvent(event),PhoneWindow对应的源码是:

    @Override
    public boolean superDispatchKeyEvent(KeyEvent event) {
        return mDecor.superDispatchKeyEvent(event);
    }

 

这里还是调用的mDecor.superDispatchKeyEvent(event)对应于DecorView的源码是:

       public boolean superDispatchKeyEvent(KeyEvent event) {
            // Give priority to closing action modes if applicable.
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                final int action = event.getAction();
                // Back cancels action modes first.
                if (mActionMode != null) {
                    if (action == KeyEvent.ACTION_UP) {
                        mActionMode.finish();
                    }
                    return true;
                }
            }


            return super.dispatchKeyEvent(event);
        }

走到这里,我们发现它调用了super.dispatchKeyEvent(event),也就是FrameLayout,我追踪到源码并无重写,故方法会走到ViewGroup的dispatchKeyEvent中去,这个是我们日常接触的最多的了。至于ViewGroup的DispatchKeyEvent如果走,我们后面再说。我们回到mView.dispatchKeyEvent中去,我们回忆一下步骤的三步:a.处理系统快捷键dispatchKeyShortcutEvent(event); b.a没有消费,则final  cb.dispatchKeyEvent(event),此时进入actiivty的dispatchKeyevent中去处理,首先处理menu操作,如果没有消费,就调用phonwindow的superDispatchKeyEevent,最后走到的都是ViewGroup里面去递归, 如果我们看到的界面上的那些个view没有消费此事件,那么在在       return event.dispatch(this, decor != null

                ? decor.getKeyDispatcherState() : null, this);

这段代码是actiivty里面dispatchKeyEvent最后的返回,传的是actiivty自己的引用,跟踪之后发现调用的是KeyEvent的OnkeyDown事件,也就是activity里面的onkeyDown.


综上所述,activity里面的onkeydown是后于view里面的Onkeydown调用的。如果view里面的onkeydown消费了此事件,那么activity的onKeyDown是走不到的。


最后来整理一下思路,理一下流程:

1.ViewRoot里面的InputHandler的handleKey,然后再是ViewRoot的dispatchKey,然后再是deliverKeyEvent,如果View系统有输入法则被输入法窗口拦截InputMethodManager对象的dispatchKeyEvent 。拦截之前有一次DecorView对象mView的dispatchKeyEventPreIme(event)的操作。然后如果没有拦截则RootViewImpl的deliverKeyEventPostIme方法。

源码对应于ViewRootImpl中:

1. private void deliverKeyEvent(QueuedInputEvent q) {  

2.     final KeyEvent event = (KeyEvent)q.mEvent;  s

3.     if (mInputEventConsistencyVerifier != null) {  

4.         mInputEventConsistencyVerifier.onKeyEvent(event, 0);  

5.     }  

6.   

7.     if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {  

8.         // If there is no view, then the event will not be handled.  

9.         if (mView == null || !mAdded) {  

10.             finishInputEvent(q, false);  

11.             return;  

12.         }  

13.   

14.         if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);  

15.   

16.         // Perform predispatching before the IME.  

17.         if (mView.dispatchKeyEventPreIme(event)) {  

18.             finishInputEvent(q, true);  

19.             return;  

20.         }  

21.   

22.         // Dispatch to the IME before propagating down the view hierarchy.  

23.         // The IME will eventually call back into handleImeFinishedEvent.  

24.         if (mLastWasImTarget) {  

25.             InputMethodManager imm = InputMethodManager.peekInstance();  

26.             if (imm != null) {  

27.                 final int seq = event.getSequenceNumber();  

28.                 if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="  

29.                         + seq + " event=" + event);  

30.                 imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);  

31.                 return;  

32.             }  

33.         }  

34.     }  

35.   

36.     // Not dispatching to IME, continue with post IME actions.  

37.     deliverKeyEventPostIme(q);  

38. }  

2.在ViewRootImpl的deliverKeyEventPostIme(q)方法中调用

1.    // Deliver the key to the view hierarchy.  

2.     if (mView.dispatchKeyEvent(event)) {  

3.         finishInputEvent(q, true);  

4.         return;  

5.     }   

3.mView就是DecorView对象的dispatchKeyEvent操作做了三步,一个是处理快捷键,二个是调用Window.Callback对象的dispatchKeyEvent

最后如果上面两步都没消费就调用phonewindow的onKeyDown,onKeyUp事件。其中第二步是actiivty的dispatchKeyEvent为入口,里面是则会给增加了一个菜单按钮的拦截,然后就又调用Window对象的superDispatchKeyEvent方法,其实就是PhoneWindow的方法拦截,这个方法最后还是调用的mDecorView.superDispatchKeyEvent方法,这个方法拦截了back按键事件之后又把事件转给了DecorView的super.dispatchKeyEvent,最后走到了viewTree里面去了。

 

0 0
原创粉丝点击