在Android系統(tǒng)中,鍵盤按鍵事件是由WindowManagerService服務(wù)來管理的,然后再以消息的形式來分發(fā)給應(yīng)用程序處理,不過和普通消息不一樣,它是由硬件中斷觸發(fā)的;在上一篇文章《Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析》中,我們分析了Android應(yīng)用程序的消息處理機(jī)制,本文將結(jié)合這種消息處理機(jī)制來詳細(xì)分析Android應(yīng)用程序是如何獲得鍵盤按鍵消息的。
在系統(tǒng)啟動的時候,SystemServer會啟動窗口管理服務(wù)WindowManagerService,WindowManagerService在啟動的時候就會通過系統(tǒng)輸入管理器InputManager來總負(fù)責(zé)監(jiān)控鍵盤消息。這些鍵盤消息一般都是分發(fā)給當(dāng)前激活的Activity窗口來處理的,因此,當(dāng)前激活的Activity窗口在創(chuàng)建的時候,會到WindowManagerService中去注冊一個接收鍵盤消息的通道,表明它要處理鍵盤消息,而當(dāng)InputManager監(jiān)控到有鍵盤消息時,就會分給給它處理。當(dāng)當(dāng)前激活的Activity窗口不再處于激活狀態(tài)時,它也會到WindowManagerService中去反注冊之前的鍵盤消息接收通道,這樣,InputManager就不會再把鍵盤消息分發(fā)給它來處理。
由于本文的內(nèi)容比較多,在接下面的章節(jié)中,我們將分為五個部分來詳細(xì)描述Android應(yīng)用程序獲得鍵盤按鍵消息的過程,每一個部分都是具體描述鍵盤消息處理過程中的一個過程。結(jié)合上面的鍵盤消息處理框架,這四個過程分別是InputManager的啟動過程、應(yīng)用程序注冊鍵盤消息接收通道的過程、InputManager分發(fā)鍵盤消息給應(yīng)用程序的過程以及應(yīng)用程序注銷鍵盤消息接收通道的過程。為了更好地理解Android應(yīng)用程序獲得鍵盤按鍵消息的整個過程,建議讀者首先閱讀Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析一文,理解了Android應(yīng)用程序的消息處理機(jī)制后,就能很好的把握本文的內(nèi)容。
1. InputManager的啟動過程分析
前面說過,Android系統(tǒng)的鍵盤事件是由InputManager來監(jiān)控的,而InputManager是由窗口管理服務(wù)WindowManagerService來啟動的。
從前面一篇文章Android系統(tǒng)進(jìn)程Zygote啟動過程的源代碼分析中,我們知道在Android系統(tǒng)中,Zygote進(jìn)程負(fù)責(zé)啟動系統(tǒng)服務(wù)進(jìn)程SystemServer,而系統(tǒng)服務(wù)進(jìn)程SystemServer負(fù)責(zé)啟動系統(tǒng)中的各種關(guān)鍵服務(wù),例如我們在前面兩篇文章Android應(yīng)用程序安裝過程源代碼分析和Android系統(tǒng)默認(rèn)Home應(yīng)用程序(Launcher)的啟動過程源代碼分析中提到的Package管理服務(wù)PackageManagerService和Activity管理服務(wù)ActivityManagerService。這里我們所討論的窗口管理服務(wù)WindowManagerService也是由SystemServer來啟動的,具體的啟動過程這里就不再詳述了,具體可以參考PackageManagerService和ActivityManagerService的啟動過程。
了解了WindowManagerService的啟動過程之后,我們就可以繼續(xù)分析InputManager的啟動過程了。我們先來看一下InputManager啟動過程的序列圖,然后根據(jù)這個序列圖來一步步分析它的啟動過程:
Step 1. WindowManagerService.main
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... public static WindowManagerService main(Context context, PowerManagerService pm, boolean haveInputMethods) { WMThread thr = new WMThread(context, pm, haveInputMethods); thr.start(); synchronized (thr) { while (thr.mService == null) { try { thr.wait(); } catch (InterruptedException e) { } } return thr.mService; } } ...... }它通過一個線程WMThread實(shí)例來執(zhí)行全局唯一的WindowManagerService實(shí)例的啟動操作。這里調(diào)用WMThread實(shí)例thr的start成員函數(shù)時,會進(jìn)入到WMThread實(shí)例thr的run函數(shù)中去。
Step 2. WMThread.run
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... static class WMThread extends Thread { ...... public void run() { ...... WindowManagerService s = new WindowManagerService(mContext, mPM, mHaveInputMethods); ...... } } ...... }這里執(zhí)行的主要操作就是創(chuàng)建一個WindowManagerService實(shí)例,這樣會調(diào)用到WindowManagerService構(gòu)造函數(shù)中去。
Step 3. WindowManagerService<init>
WindowManagerService類的構(gòu)造函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... final InputManager mInputManager; ...... private WindowManagerService(Context context, PowerManagerService pm, boolean haveInputMethods) { ...... mInputManager = new InputManager(context, this); ...... mInputManager.start(); ...... } ...... }這里我們只關(guān)心InputManager的創(chuàng)建過程,而忽略其它無關(guān)部分。首先是創(chuàng)建一個InputManager實(shí)例,然后再調(diào)用它的start成員函數(shù)來監(jiān)控鍵盤事件。在創(chuàng)建InputManager實(shí)例的過程中,會執(zhí)行一些初始化工作,因此,我們先進(jìn)入到InputManager類的構(gòu)造函數(shù)去看看,然后再回過頭來分析它的start成員函數(shù)。
Step 4. InputManager<init>@java
Java層的InputManager類的構(gòu)造函數(shù)定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager { ...... public InputManager(Context context, WindowManagerService windowManagerService) { this.mContext = context; this.mWindowManagerService = windowManagerService; this.mCallbacks = new Callbacks(); init(); } ...... }這里只是簡單地初始化InputManager類的一些成員變量,然后調(diào)用init函數(shù)進(jìn)一步執(zhí)行初始化操作。
Step 5. InputManager.init
這個函數(shù)定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager { ...... private void init() { Slog.i(TAG, "Initializing input manager"); nativeInit(mCallbacks); } ...... }函數(shù)init通過調(diào)用本地方法nativeInit來執(zhí)行C++層的相關(guān)初始化操作。
Step 6. InputManager.nativeInit
這個函數(shù)定義在frameworks/base/services/jni$ vi com_android_server_InputManager.cpp文件中:
static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz, jobject callbacks) { if (gNativeInputManager == NULL) { gNativeInputManager = new NativeInputManager(callbacks); } else { LOGE("Input manager already initialized."); jniThrowRuntimeException(env, "Input manager already initialized."); } }這個函數(shù)的作用是創(chuàng)建一個NativeInputManager實(shí)例,并保存在gNativeInputManager變量中。由于是第一次調(diào)用到這里,因此,gNativeInputManager為NULL,于是就會new一個NativeInputManager對象出來,這樣就會執(zhí)行NativeInputManager類的構(gòu)造函數(shù)來執(zhí)其它的初始化操作。
Step 7. NativeInputManager<init>
NativeInputManager類的構(gòu)造函數(shù)定義在frameworks/base/services/jni$ vi com_android_server_InputManager.cpp文件中:
NativeInputManager::NativeInputManager(jobject callbacksObj) : mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1), mMaxEventsPerSecond(-1), mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(ROTATION_0) { JNIEnv* env = jniEnv(); mCallbacksObj = env->NewGlobalRef(callbacksObj); sp<EventHub> eventHub = new EventHub(); mInputManager = new InputManager(eventHub, this, this); }這里只要是創(chuàng)建了一個EventHub實(shí)例,并且把這個EventHub作為參數(shù)來創(chuàng)建InputManager對象。注意,這里的InputManager類是定義在C++層的,和前面在Java層的InputManager不一樣,不過它們是對應(yīng)關(guān)系。EventHub類是真正執(zhí)行監(jiān)控鍵盤事件操作的地方,后面我們會進(jìn)一步分析到,現(xiàn)在我們主要關(guān)心InputManager實(shí)例的創(chuàng)建過程,它會InputManager類的構(gòu)造函數(shù)里面執(zhí)行一些初始化操作。
Step 8. InputManager<init>@C++
C++層的InputManager類的構(gòu)造函數(shù)定義在frameworks/base/libs/ui/InputManager.cpp 文件中:
InputManager::InputManager( const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& readerPolicy, const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { mDispatcher = new InputDispatcher(dispatcherPolicy); mReader = new InputReader(eventHub, readerPolicy, mDispatcher); initialize(); }這里主要是創(chuàng)建了一個InputDispatcher對象和一個InputReader對象,并且分別保存在成員變量mDispatcher和mReader中。InputDispatcher類是負(fù)責(zé)把鍵盤消息分發(fā)給當(dāng)前激活的Activity窗口的,而InputReader類則是通過EventHub類來實(shí)現(xiàn)讀取鍵盤事件的,后面我們會進(jìn)一步分析。創(chuàng)建了這兩個對象后,還要調(diào)用initialize函數(shù)來執(zhí)行其它的初始化操作。
Step 9. InputManager.initialize
這個函數(shù)定義在frameworks/base/libs/ui/InputManager.cpp 文件中:
void InputManager::initialize() { mReaderThread = new InputReaderThread(mReader); mDispatcherThread = new InputDispatcherThread(mDispatcher); }這個函數(shù)創(chuàng)建了一個InputReaderThread線程實(shí)例和一個InputDispatcherThread線程實(shí)例,并且分別保存在成員變量mReaderThread和mDispatcherThread中。這里的InputReader實(shí)列mReader就是通過這里的InputReaderThread線程實(shí)列mReaderThread來讀取鍵盤事件的,而InputDispatcher實(shí)例mDispatcher則是通過這里的InputDispatcherThread線程實(shí)例mDisptacherThread來分發(fā)鍵盤消息的。
至此,InputManager的初始化工作就完成了,在回到Step 3中繼續(xù)分析InputManager的進(jìn)一步啟動過程之前,我們先來作一個小結(jié),看看這個初始化過程都做什么事情:
A. 在Java層中的WindowManagerService中創(chuàng)建了一個InputManager對象,由它來負(fù)責(zé)管理Android應(yīng)用程序框架層的鍵盤消息處理;
B. 在C++層也相應(yīng)地創(chuàng)建一個InputManager本地對象來負(fù)責(zé)監(jiān)控鍵盤事件;
C. 在C++層中的InputManager對象中,分別創(chuàng)建了一個InputReader對象和一個InputDispatcher對象,前者負(fù)責(zé)讀取系統(tǒng)中的鍵盤消息,后者負(fù)責(zé)把鍵盤消息分發(fā)出去;
D. InputReader對象和一個InputDispatcher對象分別是通過InputReaderThread線程實(shí)例和InputDispatcherThread線程實(shí)例來實(shí)鍵盤消息的讀取和分發(fā)的。
有了這些對象之后,萬事就俱備了,回到Step 3中,調(diào)用InputManager類的start函數(shù)來執(zhí)行真正的啟動操作。
Step 10. InputManager.start
這個函數(shù)定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager { ...... public void start() { Slog.i(TAG, "Starting input manager"); nativeStart(); } ...... }這個函數(shù)通過調(diào)用本地方法nativeStart來執(zhí)行進(jìn)一步的啟動操作。
Step 11. InputManager.nativeStart
這個函數(shù)定義在frameworks/base/services/jni$ vi com_android_server_InputManager.cpp文件中:
static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) { if (checkInputManagerUnitialized(env)) { return; } status_t result = gNativeInputManager->getInputManager()->start(); if (result) { jniThrowRuntimeException(env, "Input manager could not be started."); } }這里的gNativeInputManager對象是在前面的Step 6中創(chuàng)建的,通過它的getInputManager函數(shù)可以返回C++層的InputManager對象,接著調(diào)用這個InputManager對象的start函數(shù)。
Step 12. InputManager.start
這個函數(shù)定義在frameworks/base/libs/ui/InputManager.cpp 文件中:
status_t InputManager::start() { status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); if (result) { LOGE("Could not start InputDispatcher thread due to error %d.", result); return result; } result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); if (result) { LOGE("Could not start InputReader thread due to error %d.", result); mDispatcherThread->requestExit(); return result; } return OK; }這個函數(shù)主要就是分別啟動一個InputDispatcherThread線程和一個InputReaderThread線程來讀取和分發(fā)鍵盤消息的了。這里的InputDispatcherThread線程對象mDispatcherThread和InputReaderThread線程對象是在前面的Step 9中創(chuàng)建的,調(diào)用了它們的run函數(shù)后,就會進(jìn)入到它們的threadLoop函數(shù)中去,只要threadLoop函數(shù)返回true,函數(shù)threadLoop就會一直被循環(huán)調(diào)用,于是這兩個線程就起到了不斷地讀取和分發(fā)鍵盤消息的作用。
我們先來分析InputDispatcherThread線程分發(fā)消息的過程,然后再回過頭來分析InputReaderThread線程讀取消息的過程。
Step 13. InputDispatcherThread.threadLoop
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
bool InputDispatcherThread::threadLoop() { mDispatcher->dispatchOnce(); return true; }這里的成員變量mDispatcher即為在前面Step 8中創(chuàng)建的InputDispatcher對象,調(diào)用它的dispatchOnce成員函數(shù)執(zhí)行一次鍵盤消息分發(fā)的操作。
Step 14. InputDispatcher.dispatchOnce
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::dispatchOnce() { nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout(); nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay(); nsecs_t nextWakeupTime = LONG_LONG_MAX; { // acquire lock AutoMutex _l(mLock); dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime); if (runCommandsLockedInterruptible()) { nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately } } // release lock // Wait for callback or timeout or wake. (make sure we round up, not down) nsecs_t currentTime = now(); int32_t timeoutMillis; if (nextWakeupTime > currentTime) { uint64_t timeout = uint64_t(nextWakeupTime - currentTime); timeout = (timeout + 999999LL) / 1000000LL; timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout); } else { timeoutMillis = 0; } mLooper->pollOnce(timeoutMillis); }這個函數(shù)很簡單,把鍵盤消息交給dispatchOnceInnerLocked函數(shù)來處理,這個過程我們在后面再詳細(xì)分析,然后調(diào)用mLooper->pollOnce函數(shù)等待下一次鍵盤事件的發(fā)生。這里的成員變量mLooper的類型為Looper,它定義在C++層中,具體可以參考前面Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析一文。
Step 15. Looper.pollOnce
這個函數(shù)定義在frameworks/base/libs/utils/Looper.cpp文件中,具體可以參考前面Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析一文,這里就不再詳述了。總的來說,就是在Looper類中,會創(chuàng)建一個管道,當(dāng)調(diào)用Looper類的pollOnce函數(shù)時,如果管道中沒有內(nèi)容可讀,那么當(dāng)前線程就會進(jìn)入到空閑等待狀態(tài);當(dāng)有鍵盤事件發(fā)生時,InputReader就會往這個管道中寫入新的內(nèi)容,這樣就會喚醒前面正在等待鍵盤事件發(fā)生的線程。
InputDispatcher類分發(fā)消息的過程就暫時分析到這里,后面會有更進(jìn)一步的分析,現(xiàn)在,我們回到Step 12中,接著分析InputReader類讀取鍵盤事件的過程。在調(diào)用了InputReaderThread線程類的run就函數(shù)后,同樣會進(jìn)入到InputReaderThread線程類的threadLoop函數(shù)中去。
Step 16. InputReaderThread.threadLoop
這個函數(shù)定義在frameworks/base/libs/ui/InputReader.cpp文件中:
bool InputReaderThread::threadLoop() { mReader->loopOnce(); return true; }這里的成員變量mReader即為在前面Step 8中創(chuàng)建的InputReader對象,調(diào)用它的loopOnce成員函數(shù)執(zhí)行一次鍵盤事件的讀取操作。
Step 17. InputReader.loopOnce
這個函數(shù)定義在frameworks/base/libs/ui/InputReader.cpp文件中:
void InputReader::loopOnce() { RawEvent rawEvent; mEventHub->getEvent(& rawEvent); #if DEBUG_RAW_EVENTS LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d", rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode, rawEvent.value); #endif process(& rawEvent); }這里通過成員函數(shù)mEventHub來負(fù)責(zé)鍵盤消息的讀取工作,如果當(dāng)前有鍵盤事件發(fā)生或者有鍵盤事件等待處理,通過mEventHub的getEvent函數(shù)就可以得到這個事件,然后交給process函數(shù)進(jìn)行處理,這個函數(shù)主要就是喚醒前面的InputDispatcherThread線程,通知它有新的鍵盤事件發(fā)生了,它需要進(jìn)行一次鍵盤消息的分發(fā)操作了,這個函數(shù)我們后面再進(jìn)一步詳細(xì)分析;如果沒有鍵盤事件發(fā)生或者沒有鍵盤事件等待處理,那么調(diào)用mEventHub的getEvent函數(shù)時就會進(jìn)入等待狀態(tài)。
Step 18. EventHub.getEvent
這個函數(shù)定義在frameworks/base/libs/ui/EventHub.cpp文件中:
bool EventHub::getEvent(RawEvent* outEvent) { outEvent->deviceId = 0; outEvent->type = 0; outEvent->scanCode = 0; outEvent->keyCode = 0; outEvent->flags = 0; outEvent->value = 0; outEvent->when = 0; // Note that we only allow one caller to getEvent(), so don't need // to do locking here... only when adding/removing devices. if (!mOpened) { mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR; mOpened = true; mNeedToSendFinishedDeviceScan = true; } for (;;) { // Report any devices that had last been added/removed. if (mClosingDevices != NULL) { device_t* device = mClosingDevices; LOGV("Reporting device closed: id=0x%x, name=%s\n", device->id, device->path.string()); mClosingDevices = device->next; if (device->id == mFirstKeyboardId) { outEvent->deviceId = 0; } else { outEvent->deviceId = device->id; } outEvent->type = DEVICE_REMOVED; outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); delete device; mNeedToSendFinishedDeviceScan = true; return true; } if (mOpeningDevices != NULL) { device_t* device = mOpeningDevices; LOGV("Reporting device opened: id=0x%x, name=%s\n", device->id, device->path.string()); mOpeningDevices = device->next; if (device->id == mFirstKeyboardId) { outEvent->deviceId = 0; } else { outEvent->deviceId = device->id; } outEvent->type = DEVICE_ADDED; outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); mNeedToSendFinishedDeviceScan = true; return true; } if (mNeedToSendFinishedDeviceScan) { mNeedToSendFinishedDeviceScan = false; outEvent->type = FINISHED_DEVICE_SCAN; outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); return true; } // Grab the next input event. for (;;) { // Consume buffered input events, if any. if (mInputBufferIndex < mInputBufferCount) { const struct input_event& iev = mInputBufferData[mInputBufferIndex++]; const device_t* device = mDevices[mInputDeviceIndex]; LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d", device->path.string(), (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value); if (device->id == mFirstKeyboardId) { outEvent->deviceId = 0; } else { outEvent->deviceId = device->id; } outEvent->type = iev.type; outEvent->scanCode = iev.code; if (iev.type == EV_KEY) { status_t err = device->layoutMap->map(iev.code, & outEvent->keyCode, & outEvent->flags); LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", iev.code, outEvent->keyCode, outEvent->flags, err); if (err != 0) { outEvent->keyCode = AKEYCODE_UNKNOWN; outEvent->flags = 0; } } else { outEvent->keyCode = iev.code; } outEvent->value = iev.value; // Use an event timestamp in the same timebase as // java.lang.System.nanoTime() and android.os.SystemClock.uptimeMillis() // as expected by the rest of the system. outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); return true; } // Finish reading all events from devices identified in previous poll(). // This code assumes that mInputDeviceIndex is initially 0 and that the // revents member of pollfd is initialized to 0 when the device is first added. // Since mFDs[0] is used for inotify, we process regular events starting at index 1. mInputDeviceIndex += 1; if (mInputDeviceIndex >= mFDCount) { break; } const struct pollfd& pfd = mFDs[mInputDeviceIndex]; if (pfd.revents & POLLIN) { int32_t readSize = read(pfd.fd, mInputBufferData, sizeof(struct input_event) * INPUT_BUFFER_SIZE); if (readSize < 0) { if (errno != EAGAIN && errno != EINTR) { LOGW("could not get event (errno=%d)", errno); } } else if ((readSize % sizeof(struct input_event)) != 0) { LOGE("could not get event (wrong size: %d)", readSize); } else { mInputBufferCount = readSize / sizeof(struct input_event); mInputBufferIndex = 0; } } } ...... mInputDeviceIndex = 0; // Poll for events. Mind the wake lock dance! // We hold a wake lock at all times except during poll(). This works due to some // subtle choreography. When a device driver has pending (unread) events, it acquires // a kernel wake lock. However, once the last pending event has been read, the device // driver will release the kernel wake lock. To prevent the system from going to sleep // when this happens, the EventHub holds onto its own user wake lock while the client // is processing events. Thus the system can only sleep if there are no events // pending or currently being processed. release_wake_lock(WAKE_LOCK_ID); int pollResult = poll(mFDs, mFDCount, -1); acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID); if (pollResult <= 0) { if (errno != EINTR) { LOGW("poll failed (errno=%d)\n", errno); usleep(100000); } } } }這個函數(shù)比較長,我們一步一步來分析。
首先,如果是第一次進(jìn)入到這個函數(shù)中時,成員變量mOpened的值為false,于是就會調(diào)用openPlatformInput函數(shù)來打開系統(tǒng)輸入設(shè)備,在本文中,我們主要討論的輸入設(shè)備就是鍵盤了。打開了這些輸入設(shè)備文件后,就可以對這些輸入設(shè)備進(jìn)行是監(jiān)控了。如果不是第一次進(jìn)入到這個函數(shù),那么就會分析當(dāng)前有沒有輸入事件發(fā)生,如果有,就返回這個事件,否則就會進(jìn)入等待狀態(tài),等待下一次輸入事件的發(fā)生。在我們這個場景中,就是等待下一次鍵盤事件的發(fā)生了。
我們先分析openPlatformInput函數(shù)的實(shí)現(xiàn),然后回過頭來分析這個getEvent函數(shù)的具體的實(shí)現(xiàn)。
Step 19. EventHub.openPlatformInput
這個函數(shù)定義在frameworks/base/libs/ui/EventHub.cpp文件中:
bool EventHub::openPlatformInput(void) { ...... res = scanDir(device_path); if(res < 0) { LOGE("scan dir failed for %s\n", device_path); } return true; }這個函數(shù)主要是掃描device_path目錄下的設(shè)備文件,然后打開它們,這里的變量device_path定義在frameworks/base/libs/ui/EventHub.cpp文件開始的地方:
static const char *device_path = "/dev/input";在設(shè)備目錄/dev/input中,一般有三個設(shè)備文件存在,分別是event0、mice和mouse0設(shè)備文件,其中,鍵盤事件就包含在event0設(shè)備文件中了。
Step 20. EventHub.scanDir
這個函數(shù)定義在frameworks/base/libs/ui/EventHub.cpp文件中:
int EventHub::scanDir(const char *dirname) { char devname[PATH_MAX]; char *filename; DIR *dir; struct dirent *de; dir = opendir(dirname); if(dir == NULL) return -1; strcpy(devname, dirname); filename = devname + strlen(devname); *filename++ = '/'; while((de = readdir(dir))) { if(de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) continue; strcpy(filename, de->d_name); openDevice(devname); } closedir(dir); return 0; }根據(jù)上面一步的分析,這個函數(shù)主要就是調(diào)用openDevice函數(shù)來分別打開/dev/input/event0、/dev/input/mice和/dev/input/mouse0三個設(shè)備文件了。
Step 21. EventHub.openDevice
這個函數(shù)定義在frameworks/base/libs/ui/EventHub.cpp文件中:
int EventHub::openDevice(const char *deviceName) { int version; int fd; struct pollfd *new_mFDs; device_t **new_devices; char **new_device_names; char name[80]; char location[80]; char idstr[80]; struct input_id id; LOGV("Opening device: %s", deviceName); AutoMutex _l(mLock); fd = open(deviceName, O_RDWR); if(fd < 0) { LOGE("could not open %s, %s\n", deviceName, strerror(errno)); return -1; } ...... int devid = 0; while (devid < mNumDevicesById) { if (mDevicesById[devid].device == NULL) { break; } devid++; } ...... mDevicesById[devid].seq = (mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK; if (mDevicesById[devid].seq == 0) { mDevicesById[devid].seq = 1<<SEQ_SHIFT; } new_mFDs = (pollfd*)realloc(mFDs, sizeof(mFDs[0]) * (mFDCount + 1)); new_devices = (device_t**)realloc(mDevices, sizeof(mDevices[0]) * (mFDCount + 1)); if (new_mFDs == NULL || new_devices == NULL) { LOGE("out of memory"); return -1; } mFDs = new_mFDs; mDevices = new_devices; ...... device_t* device = new device_t(devid|mDevicesById[devid].seq, deviceName, name); if (device == NULL) { LOGE("out of memory"); return -1; } device->fd = fd; mFDs[mFDCount].fd = fd; mFDs[mFDCount].events = POLLIN; mFDs[mFDCount].revents = 0; // Figure out the kinds of events the device reports. uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)]; memset(key_bitmask, 0, sizeof(key_bitmask)); LOGV("Getting keys..."); if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) >= 0) { // See if this is a keyboard. Ignore everything in the button range except for // gamepads which are also considered keyboards. if (containsNonZeroByte(key_bitmask, 0, sizeof_bit_array(BTN_MISC)) || containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_GAMEPAD), sizeof_bit_array(BTN_DIGI)) || containsNonZeroByte(key_bitmask, sizeof_bit_array(KEY_OK), sizeof_bit_array(KEY_MAX + 1))) { device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; device->keyBitmask = new uint8_t[sizeof(key_bitmask)]; if (device->keyBitmask != NULL) { memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask)); } else { delete device; LOGE("out of memory allocating key bitmask"); return -1; } } } ...... if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) { char tmpfn[sizeof(name)]; char keylayoutFilename[300]; // a more descriptive name device->name = name; // replace all the spaces with underscores strcpy(tmpfn, name); for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' ')) *p = '_'; // find the .kl file we need for this device const char* root = getenv("ANDROID_ROOT"); snprintf(keylayoutFilename, sizeof(keylayoutFilename), "%s/usr/keylayout/%s.kl", root, tmpfn); bool defaultKeymap = false; if (access(keylayoutFilename, R_OK)) { snprintf(keylayoutFilename, sizeof(keylayoutFilename), "%s/usr/keylayout/%s", root, "qwerty.kl"); defaultKeymap = true; } status_t status = device->layoutMap->load(keylayoutFilename); if (status) { LOGE("Error %d loading key layout.", status); } // tell the world about the devname (the descriptive name) if (!mHaveFirstKeyboard && !defaultKeymap && strstr(name, "-keypad")) { // the built-in keyboard has a well-known device ID of 0, // this device better not go away. mHaveFirstKeyboard = true; mFirstKeyboardId = device->id; property_set("hw.keyboards.0.devname", name); } else { // ensure mFirstKeyboardId is set to -something-. if (mFirstKeyboardId == 0) { mFirstKeyboardId = device->id; } } char propName[100]; sprintf(propName, "hw.keyboards.%u.devname", device->id); property_set(propName, name); // 'Q' key support = cheap test of whether this is an alpha-capable kbd if (hasKeycodeLocked(device, AKEYCODE_Q)) { device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY; } // See if this device has a DPAD. if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) && hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) && hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) && hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) && hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) { device->classes |= INPUT_DEVICE_CLASS_DPAD; } // See if this device has a gamepad. for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) { if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) { device->classes |= INPUT_DEVICE_CLASS_GAMEPAD; break; } } LOGI("New keyboard: device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n", device->id, name, propName, keylayoutFilename); } ...... mDevicesById[devid].device = device; device->next = mOpeningDevices; mOpeningDevices = device; mDevices[mFDCount] = device; mFDCount++; return 0; }函數(shù)首先根據(jù)文件名來打開這個設(shè)備文件:
fd = open(deviceName, O_RDWR);系統(tǒng)中所有輸入設(shè)備文件信息都保存在成員變量mDevicesById中,因此,先在mDevicesById找到一個空位置來保存當(dāng)前打開的設(shè)備文件信息:
mDevicesById[devid].seq = (mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK; if (mDevicesById[devid].seq == 0) { mDevicesById[devid].seq = 1<<SEQ_SHIFT; }找到了空閑位置后,就為這個輸入設(shè)備文件創(chuàng)建相應(yīng)的device_t信息:
mDevicesById[devid].seq = (mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK; if (mDevicesById[devid].seq == 0) { mDevicesById[devid].seq = 1<<SEQ_SHIFT; } new_mFDs = (pollfd*)realloc(mFDs, sizeof(mFDs[0]) * (mFDCount + 1)); new_devices = (device_t**)realloc(mDevices, sizeof(mDevices[0]) * (mFDCount + 1)); if (new_mFDs == NULL || new_devices == NULL) { LOGE("out of memory"); return -1; } mFDs = new_mFDs; mDevices = new_devices; ...... device_t* device = new device_t(devid|mDevicesById[devid].seq, deviceName, name); if (device == NULL) { LOGE("out of memory"); return -1; } device->fd = fd;
同時,這個設(shè)備文件還會保存在數(shù)組mFDs中:
mFDs[mFDCount].fd = fd; mFDs[mFDCount].events = POLLIN; mFDs[mFDCount].revents = 0;接下來查看這個設(shè)備是不是鍵盤:
// Figure out the kinds of events the device reports. uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)]; memset(key_bitmask, 0, sizeof(key_bitmask)); LOGV("Getting keys..."); if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) >= 0) { // See if this is a keyboard. Ignore everything in the button range except for // gamepads which are also considered keyboards. if (containsNonZeroByte(key_bitmask, 0, sizeof_bit_array(BTN_MISC)) || containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_GAMEPAD), sizeof_bit_array(BTN_DIGI)) || containsNonZeroByte(key_bitmask, sizeof_bit_array(KEY_OK), sizeof_bit_array(KEY_MAX + 1))) { device->classes |= INPUT_DEVICE_CLASS_KEYBOARD; device->keyBitmask = new uint8_t[sizeof(key_bitmask)]; if (device->keyBitmask != NULL) { memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask)); } else { delete device; LOGE("out of memory allocating key bitmask"); return -1; } } }如果是的話,還要繼續(xù)進(jìn)一步初始化前面為這個設(shè)備文件所創(chuàng)建的device_t結(jié)構(gòu)體,主要就是把結(jié)構(gòu)體device的classes成員變量的INPUT_DEVICE_CLASS_KEYBOARD位置為1了,以表明這是一個鍵盤。
如果是鍵盤設(shè)備,初始化工作還未完成,還要繼續(xù)設(shè)置鍵盤的布局等信息:
if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) { char tmpfn[sizeof(name)]; char keylayoutFilename[300]; // a more descriptive name device->name = name; // replace all the spaces with underscores strcpy(tmpfn, name); for (char *p = strchr(tmpfn, ' '); p && *p; p = strchr(tmpfn, ' ')) *p = '_'; // find the .kl file we need for this device const char* root = getenv("ANDROID_ROOT"); snprintf(keylayoutFilename, sizeof(keylayoutFilename), "%s/usr/keylayout/%s.kl", root, tmpfn); bool defaultKeymap = false; if (access(keylayoutFilename, R_OK)) { snprintf(keylayoutFilename, sizeof(keylayoutFilename), "%s/usr/keylayout/%s", root, "qwerty.kl"); defaultKeymap = true; } status_t status = device->layoutMap->load(keylayoutFilename); if (status) { LOGE("Error %d loading key layout.", status); } // tell the world about the devname (the descriptive name) if (!mHaveFirstKeyboard && !defaultKeymap && strstr(name, "-keypad")) { // the built-in keyboard has a well-known device ID of 0, // this device better not go away. mHaveFirstKeyboard = true; mFirstKeyboardId = device->id; property_set("hw.keyboards.0.devname", name); } else { // ensure mFirstKeyboardId is set to -something-. if (mFirstKeyboardId == 0) { mFirstKeyboardId = device->id; } } char propName[100]; sprintf(propName, "hw.keyboards.%u.devname", device->id); property_set(propName, name); // 'Q' key support = cheap test of whether this is an alpha-capable kbd if (hasKeycodeLocked(device, AKEYCODE_Q)) { device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY; } // See if this device has a DPAD. if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) && hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) && hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) && hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) && hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) { device->classes |= INPUT_DEVICE_CLASS_DPAD; } // See if this device has a gamepad. for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) { if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) { device->classes |= INPUT_DEVICE_CLASS_GAMEPAD; break; } } LOGI("New keyboard: device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n", device->id, name, propName, keylayoutFilename); }到這里,系統(tǒng)中的輸入設(shè)備文件就打開了。
回到Step 18中,我們繼續(xù)分析EventHub.getEvent函數(shù)的實(shí)現(xiàn)。
在中間的for循環(huán)里面,首先會檢查當(dāng)前是否有輸入設(shè)備被關(guān)閉,如果有,就返回一個設(shè)備移除的事件給調(diào)用方:
// Report any devices that had last been added/removed. if (mClosingDevices != NULL) { device_t* device = mClosingDevices; LOGV("Reporting device closed: id=0x%x, name=%s\n", device->id, device->path.string()); mClosingDevices = device->next; if (device->id == mFirstKeyboardId) { outEvent->deviceId = 0; } else { outEvent->deviceId = device->id; } outEvent->type = DEVICE_REMOVED; outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); delete device; mNeedToSendFinishedDeviceScan = true; return true; }接著,檢查當(dāng)前是否有新的輸入設(shè)備加入進(jìn)來:
if (mOpeningDevices != NULL) { device_t* device = mOpeningDevices; LOGV("Reporting device opened: id=0x%x, name=%s\n", device->id, device->path.string()); mOpeningDevices = device->next; if (device->id == mFirstKeyboardId) { outEvent->deviceId = 0; } else { outEvent->deviceId = device->id; } outEvent->type = DEVICE_ADDED; outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); mNeedToSendFinishedDeviceScan = true; return true; }接著,再檢查是否需要結(jié)束監(jiān)控輸入事件:
if (mNeedToSendFinishedDeviceScan) { mNeedToSendFinishedDeviceScan = false; outEvent->type = FINISHED_DEVICE_SCAN; outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); return true; }最后,就是要檢查當(dāng)前是否有還未處理的輸入設(shè)備事件發(fā)生了:
// Grab the next input event. for (;;) { // Consume buffered input events, if any. if (mInputBufferIndex < mInputBufferCount) { const struct input_event& iev = mInputBufferData[mInputBufferIndex++]; const device_t* device = mDevices[mInputDeviceIndex]; LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d", device->path.string(), (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value); if (device->id == mFirstKeyboardId) { outEvent->deviceId = 0; } else { outEvent->deviceId = device->id; } outEvent->type = iev.type; outEvent->scanCode = iev.code; if (iev.type == EV_KEY) { status_t err = device->layoutMap->map(iev.code, & outEvent->keyCode, & outEvent->flags); LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", iev.code, outEvent->keyCode, outEvent->flags, err); if (err != 0) { outEvent->keyCode = AKEYCODE_UNKNOWN; outEvent->flags = 0; } } else { outEvent->keyCode = iev.code; } outEvent->value = iev.value; // Use an event timestamp in the same timebase as // java.lang.System.nanoTime() and android.os.SystemClock.uptimeMillis() // as expected by the rest of the system. outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC); return true; } // Finish reading all events from devices identified in previous poll(). // This code assumes that mInputDeviceIndex is initially 0 and that the // revents member of pollfd is initialized to 0 when the device is first added. // Since mFDs[0] is used for inotify, we process regular events starting at index 1. mInputDeviceIndex += 1; if (mInputDeviceIndex >= mFDCount) { break; } const struct pollfd& pfd = mFDs[mInputDeviceIndex]; if (pfd.revents & POLLIN) { int32_t readSize = read(pfd.fd, mInputBufferData, sizeof(struct input_event) * INPUT_BUFFER_SIZE); if (readSize < 0) { if (errno != EAGAIN && errno != EINTR) { LOGW("could not get event (errno=%d)", errno); } } else if ((readSize % sizeof(struct input_event)) != 0) { LOGE("could not get event (wrong size: %d)", readSize); } else { mInputBufferCount = readSize / sizeof(struct input_event); mInputBufferIndex = 0; } } }未處理的輸入事件保存在成員變量mInputBufferData中,如果有的話,就可以直接返回了,否則的話,就要通過系統(tǒng)調(diào)用poll來等待輸入設(shè)備上發(fā)生新的事件了,在我們這個場景中,就是等待鍵盤有鍵被按下或者松開了。:
int pollResult = poll(mFDs, mFDCount, -1);
這里的mFDs包含了我們所要監(jiān)控的輸入設(shè)備的打開文件描述符,這是在前面的openPlatformInput函數(shù)中初始化的。
Step 22. poll
這是一個Linux系統(tǒng)的文件操作系統(tǒng)調(diào)用,它用來查詢指定的文件列表是否有有可讀寫的,如果有,就馬上返回,否則的話,就阻塞線程,并等待驅(qū)動程序喚醒,重新調(diào)用poll函數(shù),或超時返回。在我們的這個場景中,就是要查詢是否有鍵盤事件發(fā)生,如果有的話,就返回,否則的話,當(dāng)前線程就睡眠等待鍵盤事件的發(fā)生了。
這樣,InputManager的啟動過程就分析完了,下面我們再分析應(yīng)用程序注冊鍵盤消息接收通道的過程。
2. 應(yīng)用程序注冊鍵盤消息接收通道的過程分析
InputManager啟動以后,就開始負(fù)責(zé)監(jiān)控鍵盤輸入事件了。當(dāng)InputManager監(jiān)控到鍵盤輸入事件時,它應(yīng)該把這個鍵盤事件分發(fā)給誰呢?當(dāng)然是要把這個鍵盤消息分發(fā)給當(dāng)前激活的Activity窗口了,不過,當(dāng)前激活的Activity窗口還需要主動注冊一個鍵盤消息接收通道到InputManager中去,InputManager才能把這個鍵盤消息分發(fā)給它處理。那么,當(dāng)前被激活的Activity窗口又是什么時候去注冊這個鍵盤消息接收通道的呢?在前面一篇文章Android應(yīng)用程序啟動過程源代碼分析中,我們分析Android應(yīng)用程序的啟動過程時,在Step 33中分析到ActivityThread類的handleLaunchActivity函數(shù)中,我們曾經(jīng)說過,當(dāng)函數(shù)handleLaunchActivity調(diào)用performLaunchActivity函數(shù)來加載這個完畢應(yīng)用程序的默認(rèn)Activity后,再次回到handleLaunchActivity函數(shù)時,會調(diào)用handleResumeActivity函數(shù)來使這個Activity進(jìn)入Resumed狀態(tài)。在調(diào)用handleResumeActivity函數(shù)的過程中,ActivityThread會通過android.view.WindowManagerImpl類為該Activity創(chuàng)建一個ViewRoot實(shí)例,并且會通過調(diào)用ViewRoot類的setView成員函數(shù)把與該Activity關(guān)聯(lián)的View設(shè)置到這個ViewRoot中去,而Activity正是通過ViewRoot類的setView成員函數(shù)來注冊鍵盤消息接收通道的。
有了這些背影知識后,接下來,我們就可以從ViewRoot.setView函數(shù)開始分析應(yīng)用程序注冊鍵盤消息接收通道的過程了。首先看一下這個注冊過程的序列圖,然后再詳細(xì)分析每一個步驟:
Step 1. ViewRoot.setView
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { ...... synchronized (this) { if (mView == null) { ...... // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); mInputChannel = new InputChannel(); try { res = sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets, mInputChannel); } catch (RemoteException e) { ...... } finally { ...... } ...... if (view instanceof RootViewSurfaceTaker) { mInputQueueCallback = ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue(); } if (mInputQueueCallback != null) { mInputQueue = new InputQueue(mInputChannel); mInputQueueCallback.onInputQueueCreated(mInputQueue); } else { InputQueue.registerInputChannel(mInputChannel, mInputHandler, Looper.myQueue()); } ...... } } } }
這個函數(shù)中與注冊鍵盤消息接收通道(InputChannel)相關(guān)的邏輯主要有三處,一是調(diào)用requestLayout函數(shù)來通知InputManager,這個Activity窗口是當(dāng)前被激活的窗口,二是調(diào)用sWindowSession(WindowManagerService內(nèi)部類Session的遠(yuǎn)程接口)的add成員函數(shù)來把鍵盤消息接收通道的一端注冊在InputManager中,三是調(diào)用InputQueue的registerInputChannel成員函數(shù)來把鍵盤消息接收通道的另一端注冊在本應(yīng)用程序的消息循環(huán)(Looper)中。這樣,當(dāng)InputManager監(jiān)控到有鍵盤消息時,就會先找到當(dāng)前被激活的窗口,然后找到其在InputManager中對應(yīng)的鍵盤消息接收通道,通過這個通道在InputManager中的一端來通知在應(yīng)用程序消息循環(huán)中的另一端,就把鍵盤消息分發(fā)給當(dāng)前激活的Activity窗口了。
在接下來的內(nèi)容中,我們首先描述requestLayout函數(shù)是如何告訴InputManager當(dāng)前的Activity窗口便是激活窗口的,接著再回過頭來分析應(yīng)用程序是如何把鍵盤消息接收通道的一端注冊到InputManager中去的,最后分析應(yīng)用程序是如何鍵盤消息接收通道的另一端注冊到本應(yīng)用程序的消息循環(huán)中去了。
Step 2. ViewRoot.requestLayout
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... public void requestLayout() { ...... mLayoutRequested = true; scheduleTraversals(); } ...... }這個函數(shù)調(diào)用了scheduleTraversals函數(shù)來進(jìn)一步執(zhí)行操作,由于篇幅關(guān)系,我們就不詳細(xì)描述scheduleTraversals函數(shù)了,簡單來說,在scheduleTraversals函數(shù)中,會通過sendEmptyMessage(DO_TRAVERSAL)發(fā)送一個消息到應(yīng)用程序的消息隊(duì)列中,這個消息最終由ViewRoot的handleMessage函數(shù)處理,而ViewRoot的handleMessage函數(shù)把這個消息交給ViewRoot類的performTraversals來處理,在performTraversals函數(shù)中,又會調(diào)用ViewRoot類的relayoutWindow函數(shù)來進(jìn)一步執(zhí)行操作,最后在relayoutWindow函數(shù)中,就會通過WindowManagerService內(nèi)部類Session的遠(yuǎn)程接口sWindowSession的relayout函數(shù)來進(jìn)入到WindowManagerService中。
Step 3. WindowManagerService.Session.relayout
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java 文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { ...... public int relayout(IWindow window, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, boolean insetsPending, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); int res = relayoutWindow(this, window, attrs, requestedWidth, requestedHeight, viewFlags, insetsPending, outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface); //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); return res; } ...... } ...... }
這個函數(shù)只是簡單地調(diào)用WindowManagerService的成員函數(shù)relayoutWIndow來進(jìn)一步處理。
Step 4. WindowManagerService.relayoutWIndow
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java 文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... public int relayoutWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, boolean insetsPending, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { ...... synchronized(mWindowMap) { ...... mInputMonitor.updateInputWindowsLw(); } ...... } ...... }這個函數(shù)又會繼續(xù)調(diào)用mInputMonitor的updateInputWindowsLw成員函數(shù)來更新當(dāng)前的輸入窗口,mInputMonitor是WindowManagerService的成員變量,它的類型為InputMonitor。
Step 5. InputMonitor.updateInputWindowsLw
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java 文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... final class InputMonitor { ...... /* Updates the cached window information provided to the input dispatcher. */ public void updateInputWindowsLw() { // Populate the input window list with information about all of the windows that // could potentially receive input. // As an optimization, we could try to prune the list of windows but this turns // out to be difficult because only the native code knows for sure which window // currently has touch focus. final ArrayList<WindowState> windows = mWindows; final int N = windows.size(); for (int i = N - 1; i >= 0; i--) { final WindowState child = windows.get(i); if (child.mInputChannel == null || child.mRemoved) { // Skip this window because it cannot possibly receive input. continue; } ...... // Add a window to our list of input windows. final InputWindow inputWindow = mTempInputWindows.add(); ...... } // Send windows to native code. mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray()); ...... } ...... } ...... }這個函數(shù)將當(dāng)前系統(tǒng)中帶有InputChannel的Activity窗口都設(shè)置為InputManager的輸入窗口,但是后面我們會看到,只有當(dāng)前激活的窗口才會響應(yīng)鍵盤消息。
Step 6. InputManager.setInputWindows
這個函數(shù)定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager { ...... public void setInputWindows(InputWindow[] windows) { nativeSetInputWindows(windows); } ...... }這個函數(shù)調(diào)用了本地方法nativeSetInputWindows來進(jìn)一步執(zhí)行操作。
Step 7. InputManager.nativeSetInputWindows
這個函數(shù)定義在frameworks/base/services/jni/com_android_server_InputManager.cpp 文件中:
static void android_server_InputManager_nativeSetInputWindows(JNIEnv* env, jclass clazz, jobjectArray windowObjArray) { if (checkInputManagerUnitialized(env)) { return; } gNativeInputManager->setInputWindows(env, windowObjArray); }這里的gNativeInputManager我們前面分析InputManager的啟動過程時已經(jīng)見過了,這是一個本地InputManager對象,通過它進(jìn)一步設(shè)置當(dāng)前系統(tǒng)的輸入窗口。
Step 8. NativeInputManager.setInputWindows
這個函數(shù)定義在frameworks/base/services/jni/com_android_server_InputManager.cpp 文件中:
void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowObjArray) { Vector<InputWindow> windows; jsize length = env->GetArrayLength(windowObjArray); for (jsize i = 0; i < length; i++) { jobject inputTargetObj = env->GetObjectArrayElement(windowObjArray, i); if (! inputTargetObj) { break; // found null element indicating end of used portion of the array } windows.push(); InputWindow& window = windows.editTop(); bool valid = populateWindow(env, inputTargetObj, window); if (! valid) { windows.pop(); } env->DeleteLocalRef(inputTargetObj); } mInputManager->getDispatcher()->setInputWindows(windows); }這個函數(shù)首先將Java層的Window轉(zhuǎn)換成C++層的InputWindow,然后放在windows向量中,最后將這些輸入窗口設(shè)置到InputDispatcher中去。
Step 9. InputDispatcher.setInputWindows
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) { ...... { // acquire lock AutoMutex _l(mLock); // Clear old window pointers. sp<InputChannel> oldFocusedWindowChannel; if (mFocusedWindow) { oldFocusedWindowChannel = mFocusedWindow->inputChannel; mFocusedWindow = NULL; } mWindows.clear(); // Loop over new windows and rebuild the necessary window pointers for // tracking focus and touch. mWindows.appendVector(inputWindows); size_t numWindows = mWindows.size(); for (size_t i = 0; i < numWindows; i++) { const InputWindow* window = & mWindows.itemAt(i); if (window->hasFocus) { mFocusedWindow = window; break; } } ...... } // release lock ...... }這里InputDispatcher的成員變量mFocusedWindow就代表當(dāng)前激活的窗口的。這個函數(shù)首先清空mFocusedWindow,然后再通過一個for循環(huán)檢查當(dāng)前的輸入窗口中的哪一個窗口是獲得焦點(diǎn)的,獲得焦點(diǎn)的輸入窗口即為當(dāng)前激活的窗口。
這樣,InputManager就把當(dāng)前激活的Activity窗口保存在InputDispatcher中了,后面就可以把鍵盤消息分發(fā)給它來處理。
回到Step 1中的ViewRoot.setView函數(shù)中,接下來就調(diào)用下面語句來注冊鍵盤消息接收通道的一端到InputManager中去:
mInputChannel = new InputChannel(); try { res = sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets, mInputChannel); } catch (RemoteException e) { ...... } finally { ...... }前面說過,這里的sWindowSession是WindowManagerService內(nèi)部類Session的一個遠(yuǎn)程接口,通過它可以進(jìn)入到WindowManagerService中去。
Step 10. WindowManagerService.Session.add
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java 文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { ...... public int add(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) { return addWindow(this, window, attrs, viewVisibility, outContentInsets, outInputChannel); } ...... } ...... }這里調(diào)用WindowManagerService類的addWindow函數(shù)來進(jìn)一步執(zhí)行操作。
Step 11. WindowManagerService.addWindow
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java 文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... public int addWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) { ...... WindowState win = null; synchronized(mWindowMap) { ...... win = new WindowState(session, client, token, attachedWindow, attrs, viewVisibility); ...... if (outInputChannel != null) { String name = win.makeInputChannelName(); InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); win.mInputChannel = inputChannels[0]; inputChannels[1].transferToBinderOutParameter(outInputChannel); mInputManager.registerInputChannel(win.mInputChannel); } ...... } ...... } ...... }
這里的outInputChannel即為前面在Step 1中創(chuàng)建的InputChannel,它不為NULL,因此,這里會通過InputChannel.openInputChannelPair函數(shù)來創(chuàng)建一對輸入通道,其中一個位于WindowManagerService中,另外一個通過outInputChannel參數(shù)返回到應(yīng)用程序中:
inputChannels[1].transferToBinderOutParameter(outInputChannel);
創(chuàng)建輸入通道之前,WindowManagerService會為當(dāng)前Activity窗口創(chuàng)建一個WindowState對象win,用來記錄這個Activity窗口的狀態(tài)信息。當(dāng)創(chuàng)建這對輸入管道成功以后,也會把其中的一個管道保存在這個WindowState對象win的成員變量mInputChannel中,后面要注銷這個管道的時候,就是從這個WindownState對象中取回這個管道的:
win.mInputChannel = inputChannels[0];
接下來我們就看一下InputChannel.openInputChannelPair函數(shù)的實(shí)現(xiàn)。
Step 12. InputChannel.openInputChannelPair
這個函數(shù)定義在frameworks/base/core/java/android/view/InputChannel.java文件中:
public final class InputChannel implements Parcelable { ...... /** * Creates a new input channel pair. One channel should be provided to the input * dispatcher and the other to the application's input queue. * @param name The descriptive (non-unique) name of the channel pair. * @return A pair of input channels. They are symmetric and indistinguishable. */ public static InputChannel[] openInputChannelPair(String name) { ...... return nativeOpenInputChannelPair(name); } ...... }這個函數(shù)調(diào)用本地方法nativeOpenInputChannelPair來進(jìn)一步執(zhí)行操作。
Step 13. InputChannel.nativeOpenInputChannelPair
這個函數(shù)定義在frameworks/base/core/jni/android_view_InputChannel.cpp文件中:
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env, jclass clazz, jstring nameObj) { const char* nameChars = env->GetStringUTFChars(nameObj, NULL); String8 name(nameChars); env->ReleaseStringUTFChars(nameObj, nameChars); sp<InputChannel> serverChannel; sp<InputChannel> clientChannel; status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel); if (result) { LOGE("Could not open input channel pair. status=%d", result); jniThrowRuntimeException(env, "Could not open input channel pair."); return NULL; } // TODO more robust error checking jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, new NativeInputChannel(serverChannel)); jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, new NativeInputChannel(clientChannel)); jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL); env->SetObjectArrayElement(channelPair, 0, serverChannelObj); env->SetObjectArrayElement(channelPair, 1, clientChannelObj); return channelPair; }這個函數(shù)根據(jù)傳進(jìn)來的參數(shù)name在C++層分別創(chuàng)建兩個InputChannel,一個作為Server端使用,一個作為Client端使用,這里的Server端即是指InputManager,而Client端即是指應(yīng)用程序。這兩個本地的InputChannel是通過InputChannel::openInputChannelPair函數(shù)創(chuàng)建的,創(chuàng)建完成后,再相應(yīng)地在Java層創(chuàng)建相應(yīng)的兩個InputChannel,然后返回。
Step 14. InputChannel.openInputChannelPair
這個函數(shù)定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputChannel::openInputChannelPair(const String8& name, sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { status_t result; int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE); if (serverAshmemFd < 0) { ...... } else { result = ashmem_set_prot_region(serverAshmemFd, PROT_READ | PROT_WRITE); if (result < 0) { ...... } else { // Dup the file descriptor because the server and client input channel objects that // are returned may have different lifetimes but they share the same shared memory region. int clientAshmemFd; clientAshmemFd = dup(serverAshmemFd); if (clientAshmemFd < 0) { ...... } else { int forward[2]; if (pipe(forward)) { ...... } else { int reverse[2]; if (pipe(reverse)) { ...... } else { String8 serverChannelName = name; serverChannelName.append(" (server)"); outServerChannel = new InputChannel(serverChannelName, serverAshmemFd, reverse[0], forward[1]); String8 clientChannelName = name; clientChannelName.append(" (client)"); outClientChannel = new InputChannel(clientChannelName, clientAshmemFd, forward[0], reverse[1]); return OK; } ...... } ...... } } } ...... }在閱讀這個函數(shù)之前,我們首先了解一下C++層的InputChannel的構(gòu)造函數(shù):
InputChannel::InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd, int32_t sendPipeFd) : mName(name), mAshmemFd(ashmemFd), mReceivePipeFd(receivePipeFd), mSendPipeFd(sendPipeFd) { ...... }為了創(chuàng)建一個InputChannel,我們需要準(zhǔn)備四個參數(shù),一個是輸入通道的名稱name,一個是匿名共享內(nèi)存文件描述符,一個是管道的讀端文件描述符,一個是管道的寫端文件描述符。在上面的openInputChannelPair函數(shù),輸入通道的名稱已經(jīng)作為參數(shù)傳遞進(jìn)來,因此,還需要創(chuàng)建匿名共享內(nèi)存文件,還有管道。這里需要創(chuàng)建兩個管道,一個稱為前向管道(forward pipe),一個稱為反向管道(reverse pipe),它們交叉使用在Server端和Client端的InputChannel中,這樣就使入Server和Client可以互相通信了。
具體來說,Server端和Client端的InputChannel分別是這樣構(gòu)成的:
Server Input Channel: ashmem - reverse(read) - forward(write)
Client Input Channel: ashmem - forward(read) - reverse(write)
前面我們在Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析一文中學(xué)習(xí)Android應(yīng)用程序的消息處理機(jī)制時知道,管道可以用作進(jìn)程間通信,其中一個進(jìn)程在管道的讀端等待新的內(nèi)空可讀,另一個進(jìn)程在管道的寫端寫入新的內(nèi)容以喚醒在管道讀端等待的進(jìn)程,這樣就實(shí)現(xiàn)了進(jìn)程間通信。在我們這個情景中,Client端可以在前向管道(forward pipe)的讀端睡眠等待新的內(nèi)容可讀,而Server端可以通過向前向管道(forward pipe)的寫端寫入新的內(nèi)容來喚醒Client端,同樣,把前向管道(forward pipe)換成反向管道(reverse pipe),也能實(shí)現(xiàn)Client端喚醒Server端。在后面我們分析InputDispatcher分發(fā)鍵盤消息時,會看到它們的用法。
有了這些背景知識后,相信上面的openInputChannelPair的代碼就容易理解了,這里就不再詳述了。
創(chuàng)建好了這兩個輸入通道后,回到Step 11中的WindowManagerService.addWindow函數(shù)中,一方面它把剛才創(chuàng)建的Client端的輸入通道通過outInputChannel參數(shù)返回到應(yīng)用程序中:
inputChannels[1].transferToBinderOutParameter(outInputChannel);
另一方面,它還要把剛才創(chuàng)建的Server端的輸入通道注冊到InputManager中:
mInputManager.registerInputChannel(win.mInputChannel);Step 15. InputManager.registerInputChannel
這個函數(shù)定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager { ...... /** * Registers an input channel so that it can be used as an input event target. * @param inputChannel The input channel to register. */ public void registerInputChannel(InputChannel inputChannel) { if (inputChannel == null) { throw new IllegalArgumentException("inputChannel must not be null."); } nativeRegisterInputChannel(inputChannel, false); } ...... }它通過調(diào)用本地方法nativeRegisterInputChannel來執(zhí)行進(jìn)一步的操作。
Step 16. InputManager.nativeRegisterInputChannel
這個函數(shù)定義在frameworks/base/services/jni/com_android_server_InputManager.cpp 文件中:
static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, jobject inputChannelObj, jboolean monitor) { ...... sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); ...... status_t status = gNativeInputManager->registerInputChannel( env, inputChannel, inputChannelObj, monitor); ...... }這里首先通過Java層的InputChannel對象獲得C++層的InputChannel對象,它們之間的對應(yīng)關(guān)系是在前面的Step 13中設(shè)置好的,接著調(diào)用NativeInputManager的registerInputChannel執(zhí)行進(jìn)一步的操作。
Step 17. NativeInputManager.registerInputChannel
這個函數(shù)定義在frameworks/base/services/jni/com_android_server_InputManager.cpp 文件中:
status_t NativeInputManager::registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, jobject inputChannelObj, bool monitor) { ...... status = mInputManager->getDispatcher()->registerInputChannel(inputChannel, monitor); ...... }這個函數(shù)主要是調(diào)用了InputDispatcher的registerInputChannel來真正執(zhí)行注冊輸入通道的操作。
Step 18. InputDispatcher.registerInputChannel
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) { ...... { // acquire lock AutoMutex _l(mLock); if (getConnectionIndexLocked(inputChannel) >= 0) { LOGW("Attempted to register already registered input channel '%s'", inputChannel->getName().string()); return BAD_VALUE; } sp<Connection> connection = new Connection(inputChannel); status_t status = connection->initialize(); if (status) { LOGE("Failed to initialize input publisher for input channel '%s', status=%d", inputChannel->getName().string(), status); return status; } int32_t receiveFd = inputChannel->getReceivePipeFd(); mConnectionsByReceiveFd.add(receiveFd, connection); if (monitor) { mMonitoringChannels.push(inputChannel); } mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); runCommandsLockedInterruptible(); } // release lock return OK; }這個函數(shù)首先會通過getConnectionIndexLocked檢查從參數(shù)傳進(jìn)來的InputChannel是否已經(jīng)注冊過了,如果已經(jīng)注冊過了,就返回一個BAD_VALUE值了,否則的話,就會創(chuàng)建一個Connection對象來封裝即將要注冊的inputChannel,我們可以不關(guān)心這個Connection對象的實(shí)現(xiàn),接著還通過調(diào)用inputChannel->getReceivePipeFd獲得一個管
道的讀端描述符?;貞浺幌耂tep 14中的InputChannel.openInputChannelPair函數(shù),我們創(chuàng)建了一個Server端的InputChannel,就是對應(yīng)這里的inputChannel了,這個inputChannel的Receive Pipe Fd就是我們前面說的反向管道的讀端描述符了。有了這個Receive Pipe Fd后,就以它作為Key值來把前面創(chuàng)建的Connection對象保存在InputDispatcher中,這樣就基本完成鍵盤消息接收通道的注冊了。但是,注冊的工作還未完成,最后,還要把這個Receive Pipe Fd添加到InputDispatcher的成員變量mLooper中去,這里的成員變量mLooper的類型為Looper,我們在前面介紹InputManager的啟動過程的Step 15中已經(jīng)見過了,這里就不再詳述了,不過這里仍然值得介紹一下它的addFd函數(shù)。
在前面一篇文章Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析中,我們在介紹到Android應(yīng)用程序的消息循環(huán)一節(jié)時,曾經(jīng)說過,在Looper類內(nèi)部,會創(chuàng)建一個管道,然后Looper會睡眠在這個管道的讀端,等待另外一個線程來往這個管道的寫端寫入新的內(nèi)容,從而喚醒等待在這個管道讀端的線程,除此之外,Looper還可以同時睡眠等待在其它的文件描述符上,因?yàn)樗峭ㄟ^Linux系統(tǒng)的epoll機(jī)制來批量等待指定的文件有新的內(nèi)容可讀的。這些其它的文件描述符就是通過Looper類的addFd成函數(shù)添加進(jìn)去的了,在添加的時候,還可以指定回調(diào)函數(shù),即當(dāng)這個文件描述符所指向的文件有新的內(nèi)容可讀時,Looper就會調(diào)用這個hanldeReceiveCallback函數(shù),有興趣的讀者可以自己研究一下Looper類的addFd函數(shù)的實(shí)現(xiàn),它位于frameworks/base/libs/utils/Looper.cpp文件中。
分析到這里,Server端的InputChannel就注冊完成了。回憶一下前面介紹InputManager啟動過程的Step 14,這時InputDispatcherThread同時睡眠在InputDispatcher的成員變量mLooper內(nèi)部的管道的讀端以及這里的Server端InputChannel里面的反向管道的讀端上,mLooper內(nèi)部的管道的讀端等待鍵盤事件的發(fā)生而被喚醒,而Server端InputChannel里面的反向管道的讀端等待Client端InputChannel里面的反向管道的寫端被寫入新的內(nèi)容而被喚醒。
Server端的InputChannel注冊完成后,回到Step 11中的WindowManagerService.addWindow函數(shù),接下來就是把Client端的InputChannel轉(zhuǎn)換成addWindow的參數(shù)outInputChannel中,然后返回到Step 1中的ViewRoot.setView函數(shù)中,繼續(xù)執(zhí)行Client端的InputChannel的注冊過程,即為應(yīng)用程序這一側(cè)注冊鍵盤消息接收通道:
if (view instanceof RootViewSurfaceTaker) { mInputQueueCallback = ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue(); } if (mInputQueueCallback != null) { mInputQueue = new InputQueue(mInputChannel); mInputQueueCallback.onInputQueueCreated(mInputQueue); } else { InputQueue.registerInputChannel(mInputChannel, mInputHandler, Looper.myQueue()); }
這里的變量view一般不為RootViewSurfaceTaker的實(shí)例,因此,最后會執(zhí)行下面語句:
InputQueue.registerInputChannel(mInputChannel, mInputHandler, Looper.myQueue());它調(diào)用InputQueue的registerInputChannel函數(shù)為應(yīng)用程序注冊鍵盤消息接收通道,這里的mInputChannel即為我們在前面Step 14中創(chuàng)建的Client端的InputChannel;Looper.myQueue函數(shù)返回的便是應(yīng)用程序主線程的消息隊(duì)列,具體可以參考前面一篇文章Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析;參數(shù)mInputHandler是一個回調(diào)對象,當(dāng)有鍵盤事件發(fā)生時,這個mInputHandler的handleKey函數(shù)就會被調(diào)用,在后面的分析中,我們將會看到。
Step 19. InputQueue.registerInputChannel
這個函數(shù)定義在frameworks/base/core/java/android/view/InputQueue.java文件中:
public final class InputQueue { ...... public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler, MessageQueue messageQueue) { ...... synchronized (sLock) { ...... nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue); } } ...... }這個函數(shù)調(diào)用本地方法nativeRegisterInputChannel函數(shù)來執(zhí)行進(jìn)一步的操作。
Step 20. InputQueue.nativeRegisterInputChannel
這個函數(shù)定義在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) { status_t status = gNativeInputQueue.registerInputChannel( env, inputChannelObj, inputHandlerObj, messageQueueObj); ...... }這里繼續(xù)調(diào)用NativeInputQueue的registerInputChannel函數(shù)來執(zhí)行真正的鍵盤消息接收通道的工作。
Step 21. NativeInputQueue.registerInputChannel
這個函數(shù)定義在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) { sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); ...... sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj); { // acquire lock AutoMutex _l(mLock); if (getConnectionIndex(inputChannel) >= 0) { LOGW("Attempted to register already registered input channel '%s'", inputChannel->getName().string()); return BAD_VALUE; } uint16_t connectionId = mNextConnectionId++; sp<Connection> connection = new Connection(connectionId, inputChannel, looper); status_t result = connection->inputConsumer.initialize(); if (result) { LOGW("Failed to initialize input consumer for input channel '%s', status=%d", inputChannel->getName().string(), result); return result; } connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj); int32_t receiveFd = inputChannel->getReceivePipeFd(); mConnectionsByReceiveFd.add(receiveFd, connection); looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); } // release lock ...... return OK; }這里注冊應(yīng)用程序的InputChannel的邏輯和前面介紹的Step 18中在InputDispatcher中注冊Server端的InputChannel是一樣的,所不同的是,這里用的looper是應(yīng)用程序主線程中的消息循環(huán)對象Looper,而添加到這個looper對象中的Receive Pipe Fd是前面在Step 14中創(chuàng)建的前向管道的讀端文件描述符,而使用的回調(diào)函數(shù)是NativeInputQueue的成員函數(shù)handleReceiveCallback。
介紹到這里,應(yīng)用程序注冊鍵盤消息接收通道的過程就分析完成了。這個過程比較復(fù)雜,這里小結(jié)一下:
A. 即將會被激活的Activity窗口,會通知InputManager,它是當(dāng)前激活的窗口,因此,一旦發(fā)生鍵盤事件的時候,InputManager就把這個鍵盤事件拋給這個Activity處理;
B. 應(yīng)用程序會為這個Activity窗口和InputManager之間創(chuàng)建一個鍵盤消息接收通道,這個通道的一端由一個Server端的InputChannel構(gòu)成,另一端由Client端的InputChannel構(gòu)成,Server端的InputChannel注冊在由InputManager所管理的InputDispatcher中,而Client端的InputChannel注冊在由應(yīng)用程序主線程的消息循環(huán)對象Looper中;
C. 注冊在InputDispatcher中的InputChannel由一個反向管道的讀端和一個前向管道的寫端組成,而注冊在應(yīng)用程序主線程的消息循環(huán)對象Looper中的InputChannel由這個前向管道的讀端和反向管道的寫端組成,這種交叉結(jié)構(gòu)使得當(dāng)有鍵盤事件發(fā)生時,InputDispatcher可以把這個事件通知給應(yīng)用程序。
應(yīng)用程序注冊好鍵盤消息接收通道后,接下來就開始分析InputManager分發(fā)鍵盤消息給應(yīng)用程序的過程了。
3. InputManager分發(fā)鍵盤消息給應(yīng)用程序的過程分析
在分析InputManager分發(fā)鍵盤消息給應(yīng)用程序的過程之前,我們先假設(shè)現(xiàn)在沒有鍵盤事件發(fā)生,因此,InputManager中的InputReader正在睡眠等待鍵盤事件的發(fā)生,而InputManager中的InputDispatcher正在等待InputReader從睡眠中醒過來并且喚醒它,而應(yīng)用程序也正在消息循環(huán)中等待InputDispatcher從睡眠中醒過來并且喚醒它。這時候,用戶按下鍵盤中的一個鍵,于是,一系列喚醒的事件就依次發(fā)生了,一直到應(yīng)用程序中正在顯示的Activity得到通知,有鍵盤事件發(fā)生了。我們先來看這個過程的序列圖,然后再詳細(xì)分析每一個步驟:
Step 1. InputReader.pollOnce
Step 2. EventHub.getEvent
這兩個函數(shù)分別定義在frameworks/base/libs/ui/InputReader.cpp和frameworks/base/libs/ui/EventHub.cpp文件中,前面我們在分析InputManager的啟動過程的Step 17和Step 18時,已經(jīng)看到過這兩個函數(shù)了。InputReaderThread線程會不民地循環(huán)調(diào)用InputReader.pollOnce函數(shù)來讀入鍵盤事件,而實(shí)際的鍵盤事件讀入操作是由EventHub.getEvent函數(shù)來進(jìn)行的。如果當(dāng)前沒有鍵盤事件發(fā)生,InputReaderThread線程就會睡眠在EventHub.getEvent函數(shù)上,而當(dāng)鍵盤事件發(fā)生后,就會把這個事件封裝成一個RawEvent對象,然后返回到pollOnce函數(shù)中,執(zhí)行process函數(shù)進(jìn)一步處理:
void InputReader::loopOnce() { RawEvent rawEvent; mEventHub->getEvent(& rawEvent); ...... process(& rawEvent); }Step 3. InputReader.process
這個函數(shù)定義在frameworks/base/libs/ui/InputReader.cpp文件中:
void InputReader::process(const RawEvent* rawEvent) { switch (rawEvent->type) { case EventHubInterface::DEVICE_ADDED: addDevice(rawEvent->deviceId); break; case EventHubInterface::DEVICE_REMOVED: removeDevice(rawEvent->deviceId); break; case EventHubInterface::FINISHED_DEVICE_SCAN: handleConfigurationChanged(rawEvent->when); break; default: consumeEvent(rawEvent); break; } }
當(dāng)鍵盤事件發(fā)生時,rawEvent->type的值為EV_KEY,這是一個宏定義,具體可以參考bionic/libc/kernel/common/linux/input.h文件:
#define EV_KEY 0x01因此,接下來會調(diào)用consumeEvent函數(shù)進(jìn)一步處理。
Step 4. InputReader.consumeEvent
這個函數(shù)定義在frameworks/base/libs/ui/InputReader.cpp文件中:
void InputReader::consumeEvent(const RawEvent* rawEvent) { int32_t deviceId = rawEvent->deviceId; { // acquire device registry reader lock RWLock::AutoRLock _rl(mDeviceRegistryLock); ssize_t deviceIndex = mDevices.indexOfKey(deviceId); if (deviceIndex < 0) { LOGW("Discarding event for unknown deviceId %d.", deviceId); return; } InputDevice* device = mDevices.valueAt(deviceIndex); if (device->isIgnored()) { //LOGD("Discarding event for ignored deviceId %d.", deviceId); return; } device->process(rawEvent); } // release device registry reader lock }首先從rawEvent中取得觸發(fā)鍵盤事件設(shè)備對象device,然后調(diào)用它的process函數(shù)進(jìn)行處理。
Step 5. InputDevice.process
這個函數(shù)定義在frameworks/base/libs/ui/InputReader.cpp文件中:
void InputDevice::process(const RawEvent* rawEvent) { size_t numMappers = mMappers.size(); for (size_t i = 0; i < numMappers; i++) { InputMapper* mapper = mMappers[i]; mapper->process(rawEvent); } }這里的mMapper成員變量保存了一系列輸入設(shè)備事件處理象,例如負(fù)責(zé)處理鍵盤事件的KeyboardKeyMapper對象、負(fù)責(zé)處理軌跡球事件的TrackballInputMapper對象以及負(fù)責(zé)處理觸摸屏事件的TouchInputMapper對象, 它們是在InputReader類的成員函數(shù)createDevice中創(chuàng)建的。這里查詢每一個InputMapper對象是否要對當(dāng)前發(fā)生的事件進(jìn)行處理。由于發(fā)生的是鍵盤事件,真正會對該事件進(jìn)行處理的只有KeyboardKeyMapper對象。
Step 6. KeyboardInputMapper.process
這個函數(shù)定義在frameworks/base/libs/ui/InputReader.cpp文件中:
void KeyboardInputMapper::process(const RawEvent* rawEvent) { switch (rawEvent->type) { case EV_KEY: { int32_t scanCode = rawEvent->scanCode; if (isKeyboardOrGamepadKey(scanCode)) { processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode, rawEvent->flags); } break; } } }這個函數(shù)首先會檢查一下鍵盤掃描碼是否正確,如果正確的話,就會調(diào)用processKey函數(shù)進(jìn)一步處理。
Step 7. KeyboardInputMapper.processKey
這個函數(shù)定義在frameworks/base/libs/ui/InputReader.cpp文件中:
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) { int32_t newMetaState; nsecs_t downTime; bool metaStateChanged = false; { // acquire lock AutoMutex _l(mLock); if (down) { // Rotate key codes according to orientation if needed. // Note: getDisplayInfo is non-reentrant so we can continue holding the lock. if (mAssociatedDisplayId >= 0) { int32_t orientation; if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) { return; } keyCode = rotateKeyCode(keyCode, orientation); } // Add key down. ssize_t keyDownIndex = findKeyDownLocked(scanCode); if (keyDownIndex >= 0) { // key repeat, be sure to use same keycode as before in case of rotation keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode; } else { // key down if ((policyFlags & POLICY_FLAG_VIRTUAL) && mContext->shouldDropVirtualKey(when, getDevice(), keyCode, scanCode)) { return; } mLocked.keyDowns.push(); KeyDown& keyDown = mLocked.keyDowns.editTop(); keyDown.keyCode = keyCode; keyDown.scanCode = scanCode; } mLocked.downTime = when; } else { // Remove key down. ssize_t keyDownIndex = findKeyDownLocked(scanCode); if (keyDownIndex >= 0) { // key up, be sure to use same keycode as before in case of rotation keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode; mLocked.keyDowns.removeAt(size_t(keyDownIndex)); } else { // key was not actually down LOGI("Dropping key up from device %s because the key was not down. " "keyCode=%d, scanCode=%d", getDeviceName().string(), keyCode, scanCode); return; } } int32_t oldMetaState = mLocked.metaState; newMetaState = updateMetaState(keyCode, down, oldMetaState); if (oldMetaState != newMetaState) { mLocked.metaState = newMetaState; metaStateChanged = true; } downTime = mLocked.downTime; } // release lock if (metaStateChanged) { getContext()->updateGlobalMetaState(); } getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime); }這個函數(shù)首先對對按鍵作一些處理,例如,當(dāng)某一個DPAD鍵被按下時,根據(jù)當(dāng)時屏幕方向的不同,它所表示的意義也不同,因此,這里需要根據(jù)當(dāng)時屏幕的方向來調(diào)整鍵盤碼:
// Rotate key codes according to orientation if needed. // Note: getDisplayInfo is non-reentrant so we can continue holding the lock. if (mAssociatedDisplayId >= 0) { int32_t orientation; if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) { return; } keyCode = rotateKeyCode(keyCode, orientation); }如果這個鍵是一直按著不放的,不管屏幕的方向如何,必須保證后面的鍵盤碼和前面的一樣:
// Add key down. ssize_t keyDownIndex = findKeyDownLocked(scanCode); if (keyDownIndex >= 0) { // key repeat, be sure to use same keycode as before in case of rotation keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode; } else { // key down if ((policyFlags & POLICY_FLAG_VIRTUAL) && mContext->shouldDropVirtualKey(when, getDevice(), keyCode, scanCode)) { return; } mLocked.keyDowns.push(); KeyDown& keyDown = mLocked.keyDowns.editTop(); keyDown.keyCode = keyCode; keyDown.scanCode = scanCode; }如果是第一次按下某個鍵,還必須把它保存在mLocked.keyDowns里面,就是為了處理上面講的當(dāng)這個鍵盤一直按著不放的時候屏幕方向發(fā)生改變的情況。
如果是松開鍵盤上的某個鍵,就把它從mLocked.keyDowns里面刪除:
// Remove key down. ssize_t keyDownIndex = findKeyDownLocked(scanCode); if (keyDownIndex >= 0) { // key up, be sure to use same keycode as before in case of rotation keyCode = mLocked.keyDowns.itemAt(keyDownIndex).keyCode; mLocked.keyDowns.removeAt(size_t(keyDownIndex)); } else { // key was not actually down LOGI("Dropping key up from device %s because the key was not down. " "keyCode=%d, scanCode=%d", getDeviceName().string(), keyCode, scanCode); return; }當(dāng)然,對鍵盤事件的這些處理不是本文的重點(diǎn),本文的重點(diǎn)是分析從鍵盤事件到當(dāng)前激活的Activity窗口接收到這個鍵盤消息的過程。
最后,KeyboardInputMappger函數(shù)通知InputDispatcher,有鍵盤事件發(fā)生了:
getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);Step 8. InputDispatcher.notifyKey
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) { ...... if (! validateKeyEvent(action)) { return; } /* According to http://source.android.com/porting/keymaps_keyboard_input.html * Key definitions: Key definitions follow the syntax key SCANCODE KEYCODE [FLAGS...], * where SCANCODE is a number, KEYCODE is defined in your specific keylayout file * (android.keylayout.xxx), and potential FLAGS are defined as follows: * SHIFT: While pressed, the shift key modifier is set * ALT: While pressed, the alt key modifier is set * CAPS: While pressed, the caps lock key modifier is set * Since KeyEvent.java doesn't check if Cap lock is ON and we don't have a * modifer state for cap lock, we will not support it. */ if (policyFlags & POLICY_FLAG_ALT) { metaState |= AMETA_ALT_ON | AMETA_ALT_LEFT_ON; } if (policyFlags & POLICY_FLAG_ALT_GR) { metaState |= AMETA_ALT_ON | AMETA_ALT_RIGHT_ON; } if (policyFlags & POLICY_FLAG_SHIFT) { metaState |= AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON; } policyFlags |= POLICY_FLAG_TRUSTED; mPolicy->interceptKeyBeforeQueueing(eventTime, deviceId, action, /*byref*/ flags, keyCode, scanCode, /*byref*/ policyFlags); bool needWake; { // acquire lock AutoMutex _l(mLock); int32_t repeatCount = 0; KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime, deviceId, source, policyFlags, action, flags, keyCode, scanCode, metaState, repeatCount, downTime); needWake = enqueueInboundEventLocked(newEntry); } // release lock if (needWake) { mLooper->wake(); } }函數(shù)首先是調(diào)用validateKeyEvent函數(shù)來驗(yàn)證action參數(shù)是否正確:
static bool isValidKeyAction(int32_t action) { switch (action) { case AKEY_EVENT_ACTION_DOWN: case AKEY_EVENT_ACTION_UP: return true; default: return false; } } static bool validateKeyEvent(int32_t action) { if (! isValidKeyAction(action)) { LOGE("Key event has invalid action code 0x%x", action); return false; } return true; }正確的action參數(shù)的值只能為AKEY_EVENT_ACTION_DOWN(按下)或者AKEY_EVENT_ACTION_UP(松開)。
參數(shù)action檢查通過后,還通過policyFlags參數(shù)來檢查一下同時是否有ALT和SHIFT鍵被按下:
if (policyFlags & POLICY_FLAG_ALT) { metaState |= AMETA_ALT_ON | AMETA_ALT_LEFT_ON; } if (policyFlags & POLICY_FLAG_ALT_GR) { metaState |= AMETA_ALT_ON | AMETA_ALT_RIGHT_ON; } if (policyFlags & POLICY_FLAG_SHIFT) { metaState |= AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON; }最后,調(diào)用enqueueInboundEventLocked函數(shù)把這個按鍵事件封裝成一個KeyEntry結(jié)構(gòu)加入到InputDispatcher類的mInboundQueue隊(duì)列中去:
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
bool needWake = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(entry);
switch (entry->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
if (isAppSwitchKeyEventLocked(keyEntry)) {
if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
mAppSwitchSawKeyDown = true;
} else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
if (mAppSwitchSawKeyDown) {
......
mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
mAppSwitchSawKeyDown = false;
needWake = true;
}
}
}
break;
}
}
return needWake;
}
從這個函數(shù)我們可以看出,在兩種情況下,它的返回值為true,一是當(dāng)加入該鍵盤事件到mInboundQueue之前,mInboundQueue為空,這表示InputDispatccherThread線程正在睡眠等待InputReaderThread線程的喚醒,因此,它返回true表示要喚醒InputDispatccherThread線程;二是加入該鍵盤事件到mInboundQueue之前,mInboundQueue不為空,但是此時用戶按下的是Home鍵,按下Home鍵表示要切換App,我們知道,在切換App時,新的App會把它的鍵盤消息接收通道注冊到InputDispatcher中去,并且會等待InputReader的喚醒,因此,在這種情況下,也需要返回true,表示要喚醒InputDispatccherThread線程。如果不是這兩種情況,那么就說明InputDispatccherThread線程現(xiàn)在正在處理前面的鍵盤事件,不需要喚醒它。
回到前面的notifyKey函數(shù)中,根據(jù)enqueueInboundEventLocked函數(shù)的返回值來決定是否要喚醒InputDispatccherThread線程:
if (needWake) { mLooper->wake(); }這里,假設(shè)needWake為true,于是,就會調(diào)用mLooper對象的wake函數(shù)來喚醒InputDispatccherThread線程了。
Step 9. Looper.wake
這個函數(shù)定義在frameworks/base/libs/utils/Looper.cpp文件中,在前面一篇文章Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析中,我們已經(jīng)分析過這個函數(shù)了,這里不再詳述,簡單來說,它的作用就是用來喚醒睡眠在Looper對象內(nèi)部的管道讀端的線程,在我們的這個場景中,睡眠在Looper對象內(nèi)部的管道讀端的線程就是InputDispatccherThread線程了。
從上面InputManager啟動過程的Step 15中,我們知道,此時InputDispatccherThread線程正在InputDispatcher類的dispatchOnceb函數(shù)中通過調(diào)用mLooper->loopOnce函數(shù)進(jìn)入睡眠狀態(tài)。當(dāng)它被喚醒以后,它就會從InputDispatcher類的dispatchOnceb函數(shù)返回到InputDispatcherThread類的threadLoop函數(shù),而InputDispatcherThread類的threadLoop函數(shù)是循環(huán)執(zhí)行的,于是,它又會再次進(jìn)入到InputDispatcher類的dispatchOnce函數(shù)來處理當(dāng)前發(fā)生的鍵盤事件。
Step 10. InputDispatcher.dispatchOnce
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::dispatchOnce() { nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout(); nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay(); nsecs_t nextWakeupTime = LONG_LONG_MAX; { // acquire lock AutoMutex _l(mLock); dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime); ...... } // release lock ...... }它調(diào)用dispatchOnceInnerLocked函數(shù)來進(jìn)一步處理這個鍵盤事件。
Step 11. InputDispatcher.dispatchOnceInnerLocked
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) { ...... // Ready to start a new event. // If we don't already have a pending event, go grab one. if (! mPendingEvent) { if (mInboundQueue.isEmpty()) { ...... } else { // Inbound queue has at least one entry. EventEntry* entry = mInboundQueue.headSentinel.next; ...... mInboundQueue.dequeue(entry); mPendingEvent = entry; } ...... } ...... switch (mPendingEvent->type) { ...... case EventEntry::TYPE_KEY: { KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent); ...... done = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout, &dropReason, nextWakeupTime); break; } ...... } ...... }
我們忽略了這個函數(shù)的次要邏輯,主要關(guān)注鍵盤事件的主要處理流程。首先,如果前面發(fā)生的鍵盤事件都已經(jīng)處理完畢,那么這里的mPendingEvent就為NULL,又因?yàn)榍懊嫖覀儼褎倓偘l(fā)生的鍵盤事件加入了mInboundQueue隊(duì)列,因此,這里mInboundQueue不為NULL,于是,這里就把mInboundQueue隊(duì)列中的鍵盤事件取出來,放在mPendingEvent變量中:
mInboundQueue.dequeue(entry); mPendingEvent = entry;由于這里發(fā)生的是鍵盤事件,即mPendingEvent->type的值為EventEntry::TYPE_KEY,于是,在接下來的switch語句中就會執(zhí)行dispatchKeyLocked函數(shù)來分發(fā)鍵盤消息。
Step 12. InputDispatcher.dispatchKeyLocked
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
bool InputDispatcher::dispatchKeyLocked( nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout, DropReason* dropReason, nsecs_t* nextWakeupTime) { ...... // Identify targets. if (! mCurrentInputTargetsValid) { int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, entry, nextWakeupTime); ...... } // Dispatch the key. dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); return true; }InputDispatcher類中的mCurrentInputTargetsValid成員變量表示InputDispatcher是否已經(jīng)標(biāo)志出誰是當(dāng)前激活的Activity窗口,如果沒有,就需要通過findFocusedWindowTargetsLocked函數(shù)來把它找出來。當(dāng)把當(dāng)前激活的Activity窗口找出來以后,接下來就調(diào)用dispatchEventToCurrentInputTargetsLocked函數(shù)把鍵盤事件分發(fā)給它了。
我們先來看一InputDispatcher是如何找到當(dāng)前激活的Activity窗口的,然后再分析它把鍵盤事件分發(fā)給當(dāng)前激活A(yù)ctivity窗口的過程。
Step 13. InputDispatcher.findFocusedWindowTargetsLocked
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry, nsecs_t* nextWakeupTime) { mCurrentInputTargets.clear(); int32_t injectionResult; // If there is no currently focused window and no focused application // then drop the event. if (! mFocusedWindow) { if (mFocusedApplication) { ...... injectionResult = handleTargetsNotReadyLocked(currentTime, entry, mFocusedApplication, NULL, nextWakeupTime); goto Unresponsive; } ...... injectionResult = INPUT_EVENT_INJECTION_FAILED; goto Failed; } // Check permissions. if (! checkInjectionPermission(mFocusedWindow, entry->injectionState)) { injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; goto Failed; } // If the currently focused window is paused then keep waiting. if (mFocusedWindow->paused) { ...... injectionResult = handleTargetsNotReadyLocked(currentTime, entry, mFocusedApplication, mFocusedWindow, nextWakeupTime); goto Unresponsive; } // If the currently focused window is still working on previous events then keep waiting. if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindow)) { ...... injectionResult = handleTargetsNotReadyLocked(currentTime, entry, mFocusedApplication, mFocusedWindow, nextWakeupTime); goto Unresponsive; } // Success! Output targets. injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_FOREGROUND, BitSet32(0)); ...... return injectionResult; }回憶前面我們分析應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 9中,當(dāng)前處于激活狀態(tài)的應(yīng)用程序會通過調(diào)用InputDispatcher類setInputWindows函數(shù)把把當(dāng)前獲得焦點(diǎn)的Activity窗口設(shè)置到mFocusedWindow中去,因此,這里的mFocusedWindow不為NULL,于是,就通過了第一個if語句的檢查。
第二個if語句檢查權(quán)限問題,原來,這個鍵盤事件除了是由硬件觸發(fā)的外,也可以由其它進(jìn)程注入進(jìn)來的,如果這個鍵盤事件是由其它進(jìn)程注入進(jìn)來的,那么entry->injectState就不為NULL,它里面包含了事件注冊者的進(jìn)程ID和用戶ID,于是,這里就會調(diào)用checkInjectionPermission來檢查這個事件注入者的進(jìn)程ID和用戶ID,看看它是否具有這個權(quán)限。這里我們不考慮這種情況,因此,這里的entry->injectState為NULL,于是,這個if語句的檢查也通過了。
第三個if語句檢查當(dāng)前激活的Activity窗口是否是處于paused狀態(tài),如果是的話,也不用進(jìn)一步處理了。一般情況下,當(dāng)前激活的Activity窗口都是處于resumed狀態(tài)的,于是,這個if語句的檢查也通過了。
第四個if語句檢查當(dāng)前激活的Activity窗口是否還正在處理前一個鍵盤事件,如果是的話,那就要等待它處理完前一個鍵盤事件后再來處理新的鍵盤事件了。這里我們也假設(shè)當(dāng)前激活的Activity窗口不是正在處理前面的鍵盤事件,因此,這個if語句的檢查也通過了。
最后,就調(diào)用addWindowTargetLocked函數(shù)把當(dāng)前激活的Activity窗口添加到InputDispatcher類的mCurrentInputTargets成員變量中去。
Step 14. InputDispatcher.addWindowTargetLocked
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, BitSet32 pointerIds) { mCurrentInputTargets.push(); InputTarget& target = mCurrentInputTargets.editTop(); target.inputChannel = window->inputChannel; target.flags = targetFlags; target.xOffset = - window->frameLeft; target.yOffset = - window->frameTop; target.pointerIds = pointerIds; }這個函數(shù)簡單,就是把傳進(jìn)來的參數(shù)window添加到mCurrentInputTargets中去就完事了,后面InputDispatcher就會從mCurrentInputTargets中取出恰當(dāng)?shù)腁ctivity窗口,然后把鍵盤事件分發(fā)給它。
回到Step 12中的dispatchKeyLocked函數(shù),它接下來就調(diào)用dispatchEventToCurrentInputTargetsLocked來進(jìn)一步處理了。
Step 15. InputDispatcher.dispatchEventToCurrentInputTargetsLocked
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, EventEntry* eventEntry, bool resumeWithAppendedMotionSample) { ...... for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i); ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); if (connectionIndex >= 0) { sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget, resumeWithAppendedMotionSample); } else { ...... } }這個函數(shù)的實(shí)現(xiàn)也比較簡單,前面我們已經(jīng)把當(dāng)前需要接受鍵盤事件的Activity窗口添加到mCurrentInputTargets中去了,因此,這里就分別把它們?nèi)〕鰜恚缓笳{(diào)用prepareDispatchCycleLocked函數(shù)把鍵盤事件分發(fā)給它們處理。
前面我們在分析應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 18中(InputDispatcher.registerInputChannel),把Server端的InputChannel封裝成了一個Connection,然后以這個InputChannel中的Receive Pipe Fd作為鍵值把這個Connection對象保存在mConnectionsByReceiveFd中。這里,既然我們已經(jīng)通過mCurrentInputTargets得到了表示當(dāng)前需要接收鍵盤事件的Activity窗口的InputTarget對象,而且這個InputTarget對象的inputChannel就表示當(dāng)初在InputDispatcher中注冊的Server端InputChannel,因此,這里就可以把這個Connection對象取出來,最后調(diào)用prepareDispatchCycleLocked函數(shù)來進(jìn)一步處理。
Step 16. InputDispatcher.prepareDispatchCycleLocked
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget, bool resumeWithAppendedMotionSample) { ...... // Resume the dispatch cycle with a freshly appended motion sample. // First we check that the last dispatch entry in the outbound queue is for the same // motion event to which we appended the motion sample. If we find such a dispatch // entry, and if it is currently in progress then we try to stream the new sample. bool wasEmpty = connection->outboundQueue.isEmpty(); if (! wasEmpty && resumeWithAppendedMotionSample) { ...... return; } // This is a new event. // Enqueue a new dispatch entry onto the outbound queue for this connection. DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset); ...... // Enqueue the dispatch entry. connection->outboundQueue.enqueueAtTail(dispatchEntry); // If the outbound queue was previously empty, start the dispatch cycle going. if (wasEmpty) { ...... startDispatchCycleLocked(currentTime, connection); } }
在開始處理鍵盤事件之前,這個函數(shù)會檢查一下傳進(jìn)來的參數(shù)connection中的outboundQueue事件隊(duì)列是否為空,如果不為空,就要看看當(dāng)前要處理的事件和outboundQueue隊(duì)列中的最后一個事件是不是同一個motion事件,如果是的話,并且從上面?zhèn)鬟M(jìn)來的resumeWithAppendedMotionSample參數(shù)為true,這時候就要以流水線的方式來處理這些motion事件了。在我們這個情景中,要處理的是鍵盤事件,因此在上面Step 12中傳進(jìn)來的resumeWithAppendedMotionSample參數(shù)為false,因此,我們略過這種情況。
接下來,就會把當(dāng)前的鍵盤事件封裝成一個DispatchEntry對象,然后添加到connection對象的outboundQueue隊(duì)列中去,表示當(dāng)前鍵盤事件是一個待處理的鍵盤事件。
當(dāng)connection中的outboundQueue事件隊(duì)列不為空,即wasEmpty為false時,說明當(dāng)前這個Activity窗口正在處鍵盤事件了,因此,就不需要調(diào)用startDispatchCycleLocked來啟動Activity窗口來處理這個事件了,因?yàn)橐坏┻@個Activity窗口正在處鍵盤事件,它就會一直處理下去,直到它里的connection對象的outboundQueue為空為止。當(dāng)connection中的outboundQueue事件隊(duì)列為空時,就需要調(diào)用startDispatchCycleLocked來通知這個Activity窗口來執(zhí)行鍵盤事件處理的流程了。
Step 17. InputDispatcher.startDispatchCycleLocked
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) { ...... DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next; // Mark the dispatch entry as in progress. dispatchEntry->inProgress = true; // Update the connection's input state. EventEntry* eventEntry = dispatchEntry->eventEntry; ...... // Publish the event. status_t status; switch (eventEntry->type) { case EventEntry::TYPE_KEY: { KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry); // Apply target flags. int32_t action = keyEntry->action; int32_t flags = keyEntry->flags; // Publish the key event. status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source, action, flags, keyEntry->keyCode, keyEntry->scanCode, keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, keyEntry->eventTime); ...... break; } ...... } // Send the dispatch signal. status = connection->inputPublisher.sendDispatchSignal(); ...... }這個函數(shù)主要圍繞傳進(jìn)來的Connection對象做兩件事情,一是從它的outboundQueue隊(duì)列中取出當(dāng)前需要處理的鍵盤事件,然后把這個事件記錄在它的內(nèi)部對象inputPublisher中,二是通過它的內(nèi)部對象inputPublisher通知它所關(guān)聯(lián)的Activity窗口,現(xiàn)在有鍵盤事件需要處理了。第一件事情是通過調(diào)用它的InputPublisher對象的publishKeyEvent函數(shù)來完成的,而第二件事情是通過調(diào)用它的InputPublisher對象的sendDispatchSignal來完成的。我們先來看InputPublisher的成員函數(shù)publishKeyEvent的實(shí)現(xiàn),然后再回來分析它的另外一個成員函數(shù)sendDispatchSignal的實(shí)現(xiàn)。
Step 18. InputPublisher.publishKeyEvent
這個函數(shù)定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputPublisher::publishKeyEvent( int32_t deviceId, int32_t source, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime, nsecs_t eventTime) { ...... status_t result = publishInputEvent(AINPUT_EVENT_TYPE_KEY, deviceId, source); if (result < 0) { return result; } mSharedMessage->key.action = action; mSharedMessage->key.flags = flags; mSharedMessage->key.keyCode = keyCode; mSharedMessage->key.scanCode = scanCode; mSharedMessage->key.metaState = metaState; mSharedMessage->key.repeatCount = repeatCount; mSharedMessage->key.downTime = downTime; mSharedMessage->key.eventTime = eventTime; return OK; }這個函數(shù)主要就是把鍵盤事件記錄在InputPublisher類的成員變量mSharedMessage中了,這個mSharedMessage成員變量指向的是一個匿名共享內(nèi)存。
這個匿名共享內(nèi)存是什么時候創(chuàng)建的呢?前面我們在分析應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 18中(InputDispatcher.registerInputChannel),在把Server端的InputChannel封裝成一個 Connection對象時,會調(diào)用它的initialize成員函數(shù)來執(zhí)行一些初始化工作,就是在這個時候創(chuàng)建這個匿名共享內(nèi)存的了:
sp<Connection> connection = new Connection(inputChannel); status_t status = connection->initialize();我們來看一下這個initialize函數(shù)的實(shí)現(xiàn),它定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputPublisher::initialize() { ...... int ashmemFd = mChannel->getAshmemFd(); int result = ashmem_get_size_region(ashmemFd); ...... mAshmemSize = (size_t) result; mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); ...... mPinned = true; mSharedMessage->consumed = false; return reset(); }InputPublisher的成員變量mChannel就是指注冊在InputDispatcher中的Server端InputChannel了。我們知道,這個InputChannel除了擁有一個反向管道的讀端文件描述符和一個前向管道的寫端文件描述符之后,還有一個匿名共享文件描述符,這個匿名共享文件描述符就是用來創(chuàng)建匿名共享內(nèi)存mSharedMessage的了。
這個匿名共享內(nèi)存mSharedMessage的作用是什么呢?原來,在InputChannel中,前向管道和反向管道的作用只是用來在Server端和Client端之間相互通知有事件發(fā)生了,但是具體是什么樣的事件,還需要去讀取這個匿名共享內(nèi)存的內(nèi)容才知道。前面我們在分析應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 14中(InputChannel.openInputChannelPair)創(chuàng)建Server端和Client端的InputChannel對時,創(chuàng)建一個匿名共享內(nèi)存,這個匿名共享內(nèi)存有兩個文件描述符同時指向它,其中一個放在Server端的InputChannel中,另外一個放在Client端的InputChannel中。這樣,當(dāng)InputDispatcher通過Server端的InputChannel的前向管道來通知Client端有鍵盤事件發(fā)生時,Client端只要通過它的InputChannel中的匿名共享內(nèi)存文件描述符去讀取匿名共享內(nèi)存中的內(nèi)容,就可以知道發(fā)生了什么事情了。有關(guān)匿名共享內(nèi)存的相關(guān)知識,請參考Android系統(tǒng)匿名共享內(nèi)存Ashmem(Anonymous Shared Memory)簡要介紹和學(xué)習(xí)計(jì)劃一文。
回到Step 17中,接下來就是調(diào)用InputPublisher的成員函數(shù)sendDispatchSignal來通知Activity窗口處理鍵盤事件了。
Step 19. InputPublishe.sendDispatchSignal
這個函數(shù)定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputPublisher::sendDispatchSignal() { ...... mWasDispatched = true; return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH); }這個函數(shù)很簡單,它通過調(diào)用內(nèi)部成員變量mChannel的sendSignal函數(shù)來通知相應(yīng)的Activity窗口來處理鍵盤事件。
Step 20. InputChannel.sendSignal
這個函數(shù)定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputChannel::sendSignal(char signal) { ssize_t nWrite; do { nWrite = ::write(mSendPipeFd, & signal, 1); } while (nWrite == -1 && errno == EINTR); if (nWrite == 1) { ...... return OK; } return -errno; }這里所謂的發(fā)送信號通知,其實(shí)是通過向其內(nèi)部一個管道的寫端寫入一個字符來實(shí)現(xiàn)的。前面我們分析應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 21中(NativeInputQueue.registerInputChannel),它把一個InputChannel注冊到應(yīng)用程序主線程中的Looper對象中,然后應(yīng)用程序的主線程就通過這個Looper對象睡眠等待在這個InputChannel中的前向管道中有新的內(nèi)容可讀了,這里的mSendPipeFd就是對應(yīng)這個前向管道的寫端?,F(xiàn)在既然向這個前向管道的寫端寫入新的內(nèi)容了,于是,應(yīng)用程序的主線程就被喚醒了。
在前面分析應(yīng)用程序注冊鍵盤消息接收通道過程的Step 21中,我們也說過,當(dāng)應(yīng)用程序的主線程因?yàn)檫@個InputChannel中的前向管道的寫端喚醒時,NativeInputQueue的成員函數(shù)handleReceiveCallback就會被回調(diào),因此,接下來,應(yīng)用程序的主線程就會被喚醒,然后執(zhí)行NativeInputQueue的成員函數(shù)handleReceiveCallback。
Step 21. NativeInputQueue.handleReceiveCallback
這個函數(shù)定義在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) { NativeInputQueue* q = static_cast<NativeInputQueue*>(data); JNIEnv* env = AndroidRuntime::getJNIEnv(); sp<Connection> connection; InputEvent* inputEvent; jobject inputHandlerObjLocal; jlong finishedToken; { // acquire lock AutoMutex _l(q->mLock); ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex); ...... status_t status = connection->inputConsumer.receiveDispatchSignal(); if (status) { ...... return 0; // remove the callback } ...... status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent); ...... finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum); inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal); } // release lock ...... int32_t inputEventType = inputEvent->getType(); jobject inputEventObj; jmethodID dispatchMethodId; switch (inputEventType) { case AINPUT_EVENT_TYPE_KEY: ...... inputEventObj = android_view_KeyEvent_fromNative(env, static_cast<KeyEvent*>(inputEvent)); dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent; break; } ...... } ...... env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, dispatchMethodId, inputHandlerObjLocal, inputEventObj, jlong(finishedToken)); ...... return 1; }
這個函數(shù)首先是通過參數(shù)data獲得當(dāng)初注冊InputChannel的NativeInputQueue對象,具體可以參考前面介紹的應(yīng)用程序注冊鍵盤消息接收通道過程的Step 21。接下來再通過參數(shù)receiveFd獲得保存在這個NativeInputQueue對象中的mConnectionsByReceiveFd成員變量中的Connection對象。有了這個Connection對象后,就可以獲得它內(nèi)部的InputConsumer對象,這個InputConsumer對象是和上面的Step 18中介紹的InputPublisher對象相應(yīng)的。
在InputChannel內(nèi)部中,分別有一個InputPublisher對象和一個InputConsumer對象,對于Server端的InputChannel來說,它使用的是InputPublisher對象,通過它進(jìn)行鍵盤消息的分發(fā),而對于Client端的InputChannel來說,它使用的是InputConsumer對象,通過它進(jìn)行鍵盤消息的讀取。
獲得了這個InputConsumer對象后首先是調(diào)用它的receiveDispatchSignal來確認(rèn)是否是接收到了鍵盤消息的通知,如果是的話,再調(diào)用它的consume函數(shù)來把鍵盤事件讀取出來,最后,調(diào)用Java層的回調(diào)對象InputQueue的DispatchKeyEvent來處理這個鍵盤事件。下面,我們就依次來分析這些過程。
Step 22. InputConsumer.receiveDispatchSignal
這個函數(shù)定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputConsumer::receiveDispatchSignal() { ...... char signal; status_t result = mChannel->receiveSignal(& signal); if (result) { return result; } if (signal != INPUT_SIGNAL_DISPATCH) { ...... return UNKNOWN_ERROR; } return OK; }這個函數(shù)很簡單,它通過它內(nèi)部對象mChannel來從前向管道的讀端讀入一個字符,看看是否是前面的Step 20中寫入的INPUT_SIGNAL_DISPATCH字符。
InputChannel類的receiveSignal函數(shù)也是定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputChannel::receiveSignal(char* outSignal) { ssize_t nRead; do { nRead = ::read(mReceivePipeFd, outSignal, 1); } while (nRead == -1 && errno == EINTR); if (nRead == 1) { ...... return OK; } ...... return -errno; }Step 23. InputConsumer.consume
這個函數(shù)定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) { ...... *outEvent = NULL; int ashmemFd = mChannel->getAshmemFd(); int result = ashmem_pin_region(ashmemFd, 0, 0); ...... if (mSharedMessage->consumed) { ...... return INVALID_OPERATION; } // Acquire but *never release* the semaphore. Contention on the semaphore is used to signal // to the publisher that the message has been consumed (or is in the process of being // consumed). Eventually the publisher will reinitialize the semaphore for the next message. result = sem_wait(& mSharedMessage->semaphore); ...... mSharedMessage->consumed = true; switch (mSharedMessage->type) { case AINPUT_EVENT_TYPE_KEY: { KeyEvent* keyEvent = factory->createKeyEvent(); if (! keyEvent) return NO_MEMORY; populateKeyEvent(keyEvent); *outEvent = keyEvent; break; } ...... } return OK; }這個函數(shù)很簡單,只要對照前面的Step 18(InputPublisher.publishKeyEvent)來邏輯來看就可以了,后者是往匿名共享內(nèi)存中寫入鍵盤事件,前者是從這個匿名共享內(nèi)存中把這個鍵盤事件的內(nèi)容讀取出來。
回到Step 21中的handleReceiveCallback函數(shù)中,從InputConsumer中獲得了鍵盤事件的內(nèi)容(保存在本地變量inputEvent中)后,就開始要通知Java層的應(yīng)用程序了。在前面分析應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 21中(NativeInputQueue.registerInputChannel),會把傳進(jìn)來的對象inputHandlerObj保存在Connection對象中:
connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);這個inputHandlerObj對象的類型為Java層的InputHandler對象,因此,這里首先把它取回來:
inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal);取回來之后,我們要把作為參數(shù)來調(diào)用InputQueue類的dispatchKeyEvent靜態(tài)成員函數(shù)來通知應(yīng)用程序,有鍵盤事件發(fā)生了,因此,先找到InputQueue類的靜態(tài)成員函數(shù)dispatchKeyEvent的ID:
dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;在回調(diào)用這個InputQueue類的dispatchKeyEvent靜態(tài)成員函數(shù)之前,還要把前面獲得的inputEvent對象轉(zhuǎn)換成Java層的KeyEvent對象:
inputEventObj = android_view_KeyEvent_fromNative(env, static_cast<KeyEvent*>(inputEvent));萬事具備了,就可以通知Java層的InputQueue來處理這個鍵盤事件了:
env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, dispatchMethodId, inputHandlerObjLocal, inputEventObj, jlong(finishedToken));Step 24. InputQueue.dispatchKeyEvent
這個函數(shù)定義在frameworks/base/core/java/android/view/InputQueue.java文件中:
public final class InputQueue { ...... private static void dispatchKeyEvent(InputHandler inputHandler, KeyEvent event, long finishedToken) { Runnable finishedCallback = FinishedCallback.obtain(finishedToken); inputHandler.handleKey(event, finishedCallback); } ...... }這個函數(shù)首先會創(chuàng)建一個FinishedCallback類型的對象finishedCallback,F(xiàn)inishedCallback是InputQueue的一個內(nèi)部類,它繼承于Runnable類。這個finishedCallback對象是提供給當(dāng)前Activity窗口的,當(dāng)它處理完畢鍵盤事件后,需要通過消息分發(fā)的方式來回調(diào)這個finishedCallback對象,以及InputQueue類處理一個手尾的工作,后面我們會分析到。
這里的inputHandler對象是在前面分析應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 1(ViewRoot.setView)中傳進(jìn)來的:
InputQueue.registerInputChannel(mInputChannel, mInputHandler, Looper.myQueue());它是ViewRoot類的一個成員變量mInputHandler。因此,這里將調(diào)用ViewRoot類的內(nèi)部對象mInputHandler的成員函數(shù)handleKey來處理鍵盤事件。
Step 25. InputHandler.handleKey
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... private final InputHandler mInputHandler = new InputHandler() { public void handleKey(KeyEvent event, Runnable finishedCallback) { startInputEvent(finishedCallback); dispatchKey(event, true); } ...... }; ...... }這個函數(shù)首先調(diào)用其外部類ViewRoot的startInputEvent成員函數(shù)來把回調(diào)對象finishedCallback保存下來:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... private void startInputEvent(Runnable finishedCallback) { ...... mFinishedCallback = finishedCallback; } ...... }然后再調(diào)用其外部類ViewRoot的dispatchKey成員函數(shù)來進(jìn)一步處這個鍵盤事件。
Step 26. ViewRoot.dispatchKey
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... private void dispatchKey(KeyEvent event, boolean sendDone) { ...... Message msg = obtainMessage(DISPATCH_KEY); msg.obj = event; msg.arg1 = sendDone ? 1 : 0; ...... sendMessageAtTime(msg, event.getEventTime()); } ...... }ViewRoot不是直接處理這個鍵盤事件,而是把作為一個消息(DISPATCH_KEY)它放到消息隊(duì)列中去處理,這個消息最后由ViewRoot類的deliverKeyEvent成員函數(shù)來處理。
Step 27. ViewRoot.deliverKeyEvent
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... private void deliverKeyEvent(KeyEvent event, boolean sendDone) { // If mView is null, we just consume the key event because it doesn't // make sense to do anything else with it. boolean handled = mView != null ? mView.dispatchKeyEventPreIme(event) : true; ...... // If it is possible for this window to interact with the input // method window, then we want to first dispatch our key events // to the input method. if (mLastWasImTarget) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null && mView != null) { ...... imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback); return; } } ...... } ...... }ViewRoot在把這個鍵盤事件分發(fā)給當(dāng)前激活的Activity窗口處理之前,首先會調(diào)用InputMethodManager的dispatchKeyEvent成員函數(shù)來處理這個鍵盤事件。InputMethodManager處理完這個鍵盤事件后,再回調(diào)用這里的mInputMethodCallback對象的finishedEvent成員函數(shù)來把鍵盤事件分發(fā)給當(dāng)前激活的Activity窗口處理。當(dāng)然,在把這個鍵盤事件分發(fā)給InputMethodManager處理之前,ViewRoot也會先把這個鍵盤事件分發(fā)給當(dāng)前激活的Activity窗口的dispatchKeyEventPreIme成員函數(shù)處理。
Step 28. InputMethodManager.dispatchKeyEvent
這個函數(shù)定義在frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java文件中。這是一個輸入法相關(guān)的類,我們這里就不關(guān)注了,只要知道當(dāng)輸入法處理完成之后,它就會調(diào)用ViewRoot類的mInputMehtodCallback對象的finishedEvent成員函數(shù)。
Step 29. InputMethodCallack.finishedEvent
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... static class InputMethodCallback extends IInputMethodCallback.Stub { private WeakReference<ViewRoot> mViewRoot; public InputMethodCallback(ViewRoot viewRoot) { mViewRoot = new WeakReference<ViewRoot>(viewRoot); } public void finishedEvent(int seq, boolean handled) { final ViewRoot viewRoot = mViewRoot.get(); if (viewRoot != null) { viewRoot.dispatchFinishedEvent(seq, handled); } } ...... } ...... }這個函數(shù)最終調(diào)用ViewRoot的dispatchFinishedEvent來進(jìn)一步處理。
Step 30. ViewRoot.dispatchFinishedEvent
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... public void dispatchFinishedEvent(int seq, boolean handled) { Message msg = obtainMessage(FINISHED_EVENT); msg.arg1 = seq; msg.arg2 = handled ? 1 : 0; sendMessage(msg); } ...... }和前面的Step 26一樣,ViewRoot不是直接處理這個鍵盤事件,而是把它作為一個消息(FINISHED_EVENT)放在消息隊(duì)列中去,最后,這個消息由ViewRoot的handleFinishedEvent函數(shù)來處理。
Step 31. ViewRoot.handleFinishedEvent
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... void handleFinishedEvent(int seq, boolean handled) { final KeyEvent event = (KeyEvent)retrievePendingEvent(seq); ...... if (event != null) { final boolean sendDone = seq >= 0; if (!handled) { deliverKeyEventToViewHierarchy(event, sendDone); return; } else if (sendDone) { ...... } else { ...... } } } ...... }如果InputMethodManager沒有處理這個鍵盤事件,那么ViewRoot就會調(diào)用deliverKeyEventToViewHierarchy函數(shù)來把這個鍵盤事件分發(fā)給當(dāng)前激活的Activity窗口來處理。
Step 32. ViewRoot.deliverKeyEventToViewHierarchy
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... private void deliverKeyEventToViewHierarchy(KeyEvent event, boolean sendDone) { try { if (mView != null && mAdded) { ...... boolean keyHandled = mView.dispatchKeyEvent(event); } ...... } finally { if (sendDone) { finishInputEvent(); } } } ...... }這個函數(shù)首先會調(diào)用ViewRoot類的成員變量mView的dispatchKeyEvent來處理這個鍵盤事件,然后最調(diào)用ViewRoot類的finishInputEvent來處理手尾工作。
ViewRoot類的成員變量mView的類型為DecorView,它是由ActivityThread類第一次Resume當(dāng)前的Activity窗口時創(chuàng)建的,具體可以參考ActivityThread類的handleResumeActivity成員函數(shù),這里就不關(guān)注了。
Step 33. DecorView.dispatchKeyEvent
這個函數(shù)定義在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java文件中,它是PhoneWindow類的一個內(nèi)部類:
public class PhoneWindow extends Window implements MenuBuilder.Callback { ...... private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { ...... @Override public boolean dispatchKeyEvent(KeyEvent event) { ...... final Callback cb = getCallback(); final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event); ...... } ...... } ...... }這里通過getCallback函數(shù)返回的是當(dāng)前應(yīng)用程序的激活的Activity窗口的Window.Callback接口,一般它不為NULL,因此,這個函數(shù)會調(diào)用Activity類的dispatchKeyEvent來處理這個鍵盤事件。
Step 34. Activity.dispatchKeyEvent
這個函數(shù)定義在frameworks/base/core/java/android/app/Activity.java文件中:
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks { ...... public boolean dispatchKeyEvent(KeyEvent event) { ...... View decor = mDecor; if (decor == null) decor = win.getDecorView(); return event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this); } ...... }這里,Activity不是直接處理這個鍵盤事件,而是通過KeyEvent的dispatch轉(zhuǎn)發(fā)一下。注意,KeyEvent的成中函數(shù)dispatch的第一個參數(shù)的類型是KeyEvent.Callback,而Activity實(shí)現(xiàn)了這個接口,因此,這里可以傳this引用過去。
Step 35. KeyEvent.dispatch
這個函數(shù)定義在frameworks/base/core/java/android/view/KeyEvent.java文件中:
public class KeyEvent extends InputEvent implements Parcelable { ...... public final boolean dispatch(Callback receiver, DispatcherState state, Object target) { switch (mAction) { case ACTION_DOWN: { ...... boolean res = receiver.onKeyDown(mKeyCode, this); ...... return res; } case ACTION_UP: ...... return receiver.onKeyUp(mKeyCode, this); case ACTION_MULTIPLE: final int count = mRepeatCount; final int code = mKeyCode; if (receiver.onKeyMultiple(code, count, this)) { return true; } ...... return false; } return false; } ...... }這里就根據(jù)一個鍵是按下(ACTION_DOWN)、還是松開(ACTION_UP)或者是一個相同的鍵被多次按下和松開(ACTION_MULTIPLE)等不同事件類型來分別調(diào)用Activity的onKeyDown、onKeyUp和onKeyMultiple函數(shù)了。
Activity窗口處理完這個鍵盤事件后,層層返回,最后回到Step 32中,調(diào)用finishInputEvent事件來處理一些手尾工,下面我們將會看到這些手尾工是什么。
Step 36. ViewRoot.finishInputEvent
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... private void finishInputEvent() { ...... if (mFinishedCallback != null) { mFinishedCallback.run(); mFinishedCallback = null; } else { ...... } } ...... }
ViewRoot類里面的成員變量mFinishedCallback是在前面Step 25中由InputQueue設(shè)置的,它是一個Runnable對象,實(shí)際類型是定義在InputQueue的內(nèi)部類FinishedCallback,因此,這里調(diào)用它的run方法時,接下來就會調(diào)用InputQueue的內(nèi)部類FinishedCallback的run成員函數(shù):
public final class InputQueue { ...... private static class FinishedCallback implements Runnable { ...... public void run() { synchronized (sLock) { ...... nativeFinished(mFinishedToken); ...... } } ...... } ...... }這里它調(diào)用外部類InputQueue的本地方法nativeFinished來進(jìn)一步處理。
Step 37. InputQueue.nativeFinished
這個函數(shù)定義在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz, jlong finishedToken) { status_t status = gNativeInputQueue.finished( env, finishedToken, false /*ignoreSpuriousFinish*/); ...... }這個函數(shù)只是簡單只調(diào)用NativeInputQueue的finished方法來進(jìn)一處處理。
Step 38. NativeInputQueue.finished
這個函數(shù)定義在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish) { int32_t receiveFd; uint16_t connectionId; uint16_t messageSeqNum; parseFinishedToken(finishedToken, &receiveFd, &connectionId, &messageSeqNum); { // acquire lock AutoMutex _l(mLock); ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); ...... connection->messageInProgress = false; status_t status = connection->inputConsumer.sendFinishedSignal(); ...... } // release lock return OK; }這個函數(shù)最重要的參數(shù)便是finishedToken了,通過它可以獲得之前通知Java層的InputQueue類來處理鍵盤事件的Connection對象,它的值是在上面的Step 21(NativeInputQueue.handleReceiveCallback)中生成的:
finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum);函數(shù)generateFinishedToken的定義如下:
jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId, uint16_t messageSeqNum) { return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum); }它的實(shí)現(xiàn)很簡單,只是把receiveFd(前向管道的讀端文件描述符)、connectionId(Client端的InputChannel對應(yīng)的Connection對象在NativeInputQueue中的索引)和messageSeqNum(鍵盤消息的序列號)三個數(shù)值通過移位的方式編碼在一個jlong值里面,即編碼在上面的finishedToken參數(shù)里面。
因此,在上面的finished函數(shù)里面,首先就是要對參數(shù)值finishedToken進(jìn)行解碼,把receiveFd、connectionId和messageSeqNum三個值分別取回來:
parseFinishedToken(finishedToken, &receiveFd, &connectionId, &messageSeqNum);parseFinishedToken的定義如下:
void NativeInputQueue::parseFinishedToken(jlong finishedToken, int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex) { *outReceiveFd = int32_t(finishedToken >> 32); *outConnectionId = uint16_t(finishedToken >> 16); *outMessageIndex = uint16_t(finishedToken); }有了這個receiveFd和connectionId之后,就可以把相應(yīng)的Connection對象取回來了:
ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);接下來就是調(diào)用這個connection對象中的inputConsumer對象來發(fā)送信號通知Server端的InputChannel,應(yīng)用程序這一側(cè)處理完剛才發(fā)生的鍵盤事件了:
status_t status = connection->inputConsumer.sendFinishedSignal();
Step 39. InputConsumer.sendFinishedSignal
這個函數(shù)定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputConsumer::sendFinishedSignal() { ...... return mChannel->sendSignal(INPUT_SIGNAL_FINISHED); }這個函數(shù)的實(shí)現(xiàn)很簡單,只是調(diào)用其內(nèi)部對象mChannel的sendSignal函數(shù)來執(zhí)行發(fā)送信號的通知。前面我們已經(jīng)說過,這里的mChannel的類型為InputChannel,它是注冊在應(yīng)用程序一側(cè)的Client端InputChannel,它的成員函數(shù)sendSignal的定義我們在上面的Step 20中已經(jīng)分析過了,這里不再詳述,不過,這里和上面Step 20不一樣的地方是,它里的通知方向是從反向管道的寫端(在應(yīng)用程序這一側(cè))到反向管道的讀端(在InputDispatcher這一側(cè))。
前面我們在分析應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 18(InputDispatcher.registerInputChannel)中,說到InputDispatcher把一個反向管道的讀端文件描述符添加到WindowManagerService所運(yùn)行的線程中的Looper對象中去,然后就會在這個反向管道的讀端上睡眠等待有這個管道有新的內(nèi)容可讀?,F(xiàn)在,InputConsumer往這個反向管道寫入新的內(nèi)容了,于是,InputDispatcher就被喚醒過來了,喚醒過來后,它所調(diào)用的函數(shù)是InputDispatcher.handleReceiveCallback函數(shù),這與前面的Step 21的邏輯是一樣的。
Step 40. InputDispatcher.handleReceiveCallack
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) { InputDispatcher* d = static_cast<InputDispatcher*>(data); { // acquire lock AutoMutex _l(d->mLock); ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... nsecs_t currentTime = now(); sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex); ...... status_t status = connection->inputPublisher.receiveFinishedSignal(); if (status) { ...... return 0; // remove the callback } d->finishDispatchCycleLocked(currentTime, connection); ...... return 1; } // release lock }這個函數(shù)首先是通過傳進(jìn)來的receiveFd參數(shù)(反向管道的讀端文件描述符)的值取得相應(yīng)的Connection對象:
ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);然后通過調(diào)用這個connection對象的內(nèi)部對象inputPublisher的receiveFinishedSignal函數(shù)來確認(rèn)是否真的收到鍵盤事件處理完成的信號,確認(rèn)之后,就會調(diào)用InputDispatcher對象d的finishDispatchCycleLocked函數(shù)來執(zhí)行一些善后工作。下面我們就依次分析這兩個過程。
Step 41. InputPublisher.receiverFinishedSignal
這個函數(shù)定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputPublisher::receiveFinishedSignal() { .... char signal; status_t result = mChannel->receiveSignal(& signal); if (result) { return result; } if (signal != INPUT_SIGNAL_FINISHED) { ....... return UNKNOWN_ERROR; } return OK; }這里的邏輯和前面的Step 22中NativeInputQueue確認(rèn)是否真的收到鍵盤事件分發(fā)的信號的邏輯是一致的,都是通過InputChannel的receiveSignal函數(shù)來確認(rèn)是否在管道中收到了某一個約定的字符值,不過,這里約定的字符值為INPUT_SIGNAL_FINISHED。
回到前面的Step 40中,確認(rèn)了是真的收到了鍵盤事件處理完成的信號后,就調(diào)用InputDispatcher的finishDispatchCycleLocked函數(shù)來執(zhí)行一些善后工作了。
Step 42. InputDispatcher.finishDispatchCycleLocked
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection) { ...... // Notify other system components. onDispatchCycleFinishedLocked(currentTime, connection); // Reset the publisher since the event has been consumed. // We do this now so that the publisher can release some of its internal resources // while waiting for the next dispatch cycle to begin. status_t status = connection->inputPublisher.reset(); ...... startNextDispatchCycleLocked(currentTime, connection); }這個函數(shù)主要就是做了三件事情:
一是通知其它系統(tǒng),InputDispatcher完成了一次鍵盤事件的處理:
// Notify other system components. onDispatchCycleFinishedLocked(currentTime, connection);二是調(diào)用相應(yīng)的connection對象的內(nèi)部對象inputPublisher來的reset函數(shù)來回收一些資源,它里面其實(shí)就是釋放前面在Step 18(InputPublisher.publishKeyEvent)使用的匿名共享內(nèi)存了:
// Reset the publisher since the event has been consumed. // We do this now so that the publisher can release some of its internal resources // while waiting for the next dispatch cycle to begin. status_t status = connection->inputPublisher.reset();三是調(diào)用InputDispatcher的startNextDispatchCycleLocked函數(shù)來處理下一個鍵盤事件:
startNextDispatchCycleLocked(currentTime, connection);因?yàn)檎谔幚懋?dāng)前這個鍵盤事件的時候,很有可能又同時發(fā)生了其它的鍵盤事件,因此,這里InputDispatcher還不能停下來,需要繼續(xù)調(diào)用startNextDispatchCycleLocked繼續(xù)處理鍵盤事件,不過下一個鍵盤事件的處理過程和我們現(xiàn)在分析的過程就是一樣的了。
至此,InputManager分發(fā)鍵盤消息給應(yīng)用程序的過程就分析完成了,這是一個比較復(fù)雜的過程,不過,只要我們抓住主要的線索,就不難理解了,現(xiàn)在我們就小結(jié)一下這個過程的四個主要線索:
A. 鍵盤事件發(fā)生,InputManager中的InputReader被喚醒,此前InputReader睡眠在/dev/input/event0這個設(shè)備文件上;
B. InputReader被喚醒后,它接著喚醒InputManager中的InputDispatcher,此前InputDispatcher睡眠在InputManager所運(yùn)行的線程中的Looper對象里面的管道的讀端上;
C. InputDispatcher被喚醒后,它接著喚醒應(yīng)用程序的主線程來處理這個鍵盤事件,此前應(yīng)用程序的主線程睡眠在Client端InputChannel中的前向管道的讀端上;
D. 應(yīng)用程序處理處理鍵盤事件之后,它接著喚醒InputDispatcher來執(zhí)行善后工作,此前InputDispatcher睡眠在Server端InputChannel的反向管道的讀端上,注意這里與第二個線索處的區(qū)別。
4. 應(yīng)用程序注銷鍵盤消息接收通道的過程分析
當(dāng)Activity窗口創(chuàng)建時,它會向InputManager注冊鍵盤消息接收通道,而當(dāng)Activity窗口銷毀時,它就會向InputManager注銷前面注冊的鍵盤消息接收通道了,本節(jié)內(nèi)容就來看看應(yīng)用程序注銷鍵盤消息接收通道的過程。
當(dāng)我們按下鍵盤上的Back鍵時,當(dāng)前激活的Activity窗口就會被失去焦點(diǎn),但是這時候它還沒有被銷毀,它的狀態(tài)被設(shè)置為Stopped;當(dāng)新的Activity窗口即將要顯示時,它會通知WindowManagerService,這時候WindowManagerService就會處理當(dāng)前處理Stopped狀態(tài)的Activity窗口了,要執(zhí)行的操作就是銷毀它們了,在銷毀的時候,就會注銷它們之前所注冊的鍵盤消息接收通道。
新的Activity窗口通知WindowManagerService它即將要顯示的過程比較復(fù)雜,但是它與我們本節(jié)要介紹的內(nèi)容不是很相關(guān),因此,這里就略過大部過程了,我們從ActvitiyRecord的windowsVisible函數(shù)開始分析。注意,這里的ActivityRecord是新的Activity窗口在ActivityManangerService的代表,而那些處于Stopped狀態(tài)的Activity窗口
會放在ActivityStack類的一個等待可見的mWaitingVisibleActivities列表里面,事實(shí)于,對于那些Stopped狀態(tài)的Activity窗口來說,它們是等待銷毀,而不是等待可見。
像前面一樣,我們先來看一張應(yīng)用程序注銷鍵盤消息接收通道的過程的序列圖,然后根據(jù)這個序列圖來詳細(xì)分析互一個步驟:
Step 1. ActivityRecord.windowsVisible
這個函數(shù)定義在frameworks/base/services/java/com/android/server/am/ActivityRecord.java文件中:
class ActivityRecord extends IApplicationToken.Stub { ...... boolean nowVisible; // is this activity's window visible? boolean idle; // has the activity gone idle? ...... public void windowsVisible() { synchronized(service) { ...... if (!nowVisible) { nowVisible = true; if (!idle) { ....... } else { // If this activity was already idle, then we now need to // make sure we perform the full stop of any activities // that are waiting to do so. This is because we won't // do that while they are still waiting for this one to // become visible. final int N = stack.mWaitingVisibleActivities.size(); if (N > 0) { for (int i=0; i<N; i++) { ActivityRecord r = (ActivityRecord) stack.mWaitingVisibleActivities.get(i); r.waitingVisible = false; ...... } stack.mWaitingVisibleActivities.clear(); Message msg = Message.obtain(); msg.what = ActivityStack.IDLE_NOW_MSG; stack.mHandler.sendMessage(msg); } } ...... } } } ...... }應(yīng)用程序中的每一個Activity在ActivityManagerService都有一個代表ActivityRecord,它們以堆棧的形式組織在ActivityManaerService中的ActivityStack中。一個即將要顯示,但是還沒有顯示的Activity,它在ActivityManagerService中的ActivityRecord的成員變量nowVisible為false,而成員變量idle為ture,表示這個即將要顯示的Activity窗口處于空閑狀態(tài)。因此,在上面的這個函數(shù)中,會執(zhí)行下面的語句:
final int N = stack.mWaitingVisibleActivities.size(); if (N > 0) { for (int i=0; i<N; i++) { ActivityRecord r = (ActivityRecord) stack.mWaitingVisibleActivities.get(i); r.waitingVisible = false; ...... } stack.mWaitingVisibleActivities.clear(); Message msg = Message.obtain(); msg.what = ActivityStack.IDLE_NOW_MSG; stack.mHandler.sendMessage(msg); }前面我們說過,當(dāng)用戶按下鍵盤上的Back鍵時,當(dāng)前激活的Activity記錄就被放在ActivityStack對象stack的成員變量mWaitingVisibleActivities中了,這時候就要對它進(jìn)行處理了。首先是將它們的Activity記錄的waitingVisible設(shè)置為false,然后就把它們從ActivityStack對象stack的成員變量mWaitingVisibleActivities清空,最后向ActivityStack對象stack發(fā)送一個ActivityStack.IDLE_NOW_MSG消息。這個消息最終是由ActivityStack類的activityIdleInternal函數(shù)來處理的。
Step 2. ActivityStack.activityIdleInternal
這個函數(shù)定義在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中:
public class ActivityStack { ...... final void activityIdleInternal(IBinder token, boolean fromTimeout, Configuration config) { ...... ArrayList<ActivityRecord> stops = null; ...... int NS = 0; ...... synchronized (mService) { ...... // Atomically retrieve all of the other things to do. stops = processStoppingActivitiesLocked(true); NS = stops != null ? stops.size() : 0; ...... } int i; ...... // Stop any activities that are scheduled to do so but have been // waiting for the next one to start. for (i=0; i<NS; i++) { ActivityRecord r = (ActivityRecord)stops.get(i); synchronized (mService) { if (r.finishing) { finishCurrentActivityLocked(r, FINISH_IMMEDIATELY); } else { ...... } } } ...... } ...... }這個函數(shù)首先會調(diào)用processStoppingActivitiesLocked函數(shù)把所有處于Stopped狀態(tài)的Activity取回來,然后逐個分析它們,如果它們的ActivityRecord中的finishing成員變量為true,就說明這個Activity需要銷毀了,于是,就調(diào)用finishCurrentActivityLocked函數(shù)來銷毀它們。
Step 3. ActivityStack.finishCurrentActivityLocked
這個函數(shù)定義在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中:
public class ActivityStack { ...... private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode) { ...... return finishCurrentActivityLocked(r, index, mode); } private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int index, int mode) { ...... // make sure the record is cleaned out of other places. mStoppingActivities.remove(r); mWaitingVisibleActivities.remove(r); ...... final ActivityState prevState = r.state; r.state = ActivityState.FINISHING; if (mode == FINISH_IMMEDIATELY || prevState == ActivityState.STOPPED || prevState == ActivityState.INITIALIZING) { // If this activity is already stopped, we can just finish // it right now. return destroyActivityLocked(r, true) ? null : r; } else { ...... } return r; } ...... }從上面的Step 2中傳進(jìn)來的參數(shù)mode為FINISH_IMMEDIATELY,并且這個即將要被銷毀的Activity的狀態(tài)為Stopped,因此,接下來就會調(diào)用destroyActivityLocked函數(shù)來銷毀它。
Step 4. ActivityStack.destroyActivityLocked
這個函數(shù)定義在frameworks/base/services/java/com/android/server/am/ActivityStack.java文件中:
public class ActivityStack { ...... final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp) { ...... boolean removedFromHistory = false; ...... final boolean hadApp = r.app != null; if (hadApp) { ...... try { ...... r.app.thread.scheduleDestroyActivity(r, r.finishing, r.configChangeFlags); } catch (Exception e) { ...... } ...... } else { ...... } ...... return removedFromHistory; } ...... }在前面一篇文章Android應(yīng)用程序啟動過程源代碼分析中,我們說到,每一個應(yīng)用程序進(jìn)程在ActivityManagerService中,都ProcessRecord記錄與之對應(yīng),而每一個Activity,都是運(yùn)行在一個進(jìn)程上下文中,因此,在ActivityManagerService中,每一個ActivityRecord的app成員變量都應(yīng)該指向一個ProcessRecord記錄,于是,這里得到的hadApp為true。在ProcessRecord類中,有一個成員變量thread,它的類型為IApplicationThread。在文章Android應(yīng)用程序啟動過程源代碼分析中,我們也曾經(jīng)說過,每一個應(yīng)用程序在啟動的時候,它都會在內(nèi)部創(chuàng)建一個ActivityThread對象,而在這個ActivityThread對象中,有一個成員變量mAppThread,它的類型為ApplicationThread,這是一個Binder對象,專門用來負(fù)責(zé)在應(yīng)用程序和ActivityManagerService之間執(zhí)行進(jìn)程間通信工作的。應(yīng)用程序在啟動的時候,就會將這個Binder對象傳遞給ActivityManagerService,而ActivityManagerService就會把它保存在相應(yīng)的ProcessRecord記錄的thread成員變量中。因此,ProcessRecord記錄的thread成員變量其實(shí)就是ApplicationThread對象的遠(yuǎn)程接口,于是,執(zhí)行下面這個語句的時候:
r.app.thread.scheduleDestroyActivity(r, r.finishing, r.configChangeFlags);就會進(jìn)入到ApplicationThread類中的scheduleDestroyActivity函數(shù)來。
Step 5. ApplicationThread.scheduleDestroyActivity
這個函數(shù)定義在frameworks/base/core/java/android/app/ActivityThread.java文件中:
public final class ActivityThread { ...... private final class ApplicationThread extends ApplicationThreadNative { ...... public final void scheduleDestroyActivity(IBinder token, boolean finishing, int configChanges) { queueOrSendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0, configChanges); } ...... } ...... }這個函數(shù)調(diào)用外部類ActivityThread的queueOrSendMessage函數(shù)來往應(yīng)用程序的消息隊(duì)列中發(fā)送一個H.DESTROY_ACTIVITY消息,這個消息最終由ActivityThread類的handleDestroyActivity函數(shù)來處理。
Step 6. ActivityThread.handleDestroyActivity
這個函數(shù)定義在frameworks/base/core/java/android/app/ActivityThread.java文件中:
public final class ActivityThread { ...... private final void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ...... ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); if (r != null) { WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; if (v != null) { ...... if (r.activity.mWindowAdded) { wm.removeViewImmediate(v); } ...... } ...... } ...... } ...... }這里首先調(diào)用performDestroyActivity來執(zhí)行一些銷毀Activity的操作,期間就會調(diào)用Activity的onDestroy函數(shù)讓Activity本身有機(jī)會執(zhí)行一些銷毀前的工作了。這里通過r.activity.getWindowManager函數(shù)返回的是一個LocalWindowManager對象,而通過r.activity.mDecor得到的是一個DecorView對象,這些都是在Activity啟動的時候設(shè)置好的。函數(shù)最后調(diào)用LocalWindowManager對象wm的removeViewImmediate函員來從LocalWindowManager移除這個DecorView對象。
Step 7. LocalWindowManager.removeViewImmediate
這個函數(shù)定義在frameworks/base/core/java/android/view/Window.java文件中:
public abstract class Window { ...... private class LocalWindowManager implements WindowManager { ...... public final void removeViewImmediate(View view) { mWindowManager.removeViewImmediate(view); } ...... private final WindowManager mWindowManager; } ...... }LocalWindowManager類的成員變量mWindowManager是一個WndowManagerImpl對象,這個函數(shù)只是簡單地調(diào)用WndowManagerImpl類的removeViewImmediate來進(jìn)一步處理。
Step 8. WndowManagerImpl.removeViewImmediate
這個函數(shù)定義在frameworks/base/core/java/android/view/WindowManagerImpl.java文件中:
public class WindowManagerImpl implements WindowManager { ...... public void removeViewImmediate(View view) { synchronized (this) { int index = findViewLocked(view, true); ViewRoot root = mRoots[index]; ...... root.die(true); ...... } } ...... }這個函數(shù)首先是找到這個view所屬的ViewRoot對象root,然后調(diào)用這個root對象的die函數(shù)來銷毀它。
Step 9. ViewRoot.die
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... public void die(boolean immediate) { if (immediate) { doDie(); } else { ...... } } ...... }上面Step 8傳進(jìn)來的immediate參數(shù)為true,因此,這里直接調(diào)用doDie函數(shù)來進(jìn)一步處理。
Step 10. ViewRoot.doDie
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... void doDie() { ...... synchronized (this) { ...... if (mAdded) { mAdded = false; dispatchDetachedFromWindow(); } } } ...... }當(dāng)我們把Activity窗口中的View添加到一個ViewRoot對象時,就會把它的成員變量mAdded設(shè)置為true,這樣就表示這個ViewRoot中有View存在,于是,這里就會調(diào)用dispatchDetachedFromWindow函數(shù)來進(jìn)一步處理。
Step 11. ViewRoot.ispatchDetachedFromWindow
這個函數(shù)定義在frameworks/base/core/java/android/view/ViewRoot.java文件中:
public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... void dispatchDetachedFromWindow() { ...... if (mInputChannel != null) { if (mInputQueueCallback != null) { ...... } else { InputQueue.unregisterInputChannel(mInputChannel); } } try { sWindowSession.remove(mWindow); } catch (RemoteException e) { } ...... } ...... }前面在介紹應(yīng)用程序注冊鍵盤消息接收通道的過程時,在Step 18,我們說到,ViewRoot類中的mInputQueueCallback為null,表示由這個ViewRoot自己來管理鍵盤輸入事件,因此,這里首先會調(diào)用InputQueue的unregisterInputChannel函數(shù)來注銷注冊在應(yīng)用程序這一側(cè)的Client端InputChannel,然后再調(diào)用sWindowSession的remove函數(shù)來注銷注冊在InputManager這一側(cè)的Server端InputChannel,這個邏輯是和前面介紹應(yīng)用程序注冊鍵盤消息接收通道的邏輯相對應(yīng)的,前面分別注冊了這兩個InputChannel,現(xiàn)在Activity要銷毀了,當(dāng)然就要把它們注銷了。
我們先來看注銷注冊在應(yīng)用程序這一側(cè)的Client端InputChannel,然后再回過頭來分析注銷注冊在InputManager這一側(cè)的Server端InputChannel。
Step 12. InputQueue.unregisterInputChannel
這個函數(shù)定義在frameworks/base/core/java/android/view/InputQueue.java文件中:
public final class InputQueue { ...... public static void unregisterInputChannel(InputChannel inputChannel) { ...... synchronized (sLock) { ...... nativeUnregisterInputChannel(inputChannel); } } ...... }這個函數(shù)只是簡單地調(diào)用本地方法nativeUnregisterInputChannel來執(zhí)行具體的操作。
Step 13. InputQueue.nativeUnregisterInputChannel
這個函數(shù)定義在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, jobject inputChannelObj) { status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj); ...... }這里調(diào)用NativeInputQueue的成員函數(shù)unregisterInputChannel來進(jìn)一步處理。
Step 14. NativeInputQueue.unregisterInputChannel
這個函數(shù)定義在frameworks/base/core/jni/android_view_InputQueue.cpp文件中:
status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) { sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); ...... { // acquire lock AutoMutex _l(mLock); ssize_t connectionIndex = getConnectionIndex(inputChannel); ...... sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); mConnectionsByReceiveFd.removeItemsAt(connectionIndex); connection->status = Connection::STATUS_ZOMBIE; connection->looper->removeFd(inputChannel->getReceivePipeFd()); env->DeleteGlobalRef(connection->inputHandlerObjGlobal); connection->inputHandlerObjGlobal = NULL; ...... } // release lock ...... return OK; }真正的注銷工作就是這里實(shí)現(xiàn)的了,讀者可以對照前面介紹應(yīng)用程序注冊鍵盤消息接收通道過程中的Step 21(NativeInputQueue.registerInputChannel)來分析,它首先是將在之前創(chuàng)建的Connection對象從NativeInputQueue中的mConnectionByReceiveFd向量中刪除:
ssize_t connectionIndex = getConnectionIndex(inputChannel); ...... sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); mConnectionsByReceiveFd.removeItemsAt(connectionIndex);然后再把這個Client端InputChannel的前向管道的讀端文件描述符從應(yīng)用程序主線程中的Looper對象中刪除:
connection->looper->removeFd(inputChannel->getReceivePipeFd());這樣,這個Activity窗口以后就不會接收到鍵盤事件了。
最后將Connection對象中的回調(diào)對象inputHandlerOjbGlobal對象刪除:
env->DeleteGlobalRef(connection->inputHandlerObjGlobal); connection->inputHandlerObjGlobal = NULL;回憶一下前面我們在分析InputManager分發(fā)鍵盤消息給應(yīng)用程序處理時,曾經(jīng)說到,每當(dāng)有鍵盤事件發(fā)生時,InputManager首先就會調(diào)用NativeInputQueue類的handleReceiveCallback函數(shù)。在這個handleReceiveCallback函數(shù)里面,NativeInputQueue會找到相應(yīng)的Connection對象,然后把它里面的內(nèi)部對象inputHandlerOjbGlobal作為參數(shù)來調(diào)用Java層的InputQueue類的dispatchKeyEvent函數(shù)來通知應(yīng)用程序,有鍵盤事件發(fā)生了。在InputQueue類的dispatchKeyEvent函數(shù)里面,就是通過這個inputHandlerOjbGlobal對象來直正通知到當(dāng)前激活的Activity窗口來處理這個鍵盤事件的。
注冊在應(yīng)用程序這一側(cè)的Client端InputChannel被注銷以后,回到前面的Step 11中,我們繼續(xù)分析注銷注冊在InputManager這一側(cè)的Server端InputChannel。
Step 15. WindowManagerService.Session.remove
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { ...... public void remove(IWindow window) { removeWindow(this, window); } ...... } ...... }
這個函數(shù)只是簡單地調(diào)用其外部類WindowManagerService的removeWindow函數(shù)來進(jìn)一步執(zhí)行操作。
Step 16. WindowManagerService.removeWindow
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... public void removeWindow(Session session, IWindow client) { synchronized(mWindowMap) { WindowState win = windowForClientLocked(session, client, false); if (win == null) { return; } removeWindowLocked(session, win); } } ...... }
回憶一下前面我們在分析應(yīng)用程序注冊鍵盤消息管道的過程時,在Step 11(WindowManagerService.addWindow)中,WindowManagerService為這個即將要激活的Activity窗口創(chuàng)建了一個WindowState對象win,創(chuàng)建的時候,使用了從ViewRoot中傳過來的兩個參數(shù),分別是一個Session對象session和一個IWindow對象client。
在這個函數(shù)中,ViewRoot傳過來的兩個參數(shù)session和client和上面說的兩個參數(shù)是一致的,因此,這個函數(shù)首先通過參數(shù)session和client得到一個WindowState對象win,然后調(diào)用removeWindowLocked來把它從WindowManagerService刪除。
Step 17. WindowManagerService.removeWindowLocked
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... public void removeWindowLocked(Session session, WindowState win) { ...... win.disposeInputChannel(); ...... } ...... }我們忽略了這個函數(shù)的其它邏輯,只關(guān)注注銷之前注冊的Server端InputChannel的邏輯,這里,注銷的操作就是調(diào)用win的disposeInputChannel進(jìn)行的了。
Step 18. WindowState.disposeInputChannel
這個函數(shù)定義在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中:
public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final class WindowState implements WindowManagerPolicy.WindowState { ...... void disposeInputChannel() { if (mInputChannel != null) { mInputManager.unregisterInputChannel(mInputChannel); mInputChannel.dispose(); mInputChannel = null; } } ...... } ...... }上面說到,在前面分析應(yīng)用程序注冊鍵盤消息管道的過程時,在Step 11(WindowManagerService.addWindow)中,為當(dāng)前這個Activity窗口創(chuàng)建了一個WindowState對象,接著創(chuàng)建了一個輸入管道后,把Server端的InputChannel保存了在這個WindowState對象的成員變量mInputChannel中,因此,這里,就可以把它取回來,然后調(diào)用mInputManager對象的unregisterInputChannel函數(shù)來把它注銷掉了。
Step 19. InputManager.unregisterInputChannel
這個函數(shù)定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager { ...... public void unregisterInputChannel(InputChannel inputChannel) { ...... nativeUnregisterInputChannel(inputChannel); } ...... }這個函數(shù)很簡單,它調(diào)用本地方法nativeUnregisterInputChannel來進(jìn)一步處理。
Step 20. InputManager.nativeUnregisterInputChannel
這個函數(shù)定義在frameworks/base/services/jni/com_android_server_InputManager.cpp文件中:
static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, jobject inputChannelObj) { ...... sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); ...... status_t status = gNativeInputManager->unregisterInputChannel(env, inputChannel); ...... }這個函數(shù)首先調(diào)用android_view_InputChannel_getInputChannel函數(shù)根據(jù)Java層的InputChannel對象找到C++層的InputChannel對象,然后調(diào)用NativeInputManager的unregisterInputChannel函數(shù)來執(zhí)行注銷的操作。
Step 21. NativeInputManager.unregisterInputChannel
這個函數(shù)定義在frameworks/base/services/jni/com_android_server_InputManager.cpp文件中:
status_t NativeInputManager::unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel) { ...... return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel); }這個函數(shù)與前面分析應(yīng)用程序注冊鍵盤消息通道的Step 17(NativeInputManager.registerInputChannel)相對應(yīng),主要是調(diào)用InputDispatcher對象的unregisterInputChannel函數(shù)來執(zhí)行真正注銷的操作。
Step 22. InputDispatcher.unregisterInputChannel
這個函數(shù)定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) { ...... { // acquire lock AutoMutex _l(mLock); ssize_t connectionIndex = getConnectionIndexLocked(inputChannel); ...... sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); mConnectionsByReceiveFd.removeItemsAt(connectionIndex); ...... mLooper->removeFd(inputChannel->getReceivePipeFd()); ..... } // release lock ...... return OK; }這一步與前面的Step 14注銷應(yīng)用程序一側(cè)的Client端InputChannel是差不多的,只不過這里是從InputDispatcher中把Server端的InputChannel注銷掉。首先是根據(jù)傳進(jìn)來的參數(shù)inputChannel找到它在InputDispatcher中對應(yīng)的Connection對象在mConnectionsByReceiveFd中的索引,然后把它從mConnectionsByReceiveFd中刪除:
ssize_t connectionIndex = getConnectionIndexLocked(inputChannel); ...... sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); mConnectionsByReceiveFd.removeItemsAt(connectionIndex);最后,還需要把這個InputChannel中的反向管道讀端文件描述符從InputDispatcher的內(nèi)部對象mLooper中刪除,因?yàn)檫@個文件描述符是在前面注冊Server端的InputChannel時加入到mLooper對象去的,具體可以參考上面分析應(yīng)用程序注冊鍵盤消息接收通道的過程中的Step 18(InputDispatcher.registerInputChannel)。
這樣, 應(yīng)用程序注銷鍵盤消息接收通道的過程就分析完成了,整個應(yīng)用程序鍵盤消息處理機(jī)制也分析完成了,這是一個比較復(fù)雜的過程,要完全理解它還需要花費(fèi)一些努力和時間,不過,理解了這個過程之后,對Android應(yīng)用程序框架層的理解就更進(jìn)一步了。