KeyEvent按键点击事件处理流程

  参考 Android 8.0 源码
  是用来报告按键(这里的按键指的是返回键等硬件上支持的按键)事件的对象。每个按键都由一系列的关键事件来描述。以ACTION_DOWN开始,最后一个关键事件是ACTION_UP。

涉及到的源码

/framework/base/core/java/android/view/InputEvent.java
/framework/base/core/java/android/view/KeyEvent.java
/framework/base/core/java/android/view/View.java
/framework/base/java/android/internal/policy/DecorView.java
public abstract class Window {
    public interface Callback {
        //---;
        public boolean dispatchKeyEvent(KeyEvent event);
        public boolean dispatchTouchEvent(MotionEvent event);
        public void onWindowFocusChanged(boolean hasFocus);
        public void onAttachedToWindow();
        public void onDetachedFromWindow();
        //---;
    }
}
public abstract class InputEvent implements Parcelable {

}
public class KeyEvent extends InputEvent implements Parcelable {
    //按键按下时的状态值;
    public static final int ACTION_DOWN = 0;
    //按键抬起时的状态值;
    public static final int ACTION_UP = 1;
    
    public interface Callback {
        boolean onKeyDown(int keyCode, KeyEvent event);
        boolean onKeyLongPress(int keyCode, KeyEvent event);
        boolean onKeyUp(int keyCode, KeyEvent event);
        boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
    }
}
public class View implements Drawable.Callback, 
    KeyEvent.Callback, 
    AccessibilityEventSource {

}
public class DecorView extends FrameLayout 
    implements RootViewSurfaceTaker,
    WindowCallbacks {

}

基本介绍

  在一个Activity界面中点击了某个按键,可能是ACTION_DOWN或者ACTION_UP,这两个KeyEvent事件进行分发处理,分发流程都一样,区别就是最后是交给Activity或者View来处理(使用onKeyDown()或onKeyUp()方法来处理)。需要注意的是,KeyEvent向下分发事件在其他地方不会被消费掉,只会在单个View或者Activity中被消费。

下面是KeyEvent在应用层源码中的跳转过程:

1 . ViewRootImpl是所有View的顶层容器,所以从ViewRootImpl这里开始。ViewRootImpl的dispatchInputEvent()方法向消息队列发出MSG_DISPATCH_INPUT_EVENT消息,Handler机制处理消息并调用enqueueInputEvent()方法,将QueuedInputEvent插入到未处理的KeyEvent队列的尾部。

public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
    SomeArgs args = SomeArgs.obtain();
    args.arg1 = event;
    args.arg2 = receiver;
    Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
    msg.setAsynchronous(true);
    mHandler.sendMessage(msg);
}
final class ViewRootHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        case MSG_DISPATCH_INPUT_EVENT: {
            SomeArgs args = (SomeArgs)msg.obj;
            InputEvent event = (InputEvent)args.arg1;
            InputEventReceiver receiver = (InputEventReceiver)args.arg2;
            enqueueInputEvent(event, receiver, 0, true);
            args.recycle();
        } break;
    }
}

KeyEvent插入未处理事件队列后,接下来有两种处理方式:一种是使用doProcessInputEvents()方法直接处理;另一种是使用scheduleProcessInputEvents()方法向Handler发送异步消息MSG_PROCESS_INPUT_EVENTS来处理,最终也是调用doProcessInputEvents()方法。

void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
    
    if (processImmediately) {
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}

在该方法中遍历未处理事件队列逐个处理。

void doProcessInputEvents() {
    
    deliverInputEvent(q);
}

在该方法中,出现了InputStage对象,这个对象是在ViewRootImpl类中的setView()方法中初始化的。
  InputStage是ViewRootImpl的内部类, 其中还有几个内部类都是继承自InputStage:

final class SyntheticInputStage extends InputStage 

final class ViewPostImeInputStage extends InputStage

final class NativePostImeInputStage extends AsyncInputStage implements InputQueue.FinishedInputEventCallback

final class EarlyPostImeInputStage extends InputStage

final class ImeInputStage extends AsyncInputStage implements InputMethodManager.FinishedInputEventCallback

final class ViewPreImeInputStage extends InputStage

final class NativePreImeInputStage extends AsyncInputStage implements InputQueue.FinishedInputEventCallback

  InputStage对象实际上是个管道,也就是责任链模式,事件在前一个InputStage未处理完会继续向下传递。KeyEvent使用的是ViewPostImeInputStage对象。InputStage的deliver()方法会调用onProcess()方法,这个是InputStage子类中具体处理KeyEvent的方法。

private void deliverInputEvent(QueuedInputEvent q) {
    InputStage stage;
    
    if (stage != null) {
        stage.deliver(q);
    } else {
        finishInputEvent(q);
    }
}
final class ViewPostImeInputStage extends InputStage {
    @Override
    protected int onProcess(QueuedInputEvent q) {
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                return processPointerEvent(q);
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                return processTrackballEvent(q);
            } else {
                return processGenericMotionEvent(q);
            }
        }
    }
}

接下来调用processKeyEvent()方法。

private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent)q.mEvent;
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
    //---;
}

  mView是View对象,也就是从这里将事件分发到View类中了。

在View和Activity中的分发处理流程

1 . 我们分析上面代码最后是调用mView.dispatchKeyEvent(event)方法,这个mView是在setView()方法中赋值的,也就是DecorView对象。也就是DecorView的dispatchKeyEvent()方法来分发,而DecorView会去调用Activity的dispatchKeyEvent()方法,由Activity继续分发。

DecorView.java中的方法

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (!mWindow.isDestroyed()) {
        //Activity中实现了Window.Callback接口,所以会调用Activity中的dispatchKeyEvent()方法;
        final Window.Callback cb = mWindow.getCallback();
        final boolean handled = cb != null && mFeatureId < 0 ?cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event);
        if (handled) {
            return true;
        }
    }

    return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
            : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}

2 . Activity会先获取PhoneWindow对象,然后调用PhoneWindow的superDispatchKeyEvent()方法,PhoneWindow然后调用DecorView的superDispatchKeyEvent(),而DecorView则调用了super.dispatchKeyEvent()方法将事件交给父类进行分发,DecorView继承自FrameLayout,但是FrameLayout没有实现dispatchKeyEvent()方法,所以实际上是交给了ViewGroup的dispatchKeyEvent()方法来分发。

Activity.java中的方法

public boolean dispatchKeyEvent(KeyEvent event) {
    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);
}

PhoneWindow.java中的方法

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

DecorView.java中的方法

public boolean superDispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        final int action = event.getAction();
        if (mPrimaryActionMode != null) {
            if (action == KeyEvent.ACTION_UP) {
                mPrimaryActionMode.finish();
            }
            return true;
        }
    }
    return super.dispatchKeyEvent(event);
}

监听返回键

  监听返回键有两个方法:onBackPressed()和onKeyDown()。查看Activity中的源码,可以发现onBackPressed()方法是在onKeyDown()方法中调用的。

实现点击返回键并不自动退出Activity

1 . 第一种方法实现:

@Override
public void onBackPressed() {
    super.onBackPressed();
    Log.e(TAG, "onBackPressed...");
}

  在onBackPressed()方法中,如果调用了super.onBackPressed()这一行代码,就会自动调用finish()方法来关闭当前Activity。如果我们想实现点击返回键并不自动关闭当前Activity,那么就把super.onBackPressed()这一行代码注释掉就可以了。然后就可以在onBackPressed()方法中去写代码来实现我们的业务逻辑了。

2 . 第二种方法实现:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    Log.e(TAG, "onKeyDown...:" + keyCode);
    if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
        return false;
    }else{
        return super.onKeyDown(keyCode, event);
    }
}

  在onKeyDown()方法中,我们判断keyCode是否等于KeyEvent.KEYCODE_BACK,如果是的话,就是点击了返回键,我们让其返回值为false则Activity不关闭,如果返回值为true则关闭Activity。

实现“再按一次返回键退出程序”的功能

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    Log.e(TAG, "onKeyDown...:" + keyCode);
    if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
        //如果两秒内两次点击返回键就退出程序,否则重新更新第一次点击返回键的时间;
        if(System.currentTimeMillis() - mExitTime > 2000){
            Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
            mExitTime = System.currentTimeMillis();
        }else{
            finish();
            System.exit(0);
        }
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
---------------------last line for now-------------------