Android键盘系统

   Android中的键盘系统的生命周期从系统的启动开始到系统的关闭一直存在着,因为,在这整个过程中我们希望按下的没一个按键都会有相应的事件发生。通过调研发现,Android键盘系统贯穿着Android框架的最底层——Linux内核(驱动)到上层JAVA架构层——SystemServer。

与android键盘系统关系紧密的几个文件从下层往上层依次为:Goldfish_event.c(处于Linux内核)、EventHub.cpp(硬件抽象层)、com_android_server_KeyInputQueue.cpp(JNI本地方法)KeyInputQueue.java、WindowManagerService.java(java框架层)。发现一个很有意思的问题,Android并没有做一个纯的键盘系统,而是将键盘系统的实现放在了事件处理模块之中。这从这几个关键文件的文件名我们不难看出。

 

调用流程分析:

在WindowManagerService.java中启动了窗口管理,其中有一个语句如下:
mQueue = new KeyQ();
追踪到 KeyQ 可知:
private class KeyQ extends KeyInputQueue
(注:在下面的描述中,父类是指KeyInputQueue,子类是指KeyQ)
继续追踪KeyInputQueue
我们会发现,这是一个相当庞大的类,在这个类中创建了一个线程:
Thread mThread = new Thread("InputDeviceReader")

由构造方法:
public Thread(String threadName) {
if (threadName == null) {
throw new NullPointerException();
}
create(null, null, threadName, 0);
}

我们可以知道,Thread mThread = new Thread("InputDeviceReader")创建了一个名为InputDeviceReader的线程。
但这个线程是怎么创建起来的呢?因为我们看到,线程对象是在父类中new的。从父类的源码中,我们可以看到有一个构造方法,在这个构造方法中, 调用了mThred对象的方法,即:mThread.start();
我们来看看这个构造方法的代码:
KeyInputQueue(Context context, HapticFeedbackCallback hapticFeedbackCallback) {
if (MEASURE_LATENCY) {
lt = new LatencyTimer(100, 1000);
}

BAD_TOUCH_HACK = context.getResources().getBoolean(
com.android.internal.R.bool.config_filterTouchEvents);
 
mHapticFeedbackCallback = hapticFeedbackCallback;
 
readExcludedDevices();
 
PowerManager pm = (PowerManager)context.getSystemService(
Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"KeyInputQueue");
mWakeLock.setReferenceCounted(false);

mFirst = new QueuedEvent();
mLast = new QueuedEvent();
mFirst.next = mLast;
mLast.prev = mFirst;

mThread.start();
}


在子类KeyQ的构造方法中,调用了父类的这个构造方法: 
private class KeyQ extends KeyInputQueue
implements KeyInputQueue.FilterCallback {
PowerManager.WakeLock mHoldingScreen;
//下面是子类KeyQ的构造方法
KeyQ() {
super(mContext, WindowManagerService.this);
PowerManager pm = 
(PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
mHoldingScreen = 
pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
"KEEP_SCREEN_ON_FLAG");
mHoldingScreen.setReferenceCounted(false);
}
自然的,就创建了一个父类中的线程mThread。因此,在new一个子类对象的时候:
mQueue = new KeyQ();就调用了父类中的这个线程。
 







在这个线程中,调用了本地方法readEvent;
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
…………………..

 
RawInputEvent ev = new RawInputEvent();
while (true) {
try {
InputDevice di;
//这是一个本地方法
readEvent(ev);
…………………..

}
}
因些我们追踪到该JNI调用:com_android_server_KeyInputQueue.cpp
在该Native方法readEvent中,调用了EeventHub.cpp。
…………………..
sp<EventHub> hub = gHub; //sp是一个类,功能是smartpointer
…………………..
bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,&flags, &value, &when);
…………………..

因此我们进入EeventHub.cpp中查看方法:getEvevt

bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
int32_t* outValue, nsecs_t* outWhen)
接下来,getEvevt中调用了openPlatformInput(),openPlatformInput()中调用了scan_dir(),scan_dir()中调用了open_device。
我们注意到,函数的调用以及进入到EeventHub.cpp,这属于硬件抽象层,它可以直接对Linux内核驱动进行调用。


我们再回到线程mThread上来,在
mQueue = new KeyQ();
之后,接下来调用了
send = preprocessEvent(di, ev);
这里,实际上是调用了mQueue中的 preprocessEvent。
这是因为,子类KeyQ在继承KeyInputQueue时,重写了方法:preprocessEvent
@Override
boolean preprocessEvent(InputDevice device, RawInputEvent event) 
{
…………………..

}
接下来:与Key有关的代码有:
// Is it a key event?
if (type == RawInputEvent.EV_KEY &&
(classes&RawInputEvent.CLASS_KEYBOARD) != 0 &&
(scancode < RawInputEvent.BTN_FIRST ||
scancode > RawInputEvent.BTN_LAST)) {
boolean down;
if (ev.value != 0) {
down = true;
di.mKeyDownTime = curTime;
} else {
down = false;
}
int keycode = rotateKeyCodeLocked(ev.keycode);
addLocked(di, curTimeNano, ev.flags,
RawInputEvent.CLASS_KEYBOARD,
newKeyEvent(di, di.mKeyDownTime, curTime, down,
keycode, 0, scancode,
((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0)
? KeyEvent.FLAG_WOKE_HERE : 0)); 
} 

这mThread之后创建了另外一个线程InputDispatcherThread
…………………..
mQueue = new KeyQ();

mInputThread = new InputDispatcherThread();

PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
thr.start();
…………………..

在InputDispatcherThread中,有关于键盘的一个处理:
实际上这个线程是从 KeyQ 的事件队列中读取按键事件。
…………………..
switch (ev.classType) {
case RawInputEvent.CLASS_KEYBOARD:
KeyEvent ke = (KeyEvent)ev.event;
if (ke.isDown()) {
lastKey = ke;
downTime = curTime;
keyRepeatCount = 0;
lastKeyTime = curTime;
nextKeyTime = lastKeyTime
+ ViewConfiguration.getLongPressTimeout();
if (DEBUG_INPUT) Log.v(
TAG, "Received key down: first repeat @ "
+ nextKeyTime);
} else {
lastKey = null;
downTime = 0;
// Arbitrary long timeout.
lastKeyTime = curTime;
nextKeyTime = curTime + LONG_WAIT;
if (DEBUG_INPUT) Log.v(
TAG, "Received key up: ignore repeat @ "
+ nextKeyTime);
}
dispatchKey((KeyEvent)ev.event, 0, 0);
mQueue.recycleEvent(ev);
break;
…………………..

通过往着几个文件中插桩,从模拟器的启动日志,我们可以更直观的开到整个Android键盘系统的建立:
…………………..
I/SystemServer( 57): Window Manager
I/WindowManager( 57): New the thread : mQueue = new Key
I/KeyInputQueue( 57): Enter into the constraction of the Base Class KeyInputQueue
I/KeyInputQueue( 57): Start the mThread???????????????????
I/WindowManager( 57): After mQueue = new Key
I/WindowManager( 57): New the thread of mInputThread
I/WindowManager( 57): After mInputThread
I/WindowManager( 57): New the thread of mInputThread
I/WindowManager( 57): Begin the thread of PolicyThread thr
I/KeyInputQueue( 57): Start the methods run of the mThread
I/KeyInputQueue( 57): Before the readEvent(ev),this is a native method
I/Input ( 57): C::::::::::::4::::::::::::::Entering into the JNI method-readEvent
I/EventHub( 57): C::::::::EventHub.cpp::::::::::Entering into the EventHub::getEvent
I/EventHub( 57): C::::::::EventHub.cpp::::::::::Entering into the EventHub::openPlatformInput
I/EventHub( 57): C::::::::EventHub.cpp::::::::::Before scan_dir(device_path) 
I/EventHub( 57): C::::::::EventHub.cpp::::::::::Entering into the scan_dir(device_path) 
I/EventHub( 57): C::::::::EventHub.cpp::::::::::Entering into the open_device(device_path) 
I/EventHub( 57): Opening device: /dev/input/event0
I/EventHub( 57): Opened device: /dev/input/event0 (0 failures)
I/EventHub( 57): add device 1: /dev/input/event0
I/EventHub( 57): bus: 0000
I/EventHub( 57): vendor 0000
I/EventHub( 57): product 0000
I/EventHub( 57): version 0000
I/EventHub( 57): name: "qwerty2"
I/EventHub( 57): location: ""
I/EventHub( 57): id: ""
I/EventHub( 57): version: 1.0.0
I/EventHub( 57): New keyboard: publicID=65536 device->id=0x10000 devname='qwerty2' propName='hw.keyboards.65536.devname' keylayout='/system/usr/keylayout/qwerty.kl'
I/EventHub( 57): New device: path=/dev/input/event0 name=qwerty2 id=0x10000 (of 0x1) index=1 fd=49 classes=0x2f
I/EventHub( 57): C::::::::EventHub.cpp::::::::::Entering into the open_device(device_path) 
I/EventHub( 57): Opening device: /dev/input/mouse0
I/EventHub( 57): Opened device: /dev/input/mouse0 (0 failures)
E/EventHub( 57): could not get driver version for /dev/input/mouse0, Not a typewriter
I/EventHub( 57): C::::::::EventHub.cpp::::::::::Entering into the open_device(device_path) 
I/EventHub( 57): Opening device: /dev/input/mice
I/EventHub( 57): Opened device: /dev/input/mice (0 failures)
E/EventHub( 57): could not get driver version for /dev/input/mice, Not a typewriter
I/EventHub( 57): C::::::::EventHub.cpp:::::::::Enter into the while(1) ot the EventHub::getEvent
I/KeyInputQueue( 57): After the readEvent(ev)
I/KeyInputQueue( 57): Input event: dev=0x0 type=0x10000000 scancode=0 keycode=0 value=0
I/KeyInputQueue( 57): Device added: id=0x0, name=qwerty2, classes=2f
I/KeyInputQueue( 57): X: unknown values
I/KeyInputQueue( 57): Y: unknown values
I/KeyInputQueue( 57): Pressure: unknown values
I/KeyInputQueue( 57): Size: unknown values
I/KeyInputQueue( 57): No virtual keys found
I/KeyInputQueue( 57): Before the readEvent(ev),this is a native method
I/Input ( 57): C::::::::::::4::::::::::::::Entering into the JNI method-readEvent
I/EventHub( 57): C::::::::EventHub.cpp::::::::::Enter into the EventHub::getEvent
I/EventHub( 57): C::::::::EventHub.cpp:::::::::Enter into the while(1) of the EventHub::getEvent