文章將分析Android 的Input Event 子系統的來龍去脈。
Android 系統裡面有很多小工具,運行這些工具,我們對它們有一個感性的認識,進而閱讀和分析這些小工具原始碼,再順藤摸瓜,就可以把整個子系統的來龍去脈弄清楚。
1.運行toolbox的getevent 工具。
# getevent -help
getevent -help
Usage: getevent [-t] [-n] [-s switchmask] [-S] [-v [mask]] [-p] [-q] [-c count] [-r] [device]
-t: show time stamps
-n: don't print newlines
-s: print switch states for given bits
-S: print all switch states
-v: verbosity mask (errs=1, dev=2, name=4, info=8, vers=16, pos. events=32)
-p: show possible events (errs, dev, name, pos. events)
-q: quiet (clear verbosity mask)
-c: print given number of events then exit
-r: print rate events are received
# getevent -c 20
getevent -c 20
add device 1: /dev/input/event4
name: "sensor-input"
add device 2: /dev/input/event3
name: "88pm860x_hook"
add device 3: /dev/input/event2
name: "88pm860x_on"
add device 4: /dev/input/event1
name: "88pm860x-touch"
add device 5: /dev/input/event0
name: "pxa27x-keypad"
/dev/input/event0: 0001 0066 00000001
/dev/input/event0: 0000 0000 00000000
/dev/input/event0: 0001 0066 00000000
/dev/input/event0: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000c48
/dev/input/event1: 0003 0001 00000751
/dev/input/event1: 0001 014a 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000c67
/dev/input/event1: 0003 0001 000006f9
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000c9e
/dev/input/event1: 0003 0001 0000069e
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000cc4
/dev/input/event1: 0003 0001 00000620
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000ce8
/dev/input/event1: 0003 0001 000005ba
/dev/input/event1: 0000 0000 00000000
運行這個工具,然後按鍵或者滑動觸控螢幕,會看到程式會即時列印event。從上面的輸出來看,系統有5個input 子系統。它們分別是
add device 1: /dev/input/event4
name: "sensor-input"
#Sensor input 子系統
add device 2: /dev/input/event3
name: "88pm860x_hook"
#耳機Hook鍵子系統。可支援接電話掛電話的耳機上面有一個按鍵,對應的就是這個input 子系統。
add device 3: /dev/input/event2
name: "88pm860x_on"
#開機鍵 input 子系統
add device 4: /dev/input/event1
name: "88pm860x-touch"
#Touch Screen input 子系統
add device 5: /dev/input/event0
name: "pxa27x-keypad"
#按鍵子系統,包括Home/Menu/Back等按鍵。
可以嘗試多種event,實際感覺一下出來的log。
2.閱讀getevent的代碼。代碼為./core/toolbox/getevent.c
從代碼中,我們知道,程式在while(1)的一個死迴圈裡,不斷地在讀取 (select 操作)/dev/input 下面的檔案,檢查是否Kernel往裡面更新內容,如果有內容更新,就把它列印出來。並且從代碼中,我們還知道,任何一個event都有三種屬性,type,code,value.
while(1) {
pollres = poll(ufds, nfds, -1);
//printf("poll %d, returned %d/n", nfds, pollres);
if(ufds[0].revents & POLLIN) {
read_notify(device_path, ufds[0].fd, print_flags);
}
for(i = 1; i < nfds; i++) {
if(ufds[i].revents) {
if(ufds[i].revents & POLLIN) {
res = read(ufds[i].fd, &event, sizeof(event));
if(res < (int)sizeof(event)) {
fprintf(stderr, "could not get event/n");
return 1;
}
if(get_time) {
printf("%ld-%ld: ", event.time.tv_sec, event.time.tv_usec);
}
if(print_device)
printf("%s: ", device_names[i]);
printf("%04x %04x %08x", event.type, event.code, event.value);
if(sync_rate && event.type == 0 && event.code == 0) {
int64_t now = event.time.tv_sec * 1000000LL + event.time.tv_usec;
if(last_sync_time)
printf(" rate %lld", 1000000LL / (now - last_sync_time));
last_sync_time = now;
}
printf("%s", newline);
if(event_count && --event_count == 0)
return 0;
}
}
}
3.問題來了,Android Framework是否也是一樣的原理呢??猜測應該是一樣的才對,不然這個工具就沒有調試的價值了。
我們來閱讀和分析framework中input event的相關代碼。
我們從Kernel層往上看,先看看Framework中,直接操縱/dev/input裝置的代碼。
在.frameworks/base/libs/ui/EventHub.cpp 中,我們看到跟getevent工具類似的代碼。
bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
int32_t* outValue, nsecs_t* outWhen)
{
....
while(1) {
....
release_wake_lock(WAKE_LOCK_ID);
pollres = poll(mFDs, mFDCount, -1);
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
if (pollres <= 0) {
if (errno != EINTR) {
LOGW("select failed (errno=%d)/n", errno);
usleep(100000);
}
continue;
}
....
// mFDs[0] is used for inotify, so process regular events starting at mFDs[1]
for(i = 1; i < mFDCount; i++) {
if(mFDs[i].revents) {
LOGV("revents for %d = 0x%08x", i, mFDs[i].revents);
if(mFDs[i].revents & POLLIN) {
res = read(mFDs[i].fd, &iev, sizeof(iev));
if (res == sizeof(iev)) {
LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d",
mDevices[i]->path.string(),
....
}
4.那麼framework中那個模組再調用EventHub呢,接著往下查。
在framework目錄中,輸入下面的命令尋找
# find . -name "*.cpp" |grep -v EventHub | xargs grep EventHub
./base/services/jni/com_android_server_KeyInputQueue.cpp:#include <ui/EventHub.h>
./base/services/jni/com_android_server_KeyInputQueue.cpp:static sp<EventHub> gHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp: sp<EventHub> hub = gHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp: hub = new EventHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp: sp<EventHub> hub = gHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp: hub = new EventHub;
5.從尋找結果中得知,在jni檔案com_android_server_KeyInputQueue.cpp檔案中有對EventHub進行調用。
開啟並閱讀com_android_server_KeyInputQueue.cpp檔案得知,在下面的函數中調用了EventHub的getEvent函數
static jboolean
android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,
jobject event)
{
gLock.lock();
sp<EventHub> hub = gHub;
if (hub == NULL) {
hub = new EventHub;
gHub = hub;
}
gLock.unlock();
int32_t deviceId;
int32_t type;
int32_t scancode, keycode;
uint32_t flags;
int32_t value;
nsecs_t when;
bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,
&flags, &value, &when);
env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);
env->SetIntField(event, gInputOffsets.mType, (jint)type);
env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);
env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);
env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);
env->SetIntField(event, gInputOffsets.mValue, value);
env->SetLongField(event, gInputOffsets.mWhen,
(jlong)(nanoseconds_to_milliseconds(when)));
return res;
}
6.根據jni的調用規則,在本檔案中尋找對於的java函數。
static JNINativeMethod gInputMethods[] = {
/* name, signature, funcPtr */
{ "readEvent", "(Landroid/view/RawInputEvent;)Z",
(void*) android_server_KeyInputQueue_readEvent },
....
7. 接著順藤摸瓜,找到對應的java檔案,base/services/java/com/android/server/KeyInputQueue.java
private static native boolean readEvent(RawInputEvent outEvent);
在一個線程中會調用readEvent函數。
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
if (DEBUG) Slog.v(TAG, "InputDeviceReader.run()");
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
RawInputEvent ev = new RawInputEvent();
while (true) {
try {
InputDevice di;
// block, doesn't release the monitor
readEvent(ev);
boolean send = false;
boolean configChanged = false;
if (false) {
Slog.i(TAG, "Input event: dev=0x"
+ Integer.toHexString(ev.deviceId)
+ " type=0x" + Integer.toHexString(ev.type)
+ " scancode=" + ev.scancode
+ " keycode=" + ev.keycode
+ " value=" + ev.value);
}
8.那是誰啟動這個線程呢???尋找mThread變數,得知在KeyInputQueue的建構函式中會啟動這個線程。
KeyInputQueue(Context context, HapticFeedbackCallback hapticFeedbackCallback) {
if (MEASURE_LATENCY) {
lt = new LatencyTimer(100, 1000);
}
Resources r = context.getResources();
BAD_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterTouchEvents);
JUMPY_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterJumpyTouchEvents);
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;
mThread.start();
}
9.那這個KeyInputQueue是在哪裡被執行個體化呢?
而且查看KeyInputQueue類的聲明,得知它是一個abstract class.
public abstract class KeyInputQueue
{
.....
}
說明它肯定會被某個類繼承.接著尋找。
/frameworks$ find . -name "*.java" |grep -v KeyInputQueue | xargs grep KeyInputQueue
./policies/base/phone/com/android/internal/policy/impl/KeyguardViewMediator.java: * {@link com.android.server.KeyInputQueue}'s and {@link android.view.WindowManager}'s.
./base/services/java/com/android/server/PowerManagerService.java: && !"KeyInputQueue".equals(tag))) {
./base/services/java/com/android/server/WindowManagerService.java:import com.android.server.KeyInputQueue.QueuedEvent;
./base/services/java/com/android/server/WindowManagerService.java: implements Watchdog.Monitor, KeyInputQueue.HapticFeedbackCallback {
./base/services/java/com/android/server/WindowManagerService.java: return KeyInputQueue.getSwitchState(sw);
./base/services/java/com/android/server/WindowManagerService.java: return KeyInputQueue.getSwitchState(devid, sw);
./base/services/java/com/android/server/WindowManagerService.java: return KeyInputQueue.hasKeys(keycodes, keyExists);
./base/services/java/com/android/server/WindowManagerService.java: private class KeyQ extends KeyInputQueue
./base/services/java/com/android/server/WindowManagerService.java: implements KeyInputQueue.FilterCallback {
./base/services/java/com/android/server/InputDevice.java: // For use by KeyInputQueue for keeping track of the current touch
./base/services/java/com/android/server/InputDevice.java: if (KeyInputQueue.BAD_TOUCH_HACK) {
./base/services/java/com/android/server/InputDevice.java: Slog.i("KeyInputQueue", "Updating: " + currentMove);
./base/services/java/com/android/server/InputDevice.java: Slog.i("KeyInputQueue", "Updating: " + currentMove);
10.從上面的尋找結果得知,會在WindowManagerService.java中有一個KeyQ類繼承KeyInputQueue類,再在這個檔案中尋找KeyQ類在哪裡定義並執行個體化的,找到在其建構函式裡執行個體化的。
private WindowManagerService(Context context, PowerManagerService pm,
boolean haveInputMethods) {
if (MEASURE_LATENCY) {
lt = new LatencyTimer(100, 1000);
}
....
mQueue = new KeyQ();
mInputThread = new InputDispatcherThread();
PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
...
}
至此,基本上把Input event的Framework的流程全部走完了。WindowManagerService是屬於System server進程裡面起的一個Service.一開機就會運行,當然其建構函式一開機就能會運行。
至此,整個流程如下:
WindowManagerService
|
|
//
KeyQ
|
|
//
KeyInputQueue
|
|
//
EventHub
|
|
//
Kernel device (/dev/input)
後續的文章將介紹/dev/input在Kernel中的實現。
http://blog.csdn.net/learnrose/article/details/6236890#