在系統啟動過程中,會載入驅動程式,初始化硬體裝置,會進入bool EventHub::openPlatformInput(void)這個函數,該函數主要功能是掃描/dev/input該目錄,擷取輸入裝置。如何擷取呢?通過linux API res = scan_dir(device_path); 該函數叫
while((de = readdir(dir))) {
strcpy(filename, de->d_name);
open_device(devname);
}
不斷讀取目錄檔案,然後通過open_device()開啟裝置。具體開啟裝置函數是fd = open(deviceName, O_RDWR);以讀寫方式開啟,該函數會調用驅動裡file_operations裡的實現函數。
此時所有輸入裝置已經開啟。
在WindowManagerService服務類啟動並執行時候,在建構函式中會建立內部類KeyQ對象. 該類繼承之KeyInputQueue類。當然要進入該類的建構函式。在KeyInputQueue類的建構函式中會啟動Thread mThread=new Thread("InputDeviceReader")這個匿名內部類線程,有該線程讀驅動事件並把它放到訊息佇列中。具體實現是:
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
RawInputEvent ev = new RawInputEvent();
while (true) {
InputDevice di;
readEvent(ev);
}
}
該線程持續運行,迴圈通過readEvent(); 該函數是個native方法,具體實現為
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);
}
以上步驟獲得了事件QueuedEvent。
獲得事件後通過
private void addLocked(InputDevice device, long when, int flags,
int classType, Object event) {
boolean poke = mFirst.next == mLast;
QueuedEvent ev = obtainLocked(device, when, flags, classType, event);
QueuedEvent p = mLast.prev;
while (p != mFirst && ev.when < p.when) {
p = p.prev;
}
ev.next = p.next;
ev.prev = p;
p.next = ev;
ev.next.prev = ev;
ev.inQueue = true;
}
該函數加到訊息佇列中,該訊息佇列就是個雙向連表,頭和尾分別是 final QueuedEvent mFirst;
final QueuedEvent mLast; 該函數就是把新訊息插到尾的前面。
訊息佇列有了。還需要讀隊列。在WindowManagerService.java 類中同時又開了個內部線程
mInputThread = new InputDispatcherThread();
mInputThread.start();
該線程和剛才的寫隊列線程是並行的。該線程起來後,通過
public void run() {
while (true) {
try {
process();
} catch (Exception e) {
Log.e(TAG, "Exception in input dispatcher", e);
}
}
}
這個process()函數來讀訊息。具體實現是
private void process() {
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
while (true) {
QueuedEvent ev = mQueue.getEvent(
(int)((!configChanged && curTime < nextKeyTime)
? (nextKeyTime-curTime) : 0));
}
該getEvent()函數的具體實現是
QueuedEvent getEvent(long timeoutMS) {
long begin = SystemClock.uptimeMillis();
final long end = begin+timeoutMS;
long now = begin;
synchronized (mFirst) {
while (mFirst.next == mLast && end > now) {
QueuedEvent p = mFirst.next;
mFirst.next = p.next;
mFirst.next.prev = mFirst;
p.inQueue = false;
return p;
}
}
持續的讀訊息,如果沒訊息就阻塞,有訊息,就讀取訊息,所謂讀取訊息就得到引用,然後把該訊息從雙向連表中刪除。得到訊息後根據訊息輸入裝置類型把訊息發送到具體AP 中。如
switch (ev.classType) {
case RawInputEvent.CLASS_KEYBOARD:
dispatchKey((KeyEvent)ev.event, 0, 0);
mQueue.recycleEvent(ev);
break;
case RawInputEvent.CLASS_TOUCHSCREEN:
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
break;
case RawInputEvent.CLASS_TRACKBALL:
dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
break;
case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
configChanged = true;
break;
default:
mQueue.recycleEvent(ev);
break;
}
當然這其中涉及很多細節,有興趣可以看看。同時讀寫隊列的互斥機制也值得學習。