我们先来写个测试应用,主要文件如下:
MainActivity.java
package com.test.keyevent;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.d("KEYEVENT", "MainActivity:onKeyDown");
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
Log.d("KEYEVENT", "MainActivity:onKeyUp");
return super.onKeyUp(keyCode, event);
}
@Override
public void onUserInteraction() {
Log.d("KEYEVENT", "MainActivity:onUserInteraction");
super.onUserInteraction();
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.d("KEYEVENT", "MainActivity:dispatchKeyEvent");
return super.dispatchKeyEvent(event);
}
}
2. MyFrameLayout.java
package com.test.keyevent;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.FrameLayout;
public class MyFrameLayout extends FrameLayout {
public MyFrameLayout(Context context) {
super(context, null);
}
public MyFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public MyFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.d("KEYEVENT", "MyFrameLayout:dispatchKeyEvent");
return super.dispatchKeyEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
Log.d("KEYEVENT", "MyFrameLayout:onKeyDown");
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
Log.d("KEYEVENT", "MyFrameLayout:onKeyUp");
return super.onKeyUp(keyCode, event);
}
}
3. MyEditText.java
package com.test.keyevent;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.EditText;
public class MyEditText extends EditText {
public MyEditText(Context context) {
super(context, null);
// TODO Auto-generated constructor stub
}
public MyEditText(Context context, AttributeSet attrs) {
super(context, attrs, 0);
// TODO Auto-generated constructor stub
}
public MyEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.d("KEYEVENT", "MyEditText:onKeyDown");
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
Log.d("KEYEVENT", "MyEditText:onKeyUp");
return super.onKeyUp(keyCode, event);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
Log.d("KEYEVENT", "MyEditText:dispatchKeyEvent");
return super.dispatchKeyEvent(event);
}
}
4. activity_main.xml
<com.test.keyevent.MyFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<com.test.keyevent.MyEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@android:color/white"
android:textColor="@android:color/black"
android:hint="请输入..." />
</com.test.keyevent.MyFrameLayout>
安装并打开应用。
在adb shell中,输入input keyevent 29 往系统注入一个KeyEvent(29是a的键值,可以在系统源码KeyEvent.java中查到),我们得到如下日志:
03-02 20:18:48.991: D/KEYEVENT(5840): MainActivity:dispatchKeyEvent
03-02 20:18:48.991: D/KEYEVENT(5840): MainActivity:onUserInteraction
03-02 20:18:48.991: D/KEYEVENT(5840): MyFrameLayout:dispatchKeyEvent
03-02 20:18:48.991: D/KEYEVENT(5840): MyEditText:dispatchKeyEvent
03-02 20:18:48.992: D/KEYEVENT(5840): MyEditText:onKeyDown
03-02 20:18:49.040: D/KEYEVENT(5840): MainActivity:dispatchKeyEvent
03-02 20:18:49.040: D/KEYEVENT(5840): MainActivity:onUserInteraction
03-02 20:18:49.040: D/KEYEVENT(5840): MyFrameLayout:dispatchKeyEvent
03-02 20:18:49.040: D/KEYEVENT(5840): MyEditText:dispatchKeyEvent
03-02 20:18:49.040: D/KEYEVENT(5840): MyEditText:onKeyUp
03-02 20:18:49.040: D/KEYEVENT(5840): MainActivity:onKeyUp
下面我们一步一步分析。
1. MainActivity:dispatchKeyEvent
首先被调用的是MainActivity的dispatchKeyEvent的函数。KeyEvent是如何传递到Acitivity中的,这是比较复杂的,可以参看http://blog.youkuaiyun.com/luoshengyang/article/details/6882903这篇博文。但要注意,这篇文章是更具android2.3来写的,对于我们常见的4.2以上的系统,经过我分析比较,是有一些不同的。
4.2系统中,native层InputManager不是在windowManagerService中创建的,而是在SystemServer中创建了个InputManagerService,并在里面创建初始化了InputManager。InputManager的启动也是由SystemServer来启动的。并且InputReaderThread的loopOnce()支持一次读取多条event,优化了性能。还有很多其他的不同的地方,但是大体逻辑上还是一样的。
这块好应用开发关系不太大,就不细说了。最终,Activity的ViewRootImpl的deliverKeyEventPostIme方法中会调用 DecorView的dispatchKeyEvent方法,而DecorView的dispatchKeyEvent方法,可以看下源码,它回调了Activity的 dispatchKeyEvent方法,于是,我们看到了这行日志。
2.MainActivity:onUserInteraction
我们看Activity的dispatchKeyEvent方法,
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
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);
}
里面调用了onUserInteraction方法。我们可以覆写这个方法,这样就可以在KeyEvent被派发之前,做一些操作,默认是什么都不做。
3. MyFrameLayout:dispatchKeyEvent
从日志看出,事件从Activity又回到了我们的View中。为什么要从Activity中走一下,这样给Activity一个机会,可以控制KeyEvent的派发。
如何回去的,我们接着看Acitivity.dispatchKeyEvent方法
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
win就是Activity的mWindow对象,是一个PhoneWindow。我们看PhoneWindow的superDispatchKeyEvent(KeyEvent event)方法:
public boolean superDispatchKeyEvent(KeyEvent event) {
return mDecor.superDispatchKeyEvent(event);
}
mDecor就是我们熟悉的DecorView对象啦,看它的superDispatchKeyEvent:
public boolean superDispatchKeyEvent(KeyEvent event) {
if (super.dispatchKeyEvent(event)) {
return true;
}
......
}
因为DecorView是FrameLayout子类,FrameLayout是ViewGroup的子类,所以super.dispatchKeyEvent(event)会调用到ViewGroup的dispatchKeyEvent方法:
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
// 如果自己有焦点,则调用父类View的dispatchKeyEvent
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
// 子视图有焦点,则调用子视图
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
焦点在MyEditText上,而MyEditText包含于MyFrameLayout中,所以最终mFocused.dispatchKeyEvent(event)会调用到MyFrameLayout.dispatchKeyEvent方法。
4. MyEditText:dispatchKeyEvent
MyFrameLayout也是FrameLayout子类,所以MyFrameLayout.dispatchKeyEvent和上面一样,会调用ViewGroup的dispatchKeyEvent。这次mFocused就是MyEditText,调用它的dispatchKeyEvent方法。
5.MyEditText:onKeyDown
MyEditText是View子类,会调用View的dispatchKeyEvent方法:
public boolean dispatchKeyEvent(KeyEvent event) {
......
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
......
return false;
}
View.dispatchKeyEvent会调用KeyEvent的dispatch方法:
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
......
boolean res = receiver.onKeyDown(mKeyCode, this);
......
}
}
receiver就是之前的View,也就是MyEditText,也就回调了MyEditText的onKeyDown方法。
至此,ACTION_DOWN的KeyEvent分析完毕。
ACTION_UP的KeyEvent和Down的基本一样,只是应为View的onKeyUp方法返回了false,所以最终会调用到Acitivity的onKeyUp方法,所以才会有最后一条日志的输出。