在Android系統中,鍵盤按鍵事件是由WindowManagerService服務來管理的,然后再以消息的形式來分發給應用程序處理,不過和普通消息不一樣,它是由硬件中斷觸發的;在上一篇文章《Android應用程序消息處理機制(Looper、Handler)分析》中,我們分析了Android應用程序的消息處理機制,本文將結合這種消息處理機制來詳細分析Android應用程序是如何獲得鍵盤按鍵消息的。
在系統啟動的時候,SystemServer會啟動窗口管理服務WindowManagerService,WindowManagerService在啟動的時候就會通過系統輸入管理器InputManager來總負責監控鍵盤消息。這些鍵盤消息一般都是分發給當前激活的Activity窗口來處理的,因此,當前激活的Activity窗口在創建的時候,會到WindowManagerService中去注冊一個接收鍵盤消息的通道,表明它要處理鍵盤消息,而當InputManager監控到有鍵盤消息時,就會分給給它處理。當當前激活的Activity窗口不再處于激活狀態時,它也會到WindowManagerService中去反注冊之前的鍵盤消息接收通道,這樣,InputManager就不會再把鍵盤消息分發給它來處理。
由于本文的內容比較多,在接下面的章節中,我們將分為五個部分來詳細描述Android應用程序獲得鍵盤按鍵消息的過程,每一個部分都是具體描述鍵盤消息處理過程中的一個過程。結合上面的鍵盤消息處理框架,這四個過程分別是InputManager的啟動過程、應用程序注冊鍵盤消息接收通道的過程、InputManager分發鍵盤消息給應用程序的過程以及應用程序注銷鍵盤消息接收通道的過程。為了更好地理解Android應用程序獲得鍵盤按鍵消息的整個過程,建議讀者首先閱讀Android應用程序消息處理機制(Looper、Handler)分析一文,理解了Android應用程序的消息處理機制后,就能很好的把握本文的內容。
1. InputManager的啟動過程分析
前面說過,Android系統的鍵盤事件是由InputManager來監控的,而InputManager是由窗口管理服務WindowManagerService來啟動的。
從前面一篇文章Android系統進程Zygote啟動過程的源代碼分析中,我們知道在Android系統中,Zygote進程負責啟動系統服務進程SystemServer,而系統服務進程SystemServer負責啟動系統中的各種關鍵服務,例如我們在前面兩篇文章Android應用程序安裝過程源代碼分析和Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析中提到的Package管理服務PackageManagerService和Activity管理服務ActivityManagerService。這里我們所討論的窗口管理服務WindowManagerService也是由SystemServer來啟動的,具體的啟動過程這里就不再詳述了,具體可以參考PackageManagerService和ActivityManagerService的啟動過程。
了解了WindowManagerService的啟動過程之后,我們就可以繼續分析InputManager的啟動過程了。我們先來看一下InputManager啟動過程的序列圖,然后根據這個序列圖來一步步分析它的啟動過程:

點擊查看大圖
Step 1. WindowManagerService.main
這個函數定義在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實例來執行全局唯一的WindowManagerService實例的啟動操作。這里調用WMThread實例thr的start成員函數時,會進入到WMThread實例thr的run函數中去。
Step 2. WMThread.run
這個函數定義在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);
......
}
}
......
}
這里執行的主要操作就是創建一個WindowManagerService實例,這樣會調用到WindowManagerService構造函數中去。
Step 3. WindowManagerService<init>
WindowManagerService類的構造函數定義在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();
......
}
......
}
這里我們只關心InputManager的創建過程,而忽略其它無關部分。首先是創建一個InputManager實例,然后再調用它的start成員函數來監控鍵盤事件。在創建InputManager實例的過程中,會執行一些初始化工作,因此,我們先進入到InputManager類的構造函數去看看,然后再回過頭來分析它的start成員函數。
Step 4. InputManager<init>@java
Java層的InputManager類的構造函數定義在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類的一些成員變量,然后調用init函數進一步執行初始化操作。
Step 5. InputManager.init
這個函數定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager {
......
private void init() {
Slog.i(TAG, "Initializing input manager");
nativeInit(mCallbacks);
}
......
}
函數init通過調用本地方法nativeInit來執行C++層的相關初始化操作。
Step 6. InputManager.nativeInit
這個函數定義在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.");
}
}
這個函數的作用是創建一個NativeInputManager實例,并保存在gNativeInputManager變量中。由于是第一次調用到這里,因此,gNativeInputManager為NULL,于是就會new一個NativeInputManager對象出來,這樣就會執行NativeInputManager類的構造函數來執其它的初始化操作。
Step 7. NativeInputManager<init>
NativeInputManager類的構造函數定義在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);
}
這里只要是創建了一個EventHub實例,并且把這個EventHub作為參數來創建InputManager對象。注意,這里的InputManager類是定義在C++層的,和前面在Java層的InputManager不一樣,不過它們是對應關系。EventHub類是真正執行監控鍵盤事件操作的地方,后面我們會進一步分析到,現在我們主要關心InputManager實例的創建過程,它會InputManager類的構造函數里面執行一些初始化操作。
Step 8. InputManager<init>@C++
C++層的InputManager類的構造函數定義在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();
}
這里主要是創建了一個InputDispatcher對象和一個InputReader對象,并且分別保存在成員變量mDispatcher和mReader中。InputDispatcher類是負責把鍵盤消息分發給當前激活的Activity窗口的,而InputReader類則是通過EventHub類來實現讀取鍵盤事件的,后面我們會進一步分析。創建了這兩個對象后,還要調用initialize函數來執行其它的初始化操作。
Step 9. InputManager.initialize
這個函數定義在frameworks/base/libs/ui/InputManager.cpp 文件中:
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
這個函數創建了一個InputReaderThread線程實例和一個InputDispatcherThread線程實例,并且分別保存在成員變量mReaderThread和mDispatcherThread中。這里的InputReader實列mReader就是通過這里的InputReaderThread線程實列mReaderThread來讀取鍵盤事件的,而InputDispatcher實例mDispatcher則是通過這里的InputDispatcherThread線程實例mDisptacherThread來分發鍵盤消息的。
至此,InputManager的初始化工作就完成了,在回到Step 3中繼續分析InputManager的進一步啟動過程之前,我們先來作一個小結,看看這個初始化過程都做什么事情:
A. 在Java層中的WindowManagerService中創建了一個InputManager對象,由它來負責管理Android應用程序框架層的鍵盤消息處理;
B. 在C++層也相應地創建一個InputManager本地對象來負責監控鍵盤事件;
C. 在C++層中的InputManager對象中,分別創建了一個InputReader對象和一個InputDispatcher對象,前者負責讀取系統中的鍵盤消息,后者負責把鍵盤消息分發出去;
D. InputReader對象和一個InputDispatcher對象分別是通過InputReaderThread線程實例和InputDispatcherThread線程實例來實鍵盤消息的讀取和分發的。
有了這些對象之后,萬事就俱備了,回到Step 3中,調用InputManager類的start函數來執行真正的啟動操作。
Step 10. InputManager.start
這個函數定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager {
......
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart();
}
......
}
這個函數通過調用本地方法nativeStart來執行進一步的啟動操作。
Step 11. InputManager.nativeStart
這個函數定義在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中創建的,通過它的getInputManager函數可以返回C++層的InputManager對象,接著調用這個InputManager對象的start函數。
Step 12. InputManager.start
這個函數定義在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;
}
這個函數主要就是分別啟動一個InputDispatcherThread線程和一個InputReaderThread線程來讀取和分發鍵盤消息的了。這里的InputDispatcherThread線程對象mDispatcherThread和InputReaderThread線程對象是在前面的Step 9中創建的,調用了它們的run函數后,就會進入到它們的threadLoop函數中去,只要threadLoop函數返回true,函數threadLoop就會一直被循環調用,于是這兩個線程就起到了不斷地讀取和分發鍵盤消息的作用。
我們先來分析InputDispatcherThread線程分發消息的過程,然后再回過頭來分析InputReaderThread線程讀取消息的過程。
Step 13. InputDispatcherThread.threadLoop
這個函數定義在frameworks/base/libs/ui/InputDispatcher.cpp文件中:
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
這里的成員變量mDispatcher即為在前面Step 8中創建的InputDispatcher對象,調用它的dispatchOnce成員函數執行一次鍵盤消息分發的操作。
Step 14. InputDispatcher.dispatchOnce
這個函數定義在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);
}
這個函數很簡單,把鍵盤消息交給dispatchOnceInnerLocked函數來處理,這個過程我們在后面再詳細分析,然后調用mLooper->pollOnce函數等待下一次鍵盤事件的發生。這里的成員變量mLooper的類型為Looper,它定義在C++層中,具體可以參考前面
Android應用程序消息處理機制(Looper、Handler)分析一文。
Step 15. Looper.pollOnce
這個函數定義在frameworks/base/libs/utils/Looper.cpp文件中,具體可以參考前面Android應用程序消息處理機制(Looper、Handler)分析一文,這里就不再詳述了。總的來說,就是在Looper類中,會創建一個管道,當調用Looper類的pollOnce函數時,如果管道中沒有內容可讀,那么當前線程就會進入到空閑等待狀態;當有鍵盤事件發生時,InputReader就會往這個管道中寫入新的內容,這樣就會喚醒前面正在等待鍵盤事件發生的線程。
InputDispatcher類分發消息的過程就暫時分析到這里,后面會有更進一步的分析,現在,我們回到Step 12中,接著分析InputReader類讀取鍵盤事件的過程。在調用了InputReaderThread線程類的run就函數后,同樣會進入到InputReaderThread線程類的threadLoop函數中去。
Step 16. InputReaderThread.threadLoop
這個函數定義在frameworks/base/libs/ui/InputReader.cpp文件中:
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
這里的成員變量mReader即為在前面Step 8中創建的InputReader對象,調用它的loopOnce成員函數執行一次鍵盤事件的讀取操作。
Step 17. InputReader.loopOnce
這個函數定義在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);
}
這里通過成員函數mEventHub來負責鍵盤消息的讀取工作,如果當前有鍵盤事件發生或者有鍵盤事件等待處理,通過mEventHub的getEvent函數就可以得到這個事件,然后交給process函數進行處理,這個函數主要就是喚醒前面的InputDispatcherThread線程,通知它有新的鍵盤事件發生了,它需要進行一次鍵盤消息的分發操作了,這個函數我們后面再進一步詳細分析;如果沒有鍵盤事件發生或者沒有鍵盤事件等待處理,那么調用mEventHub的getEvent函數時就會進入等待狀態。
Step 18. EventHub.getEvent
這個函數定義在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);
}
}
}
}
這個函數比較長,我們一步一步來分析。
首先,如果是第一次進入到這個函數中時,成員變量mOpened的值為false,于是就會調用openPlatformInput函數來打開系統輸入設備,在本文中,我們主要討論的輸入設備就是鍵盤了。打開了這些輸入設備文件后,就可以對這些輸入設備進行是監控了。如果不是第一次進入到這個函數,那么就會分析當前有沒有輸入事件發生,如果有,就返回這個事件,否則就會進入等待狀態,等待下一次輸入事件的發生。在我們這個場景中,就是等待下一次鍵盤事件的發生了。
我們先分析openPlatformInput函數的實現,然后回過頭來分析這個getEvent函數的具體的實現。
Step 19. EventHub.openPlatformInput
這個函數定義在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;
}
這個函數主要是掃描device_path目錄下的設備文件,然后打開它們,這里的變量device_path定義在frameworks/base/libs/ui/EventHub.cpp文件開始的地方:
static const char *device_path = "/dev/input";
在設備目錄/dev/input中,一般有三個設備文件存在,分別是event0、mice和mouse0設備文件,其中,鍵盤事件就包含在event0設備文件中了。
Step 20. EventHub.scanDir
這個函數定義在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;
}
根據上面一步的分析,這個函數主要就是調用openDevice函數來分別打開/dev/input/event0、/dev/input/mice和/dev/input/mouse0三個設備文件了。
Step 21. EventHub.openDevice
這個函數定義在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;
}
函數首先根據文件名來打開這個設備文件:
fd = open(deviceName, O_RDWR);
系統中所有輸入設備文件信息都保存在成員變量mDevicesById中,因此,先在mDevicesById找到一個空位置來保存當前打開的設備文件信息:
mDevicesById[devid].seq = (mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK;
if (mDevicesById[devid].seq == 0) {
mDevicesById[devid].seq = 1<<SEQ_SHIFT;
}
找到了空閑位置后,就為這個輸入設備文件創建相應的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;
同時,這個設備文件還會保存在數組mFDs中:
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;
}
}
}
如果是的話,還要繼續進一步初始化前面為這個設備文件所創建的device_t結構體,主要就是把結構體device的classes成員變量的INPUT_DEVICE_CLASS_KEYBOARD位置為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);
}
到這里,系統中的輸入設備文件就打開了。
回到Step 18中,我們繼續分析EventHub.getEvent函數的實現。
在中間的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;
}
}
}
未處理的輸入事件保存在成員變量mInputBufferData中,如果有的話,就可以直接返回了,否則的話,就要通過系統調用poll來等待輸入設備上發生新的事件了,在我們這個場景中,就是等待鍵盤有鍵被按下或者松開了。:
int pollResult = poll(mFDs, mFDCount, -1);
這里的mFDs包含了我們所要監控的輸入設備的打開文件描述符,這是在前面的openPlatformInput函數中初始化的。
Step 22. poll
這是一個Linux系統的文件操作系統調用,它用來查詢指定的文件列表是否有有可讀寫的,如果有,就馬上返回,否則的話,就阻塞線程,并等待驅動程序喚醒,重新調用poll函數,或超時返回。在我們的這個場景中,就是要查詢是否有鍵盤事件發生,如果有的話,就返回,否則的話,當前線程就睡眠等待鍵盤事件的發生了。
這樣,InputManager的啟動過程就分析完了,下面我們再分析應用程序注冊鍵盤消息接收通道的過程。
2. 應用程序注冊鍵盤消息接收通道的過程分析
InputManager啟動以后,就開始負責監控鍵盤輸入事件了。當InputManager監控到鍵盤輸入事件時,它應該把這個鍵盤事件分發給誰呢?當然是要把這個鍵盤消息分發給當前激活的Activity窗口了,不過,當前激活的Activity窗口還需要主動注冊一個鍵盤消息接收通道到InputManager中去,InputManager才能把這個鍵盤消息分發給它處理。那么,當前被激活的Activity窗口又是什么時候去注冊這個鍵盤消息接收通道的呢?在前面一篇文章Android應用程序啟動過程源代碼分析中,我們分析Android應用程序的啟動過程時,在Step 33中分析到ActivityThread類的handleLaunchActivity函數中,我們曾經說過,當函數handleLaunchActivity調用performLaunchActivity函數來加載這個完畢應用程序的默認Activity后,再次回到handleLaunchActivity函數時,會調用handleResumeActivity函數來使這個Activity進入Resumed狀態。在調用handleResumeActivity函數的過程中,ActivityThread會通過android.view.WindowManagerImpl類為該Activity創建一個ViewRoot實例,并且會通過調用ViewRoot類的setView成員函數把與該Activity關聯的View設置到這個ViewRoot中去,而Activity正是通過ViewRoot類的setView成員函數來注冊鍵盤消息接收通道的。
有了這些背影知識后,接下來,我們就可以從ViewRoot.setView函數開始分析應用程序注冊鍵盤消息接收通道的過程了。首先看一下這個注冊過程的序列圖,然后再詳細分析每一個步驟:

點擊查看大圖
Step 1. ViewRoot.setView
這個函數定義在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());
}
......
}
}
}
}
這個函數中與注冊鍵盤消息接收通道(InputChannel)相關的邏輯主要有三處,一是調用requestLayout函數來通知InputManager,這個Activity窗口是當前被激活的窗口,二是調用sWindowSession(WindowManagerService內部類Session的遠程接口)的add成員函數來把鍵盤消息接收通道的一端注冊在InputManager中,三是調用InputQueue的registerInputChannel成員函數來把鍵盤消息接收通道的另一端注冊在本應用程序的消息循環(Looper)中。這樣,當InputManager監控到有鍵盤消息時,就會先找到當前被激活的窗口,然后找到其在InputManager中對應的鍵盤消息接收通道,通過這個通道在InputManager中的一端來通知在應用程序消息循環中的另一端,就把鍵盤消息分發給當前激活的Activity窗口了。
在接下來的內容中,我們首先描述requestLayout函數是如何告訴InputManager當前的Activity窗口便是激活窗口的,接著再回過頭來分析應用程序是如何把鍵盤消息接收通道的一端注冊到InputManager中去的,最后分析應用程序是如何鍵盤消息接收通道的另一端注冊到本應用程序的消息循環中去了。
Step 2. ViewRoot.requestLayout
這個函數定義在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();
}
......
}
這個函數調用了scheduleTraversals函數來進一步執行操作,由于篇幅關系,我們就不詳細描述scheduleTraversals函數了,簡單來說,在scheduleTraversals函數中,會通過sendEmptyMessage(DO_TRAVERSAL)發送一個消息到應用程序的消息隊列中,這個消息最終由ViewRoot的handleMessage函數處理,而ViewRoot的handleMessage函數把這個消息交給ViewRoot類的performTraversals來處理,在performTraversals函數中,又會調用ViewRoot類的relayoutWindow函數來進一步執行操作,最后在relayoutWindow函數中,就會通過WindowManagerService內部類Session的遠程接口sWindowSession的relayout函數來進入到WindowManagerService中。
Step 3. WindowManagerService.Session.relayout
這個函數定義在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;
}
......
}
......
}
這個函數只是簡單地調用WindowManagerService的成員函數relayoutWIndow來進一步處理。
Step 4. WindowManagerService.relayoutWIndow
這個函數定義在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();
}
......
}
......
}
這個函數又會繼續調用mInputMonitor的updateInputWindowsLw成員函數來更新當前的輸入窗口,mInputMonitor是WindowManagerService的成員變量,它的類型為InputMonitor。
Step 5. InputMonitor.updateInputWindowsLw
這個函數定義在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());
......
}
......
}
......
}
這個函數將當前系統中帶有InputChannel的Activity窗口都設置為InputManager的輸入窗口,但是后面我們會看到,只有當前激活的窗口才會響應鍵盤消息。
Step 6. InputManager.setInputWindows
這個函數定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager {
......
public void setInputWindows(InputWindow[] windows) {
nativeSetInputWindows(windows);
}
......
}
這個函數調用了本地方法nativeSetInputWindows來進一步執行操作。
Step 7. InputManager.nativeSetInputWindows
這個函數定義在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的啟動過程時已經見過了,這是一個本地InputManager對象,通過它進一步設置當前系統的輸入窗口。
Step 8. NativeInputManager.setInputWindows
這個函數定義在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);
}
這個函數首先將Java層的Window轉換成C++層的InputWindow,然后放在windows向量中,最后將這些輸入窗口設置到InputDispatcher中去。
Step 9. InputDispatcher.setInputWindows
這個函數定義在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就代表當前激活的窗口的。這個函數首先清空mFocusedWindow,然后再通過一個for循環檢查當前的輸入窗口中的哪一個窗口是獲得焦點的,獲得焦點的輸入窗口即為當前激活的窗口。
這樣,InputManager就把當前激活的Activity窗口保存在InputDispatcher中了,后面就可以把鍵盤消息分發給它來處理。
回到Step 1中的ViewRoot.setView函數中,接下來就調用下面語句來注冊鍵盤消息接收通道的一端到InputManager中去:
mInputChannel = new InputChannel();
try {
res = sWindowSession.add(mWindow, mWindowAttributes,
getHostVisibility(), mAttachInfo.mContentInsets,
mInputChannel);
} catch (RemoteException e) {
......
} finally {
......
}
前面說過,這里的sWindowSession是WindowManagerService內部類Session的一個遠程接口,通過它可以進入到WindowManagerService中去。
Step 10. WindowManagerService.Session.add
這個函數定義在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);
}
......
}
......
}
這里調用WindowManagerService類的addWindow函數來進一步執行操作。
Step 11. WindowManagerService.addWindow
這個函數定義在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中創建的InputChannel,它不為NULL,因此,這里會通過InputChannel.openInputChannelPair函數來創建一對輸入通道,其中一個位于WindowManagerService中,另外一個通過outInputChannel參數返回到應用程序中:
inputChannels[1].transferToBinderOutParameter(outInputChannel);
創建輸入通道之前,WindowManagerService會為當前Activity窗口創建一個WindowState對象win,用來記錄這個Activity窗口的狀態信息。當創建這對輸入管道成功以后,也會把其中的一個管道保存在這個WindowState對象win的成員變量mInputChannel中,后面要注銷這個管道的時候,就是從這個WindownState對象中取回這個管道的:
win.mInputChannel = inputChannels[0];
接下來我們就看一下InputChannel.openInputChannelPair函數的實現。
Step 12. InputChannel.openInputChannelPair
這個函數定義在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);
}
......
}
這個函數調用本地方法nativeOpenInputChannelPair來進一步執行操作。
Step 13. InputChannel.nativeOpenInputChannelPair
這個函數定義在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;
}
這個函數根據傳進來的參數name在C++層分別創建兩個InputChannel,一個作為Server端使用,一個作為Client端使用,這里的Server端即是指InputManager,而Client端即是指應用程序。這兩個本地的InputChannel是通過InputChannel::openInputChannelPair函數創建的,創建完成后,再相應地在Java層創建相應的兩個InputChannel,然后返回。
Step 14. InputChannel.openInputChannelPair
這個函數定義在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;
}
......
}
......
}
}
}
......
}
在閱讀這個函數之前,我們首先了解一下C++層的InputChannel的構造函數:
InputChannel::InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd,
int32_t sendPipeFd) :
mName(name), mAshmemFd(ashmemFd), mReceivePipeFd(receivePipeFd), mSendPipeFd(sendPipeFd) {
......
}
為了創建一個InputChannel,我們需要準備四個參數,一個是輸入通道的名稱name,一個是
匿名共享內存文件描述符,一個是管道的讀端文件描述符,一個是管道的寫端文件描述符。在上面的openInputChannelPair函數,輸入通道的名稱已經作為參數傳遞進來,因此,還需要創建匿名共享內存文件,還有管道。這里需要創建兩個管道,一個稱為前向管道(forward pipe),一個稱為反向管道(reverse pipe),它們交叉使用在Server端和Client端的InputChannel中,這樣就使入Server和Client可以互相通信了。
具體來說,Server端和Client端的InputChannel分別是這樣構成的:
Server Input Channel: ashmem - reverse(read) - forward(write)
Client Input Channel: ashmem - forward(read) - reverse(write)
前面我們在Android應用程序消息處理機制(Looper、Handler)分析一文中學習Android應用程序的消息處理機制時知道,管道可以用作進程間通信,其中一個進程在管道的讀端等待新的內空可讀,另一個進程在管道的寫端寫入新的內容以喚醒在管道讀端等待的進程,這樣就實現了進程間通信。在我們這個情景中,Client端可以在前向管道(forward pipe)的讀端睡眠等待新的內容可讀,而Server端可以通過向前向管道(forward pipe)的寫端寫入新的內容來喚醒Client端,同樣,把前向管道(forward pipe)換成反向管道(reverse pipe),也能實現Client端喚醒Server端。在后面我們分析InputDispatcher分發鍵盤消息時,會看到它們的用法。
有了這些背景知識后,相信上面的openInputChannelPair的代碼就容易理解了,這里就不再詳述了。
創建好了這兩個輸入通道后,回到Step 11中的WindowManagerService.addWindow函數中,一方面它把剛才創建的Client端的輸入通道通過outInputChannel參數返回到應用程序中:
inputChannels[1].transferToBinderOutParameter(outInputChannel);
另一方面,它還要把剛才創建的Server端的輸入通道注冊到InputManager中:
mInputManager.registerInputChannel(win.mInputChannel);
Step 15. InputManager.registerInputChannel
這個函數定義在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);
}
......
}
它通過調用本地方法nativeRegisterInputChannel來執行進一步的操作。
Step 16. InputManager.nativeRegisterInputChannel
這個函數定義在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對象,它們之間的對應關系是在前面的Step 13中設置好的,接著調用NativeInputManager的registerInputChannel執行進一步的操作。
Step 17. NativeInputManager.registerInputChannel
這個函數定義在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);
......
}
這個函數主要是調用了InputDispatcher的registerInputChannel來真正執行注冊輸入通道的操作。
Step 18. InputDispatcher.registerInputChannel
這個函數定義在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;
}
這個函數首先會通過getConnectionIndexLocked檢查從參數傳進來的InputChannel是否已經注冊過了,如果已經注冊過了,就返回一個BAD_VALUE值了,否則的話,就會創建一個Connection對象來封裝即將要注冊的inputChannel,我們可以不關心這個Connection對象的實現,接著還通過調用inputChannel->getReceivePipeFd獲得一個管
道的讀端描述符。回憶一下Step 14中的InputChannel.openInputChannelPair函數,我們創建了一個Server端的InputChannel,就是對應這里的inputChannel了,這個inputChannel的Receive Pipe Fd就是我們前面說的反向管道的讀端描述符了。有了這個Receive Pipe Fd后,就以它作為Key值來把前面創建的Connection對象保存在InputDispatcher中,這樣就基本完成鍵盤消息接收通道的注冊了。但是,注冊的工作還未完成,最后,還要把這個Receive Pipe Fd添加到InputDispatcher的成員變量mLooper中去,這里的成員變量mLooper的類型為Looper,我們在前面介紹InputManager的啟動過程的Step 15中已經見過了,這里就不再詳述了,不過這里仍然值得介紹一下它的addFd函數。
在前面一篇文章Android應用程序消息處理機制(Looper、Handler)分析中,我們在介紹到Android應用程序的消息循環一節時,曾經說過,在Looper類內部,會創建一個管道,然后Looper會睡眠在這個管道的讀端,等待另外一個線程來往這個管道的寫端寫入新的內容,從而喚醒等待在這個管道讀端的線程,除此之外,Looper還可以同時睡眠等待在其它的文件描述符上,因為它是通過Linux系統的epoll機制來批量等待指定的文件有新的內容可讀的。這些其它的文件描述符就是通過Looper類的addFd成函數添加進去的了,在添加的時候,還可以指定回調函數,即當這個文件描述符所指向的文件有新的內容可讀時,Looper就會調用這個hanldeReceiveCallback函數,有興趣的讀者可以自己研究一下Looper類的addFd函數的實現,它位于frameworks/base/libs/utils/Looper.cpp文件中。
分析到這里,Server端的InputChannel就注冊完成了。回憶一下前面介紹InputManager啟動過程的Step 14,這時InputDispatcherThread同時睡眠在InputDispatcher的成員變量mLooper內部的管道的讀端以及這里的Server端InputChannel里面的反向管道的讀端上,mLooper內部的管道的讀端等待鍵盤事件的發生而被喚醒,而Server端InputChannel里面的反向管道的讀端等待Client端InputChannel里面的反向管道的寫端被寫入新的內容而被喚醒。
Server端的InputChannel注冊完成后,回到Step 11中的WindowManagerService.addWindow函數,接下來就是把Client端的InputChannel轉換成addWindow的參數outInputChannel中,然后返回到Step 1中的ViewRoot.setView函數中,繼續執行Client端的InputChannel的注冊過程,即為應用程序這一側注冊鍵盤消息接收通道:
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的實例,因此,最后會執行下面語句:
InputQueue.registerInputChannel(mInputChannel, mInputHandler,
Looper.myQueue());
它調用InputQueue的registerInputChannel函數為應用程序注冊鍵盤消息接收通道,這里的mInputChannel即為我們在前面Step 14中創建的Client端的InputChannel;Looper.myQueue函數返回的便是應用程序主線程的消息隊列,具體可以參考前面一篇文章
Android應用程序消息處理機制(Looper、Handler)分析;參數mInputHandler是一個回調對象,當有鍵盤事件發生時,這個mInputHandler的handleKey函數就會被調用,在后面的分析中,我們將會看到。
Step 19. InputQueue.registerInputChannel
這個函數定義在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);
}
}
......
}
這個函數調用本地方法nativeRegisterInputChannel函數來執行進一步的操作。
Step 20. InputQueue.nativeRegisterInputChannel
這個函數定義在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);
......
}
這里繼續調用NativeInputQueue的registerInputChannel函數來執行真正的鍵盤消息接收通道的工作。
Step 21. NativeInputQueue.registerInputChannel
這個函數定義在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;
}
這里注冊應用程序的InputChannel的邏輯和前面介紹的Step 18中在InputDispatcher中注冊Server端的InputChannel是一樣的,所不同的是,這里用的looper是應用程序主線程中的消息循環對象Looper,而添加到這個looper對象中的Receive Pipe Fd是前面在Step 14中創建的前向管道的讀端文件描述符,而使用的回調函數是NativeInputQueue的成員函數handleReceiveCallback。
介紹到這里,應用程序注冊鍵盤消息接收通道的過程就分析完成了。這個過程比較復雜,這里小結一下:
A. 即將會被激活的Activity窗口,會通知InputManager,它是當前激活的窗口,因此,一旦發生鍵盤事件的時候,InputManager就把這個鍵盤事件拋給這個Activity處理;
B. 應用程序會為這個Activity窗口和InputManager之間創建一個鍵盤消息接收通道,這個通道的一端由一個Server端的InputChannel構成,另一端由Client端的InputChannel構成,Server端的InputChannel注冊在由InputManager所管理的InputDispatcher中,而Client端的InputChannel注冊在由應用程序主線程的消息循環對象Looper中;
C. 注冊在InputDispatcher中的InputChannel由一個反向管道的讀端和一個前向管道的寫端組成,而注冊在應用程序主線程的消息循環對象Looper中的InputChannel由這個前向管道的讀端和反向管道的寫端組成,這種交叉結構使得當有鍵盤事件發生時,InputDispatcher可以把這個事件通知給應用程序。
應用程序注冊好鍵盤消息接收通道后,接下來就開始分析InputManager分發鍵盤消息給應用程序的過程了。
3. InputManager分發鍵盤消息給應用程序的過程分析
在分析InputManager分發鍵盤消息給應用程序的過程之前,我們先假設現在沒有鍵盤事件發生,因此,InputManager中的InputReader正在睡眠等待鍵盤事件的發生,而InputManager中的InputDispatcher正在等待InputReader從睡眠中醒過來并且喚醒它,而應用程序也正在消息循環中等待InputDispatcher從睡眠中醒過來并且喚醒它。這時候,用戶按下鍵盤中的一個鍵,于是,一系列喚醒的事件就依次發生了,一直到應用程序中正在顯示的Activity得到通知,有鍵盤事件發生了。我們先來看這個過程的序列圖,然后再詳細分析每一個步驟:

點擊查看大圖
Step 1. InputReader.pollOnce
Step 2. EventHub.getEvent
這兩個函數分別定義在frameworks/base/libs/ui/InputReader.cpp和frameworks/base/libs/ui/EventHub.cpp文件中,前面我們在分析InputManager的啟動過程的Step 17和Step 18時,已經看到過這兩個函數了。InputReaderThread線程會不民地循環調用InputReader.pollOnce函數來讀入鍵盤事件,而實際的鍵盤事件讀入操作是由EventHub.getEvent函數來進行的。如果當前沒有鍵盤事件發生,InputReaderThread線程就會睡眠在EventHub.getEvent函數上,而當鍵盤事件發生后,就會把這個事件封裝成一個RawEvent對象,然后返回到pollOnce函數中,執行process函數進一步處理:
void InputReader::loopOnce() {
RawEvent rawEvent;
mEventHub->getEvent(& rawEvent);
......
process(& rawEvent);
}
Step 3. InputReader.process
這個函數定義在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;
}
}
當鍵盤事件發生時,rawEvent->type的值為EV_KEY,這是一個宏定義,具體可以參考bionic/libc/kernel/common/linux/input.h文件:
#define EV_KEY 0x01
因此,接下來會調用consumeEvent函數進一步處理。
Step 4. InputReader.consumeEvent
這個函數定義在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中取得觸發鍵盤事件設備對象device,然后調用它的process函數進行處理。
Step 5. InputDevice.process
這個函數定義在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成員變量保存了一系列輸入設備事件處理象,例如負責處理鍵盤事件的KeyboardKeyMapper對象、負責處理軌跡球事件的TrackballInputMapper對象以及負責處理觸摸屏事件的TouchInputMapper對象, 它們是在InputReader類的成員函數createDevice中創建的。這里查詢每一個InputMapper對象是否要對當前發生的事件進行處理。由于發生的是鍵盤事件,真正會對該事件進行處理的只有KeyboardKeyMapper對象。
Step 6. KeyboardInputMapper.process
這個函數定義在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;
}
}
}
這個函數首先會檢查一下鍵盤掃描碼是否正確,如果正確的話,就會調用processKey函數進一步處理。
Step 7. KeyboardInputMapper.processKey
這個函數定義在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);
}
這個函數首先對對按鍵作一些處理,例如,當某一個DPAD鍵被按下時,根據當時屏幕方向的不同,它所表示的意義也不同,因此,這里需要根據當時屏幕的方向來調整鍵盤碼:
// 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里面,就是為了處理上面講的當這個鍵盤一直按著不放的時候屏幕方向發生改變的情況。
如果是松開鍵盤上的某個鍵,就把它從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;
}
當然,對鍵盤事件的這些處理不是本文的重點,本文的重點是分析從鍵盤事件到當前激活的Activity窗口接收到這個鍵盤消息的過程。
最后,KeyboardInputMappger函數通知InputDispatcher,有鍵盤事件發生了:
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
這個函數定義在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();
}
}
函數首先是調用validateKeyEvent函數來驗證action參數是否正確:
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參數的值只能為AKEY_EVENT_ACTION_DOWN(按下)或者AKEY_EVENT_ACTION_UP(松開)。
參數action檢查通過后,還通過policyFlags參數來檢查一下同時是否有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;
}
最后,調用enqueueInboundEventLocked函數把這個按鍵事件封裝成一個KeyEntry結構加入到InputDispatcher類的mInboundQueue隊列中去:
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;
}
從這個函數我們可以看出,在兩種情況下,它的返回值為true,一是當加入該鍵盤事件到mInboundQueue之前,mInboundQueue為空,這表示InputDispatccherThread線程正在睡眠等待InputReaderThread線程的喚醒,因此,它返回true表示要喚醒InputDispatccherThread線程;二是加入該鍵盤事件到mInboundQueue之前,mInboundQueue不為空,但是此時用戶按下的是Home鍵,按下Home鍵表示要切換App,我們知道,在切換App時,新的App會把它的鍵盤消息接收通道注冊到InputDispatcher中去,并且會等待InputReader的喚醒,因此,在這種情況下,也需要返回true,表示要喚醒InputDispatccherThread線程。如果不是這兩種情況,那么就說明InputDispatccherThread線程現在正在處理前面的鍵盤事件,不需要喚醒它。
回到前面的notifyKey函數中,根據enqueueInboundEventLocked函數的返回值來決定是否要喚醒InputDispatccherThread線程:
if (needWake) {
mLooper->wake();
}
這里,假設needWake為true,于是,就會調用mLooper對象的wake函數來喚醒InputDispatccherThread線程了。
Step 9. Looper.wake
這個函數定義在frameworks/base/libs/utils/Looper.cpp文件中,在前面一篇文章Android應用程序消息處理機制(Looper、Handler)分析中,我們已經分析過這個函數了,這里不再詳述,簡單來說,它的作用就是用來喚醒睡眠在Looper對象內部的管道讀端的線程,在我們的這個場景中,睡眠在Looper對象內部的管道讀端的線程就是InputDispatccherThread線程了。
從上面InputManager啟動過程的Step 15中,我們知道,此時InputDispatccherThread線程正在InputDispatcher類的dispatchOnceb函數中通過調用mLooper->loopOnce函數進入睡眠狀態。當它被喚醒以后,它就會從InputDispatcher類的dispatchOnceb函數返回到InputDispatcherThread類的threadLoop函數,而InputDispatcherThread類的threadLoop函數是循環執行的,于是,它又會再次進入到InputDispatcher類的dispatchOnce函數來處理當前發生的鍵盤事件。
Step 10. InputDispatcher.dispatchOnce
這個函數定義在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
......
}
它調用dispatchOnceInnerLocked函數來進一步處理這個鍵盤事件。
Step 11. InputDispatcher.dispatchOnceInnerLocked
這個函數定義在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;
}
......
}
......
}
我們忽略了這個函數的次要邏輯,主要關注鍵盤事件的主要處理流程。首先,如果前面發生的鍵盤事件都已經處理完畢,那么這里的mPendingEvent就為NULL,又因為前面我們把剛剛發生的鍵盤事件加入了mInboundQueue隊列,因此,這里mInboundQueue不為NULL,于是,這里就把mInboundQueue隊列中的鍵盤事件取出來,放在mPendingEvent變量中:
mInboundQueue.dequeue(entry);
mPendingEvent = entry;
由于這里發生的是鍵盤事件,即mPendingEvent->type的值為EventEntry::TYPE_KEY,于是,在接下來的switch語句中就會執行dispatchKeyLocked函數來分發鍵盤消息。
Step 12. InputDispatcher.dispatchKeyLocked
這個函數定義在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是否已經標志出誰是當前激活的Activity窗口,如果沒有,就需要通過findFocusedWindowTargetsLocked函數來把它找出來。當把當前激活的Activity窗口找出來以后,接下來就調用dispatchEventToCurrentInputTargetsLocked函數把鍵盤事件分發給它了。
我們先來看一InputDispatcher是如何找到當前激活的Activity窗口的,然后再分析它把鍵盤事件分發給當前激活Activity窗口的過程。
Step 13. InputDispatcher.findFocusedWindowTargetsLocked
這個函數定義在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;
}
回憶前面我們分析應用程序注冊鍵盤消息接收通道的過程時,在Step 9中,當前處于激活狀態的應用程序會通過調用InputDispatcher類setInputWindows函數把把當前獲得焦點的Activity窗口設置到mFocusedWindow中去,因此,這里的mFocusedWindow不為NULL,于是,就通過了第一個if語句的檢查。
第二個if語句檢查權限問題,原來,這個鍵盤事件除了是由硬件觸發的外,也可以由其它進程注入進來的,如果這個鍵盤事件是由其它進程注入進來的,那么entry->injectState就不為NULL,它里面包含了事件注冊者的進程ID和用戶ID,于是,這里就會調用checkInjectionPermission來檢查這個事件注入者的進程ID和用戶ID,看看它是否具有這個權限。這里我們不考慮這種情況,因此,這里的entry->injectState為NULL,于是,這個if語句的檢查也通過了。
第三個if語句檢查當前激活的Activity窗口是否是處于paused狀態,如果是的話,也不用進一步處理了。一般情況下,當前激活的Activity窗口都是處于resumed狀態的,于是,這個if語句的檢查也通過了。
第四個if語句檢查當前激活的Activity窗口是否還正在處理前一個鍵盤事件,如果是的話,那就要等待它處理完前一個鍵盤事件后再來處理新的鍵盤事件了。這里我們也假設當前激活的Activity窗口不是正在處理前面的鍵盤事件,因此,這個if語句的檢查也通過了。
最后,就調用addWindowTargetLocked函數把當前激活的Activity窗口添加到InputDispatcher類的mCurrentInputTargets成員變量中去。
Step 14. InputDispatcher.addWindowTargetLocked
這個函數定義在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;
}
這個函數簡單,就是把傳進來的參數window添加到mCurrentInputTargets中去就完事了,后面InputDispatcher就會從mCurrentInputTargets中取出恰當的Activity窗口,然后把鍵盤事件分發給它。
回到Step 12中的dispatchKeyLocked函數,它接下來就調用dispatchEventToCurrentInputTargetsLocked來進一步處理了。
Step 15. InputDispatcher.dispatchEventToCurrentInputTargetsLocked
這個函數定義在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 {
......
}
}
這個函數的實現也比較簡單,前面我們已經把當前需要接受鍵盤事件的Activity窗口添加到mCurrentInputTargets中去了,因此,這里就分別把它們取出來,然后調用prepareDispatchCycleLocked函數把鍵盤事件分發給它們處理。
前面我們在分析應用程序注冊鍵盤消息接收通道的過程時,在Step 18中(InputDispatcher.registerInputChannel),把Server端的InputChannel封裝成了一個Connection,然后以這個InputChannel中的Receive Pipe Fd作為鍵值把這個Connection對象保存在mConnectionsByReceiveFd中。這里,既然我們已經通過mCurrentInputTargets得到了表示當前需要接收鍵盤事件的Activity窗口的InputTarget對象,而且這個InputTarget對象的inputChannel就表示當初在InputDispatcher中注冊的Server端InputChannel,因此,這里就可以把這個Connection對象取出來,最后調用prepareDispatchCycleLocked函數來進一步處理。
Step 16. InputDispatcher.prepareDispatchCycleLocked
這個函數定義在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);
}
}
在開始處理鍵盤事件之前,這個函數會檢查一下傳進來的參數connection中的outboundQueue事件隊列是否為空,如果不為空,就要看看當前要處理的事件和outboundQueue隊列中的最后一個事件是不是同一個motion事件,如果是的話,并且從上面傳進來的resumeWithAppendedMotionSample參數為true,這時候就要以流水線的方式來處理這些motion事件了。在我們這個情景中,要處理的是鍵盤事件,因此在上面Step 12中傳進來的resumeWithAppendedMotionSample參數為false,因此,我們略過這種情況。
接下來,就會把當前的鍵盤事件封裝成一個DispatchEntry對象,然后添加到connection對象的outboundQueue隊列中去,表示當前鍵盤事件是一個待處理的鍵盤事件。
當connection中的outboundQueue事件隊列不為空,即wasEmpty為false時,說明當前這個Activity窗口正在處鍵盤事件了,因此,就不需要調用startDispatchCycleLocked來啟動Activity窗口來處理這個事件了,因為一旦這個Activity窗口正在處鍵盤事件,它就會一直處理下去,直到它里的connection對象的outboundQueue為空為止。當connection中的outboundQueue事件隊列為空時,就需要調用startDispatchCycleLocked來通知這個Activity窗口來執行鍵盤事件處理的流程了。
Step 17. InputDispatcher.startDispatchCycleLocked
這個函數定義在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();
......
}
這個函數主要圍繞傳進來的Connection對象做兩件事情,一是從它的outboundQueue隊列中取出當前需要處理的鍵盤事件,然后把這個事件記錄在它的內部對象inputPublisher中,二是通過它的內部對象inputPublisher通知它所關聯的Activity窗口,現在有鍵盤事件需要處理了。第一件事情是通過調用它的InputPublisher對象的publishKeyEvent函數來完成的,而第二件事情是通過調用它的InputPublisher對象的sendDispatchSignal來完成的。我們先來看InputPublisher的成員函數publishKeyEvent的實現,然后再回來分析它的另外一個成員函數sendDispatchSignal的實現。
Step 18. InputPublisher.publishKeyEvent
這個函數定義在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;
}
這個函數主要就是把鍵盤事件記錄在InputPublisher類的成員變量mSharedMessage中了,這個mSharedMessage成員變量指向的是一個匿名共享內存。
這個匿名共享內存是什么時候創建的呢?前面我們在分析應用程序注冊鍵盤消息接收通道的過程時,在Step 18中(InputDispatcher.registerInputChannel),在把Server端的InputChannel封裝成一個 Connection對象時,會調用它的initialize成員函數來執行一些初始化工作,就是在這個時候創建這個匿名共享內存的了:
sp<Connection> connection = new Connection(inputChannel);
status_t status = connection->initialize();
我們來看一下這個initialize函數的實現,它定義在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除了擁有一個反向管道的讀端文件描述符和一個前向管道的寫端文件描述符之后,還有一個匿名共享文件描述符,這個匿名共享文件描述符就是用來創建匿名共享內存mSharedMessage的了。
這個匿名共享內存mSharedMessage的作用是什么呢?原來,在InputChannel中,前向管道和反向管道的作用只是用來在Server端和Client端之間相互通知有事件發生了,但是具體是什么樣的事件,還需要去讀取這個匿名共享內存的內容才知道。前面我們在分析應用程序注冊鍵盤消息接收通道的過程時,在Step 14中(InputChannel.openInputChannelPair)創建Server端和Client端的InputChannel對時,創建一個匿名共享內存,這個匿名共享內存有兩個文件描述符同時指向它,其中一個放在Server端的InputChannel中,另外一個放在Client端的InputChannel中。這樣,當InputDispatcher通過Server端的InputChannel的前向管道來通知Client端有鍵盤事件發生時,Client端只要通過它的InputChannel中的匿名共享內存文件描述符去讀取匿名共享內存中的內容,就可以知道發生了什么事情了。有關匿名共享內存的相關知識,請參考Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃一文。
回到Step 17中,接下來就是調用InputPublisher的成員函數sendDispatchSignal來通知Activity窗口處理鍵盤事件了。
Step 19. InputPublishe.sendDispatchSignal
這個函數定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputPublisher::sendDispatchSignal() {
......
mWasDispatched = true;
return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH);
}
這個函數很簡單,它通過調用內部成員變量mChannel的sendSignal函數來通知相應的Activity窗口來處理鍵盤事件。
Step 20. InputChannel.sendSignal
這個函數定義在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;
}
這里所謂的發送信號通知,其實是通過向其內部一個管道的寫端寫入一個字符來實現的。前面我們分析應用程序注冊鍵盤消息接收通道的過程時,在Step 21中(NativeInputQueue.registerInputChannel),它把一個InputChannel注冊到應用程序主線程中的Looper對象中,然后應用程序的主線程就通過這個Looper對象睡眠等待在這個InputChannel中的前向管道中有新的內容可讀了,這里的mSendPipeFd就是對應這個前向管道的寫端。現在既然向這個前向管道的寫端寫入新的內容了,于是,應用程序的主線程就被喚醒了。
在前面分析應用程序注冊鍵盤消息接收通道過程的Step 21中,我們也說過,當應用程序的主線程因為這個InputChannel中的前向管道的寫端喚醒時,NativeInputQueue的成員函數handleReceiveCallback就會被回調,因此,接下來,應用程序的主線程就會被喚醒,然后執行NativeInputQueue的成員函數handleReceiveCallback。
Step 21. NativeInputQueue.handleReceiveCallback
這個函數定義在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;
}
這個函數首先是通過參數data獲得當初注冊InputChannel的NativeInputQueue對象,具體可以參考前面介紹的應用程序注冊鍵盤消息接收通道過程的Step 21。接下來再通過參數receiveFd獲得保存在這個NativeInputQueue對象中的mConnectionsByReceiveFd成員變量中的Connection對象。有了這個Connection對象后,就可以獲得它內部的InputConsumer對象,這個InputConsumer對象是和上面的Step 18中介紹的InputPublisher對象相應的。
在InputChannel內部中,分別有一個InputPublisher對象和一個InputConsumer對象,對于Server端的InputChannel來說,它使用的是InputPublisher對象,通過它進行鍵盤消息的分發,而對于Client端的InputChannel來說,它使用的是InputConsumer對象,通過它進行鍵盤消息的讀取。
獲得了這個InputConsumer對象后首先是調用它的receiveDispatchSignal來確認是否是接收到了鍵盤消息的通知,如果是的話,再調用它的consume函數來把鍵盤事件讀取出來,最后,調用Java層的回調對象InputQueue的DispatchKeyEvent來處理這個鍵盤事件。下面,我們就依次來分析這些過程。
Step 22. InputConsumer.receiveDispatchSignal
這個函數定義在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;
}
這個函數很簡單,它通過它內部對象mChannel來從前向管道的讀端讀入一個字符,看看是否是前面的Step 20中寫入的INPUT_SIGNAL_DISPATCH字符。
InputChannel類的receiveSignal函數也是定義在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
這個函數定義在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;
}
這個函數很簡單,只要對照前面的Step 18(InputPublisher.publishKeyEvent)來邏輯來看就可以了,后者是往匿名共享內存中寫入鍵盤事件,前者是從這個匿名共享內存中把這個鍵盤事件的內容讀取出來。
回到Step 21中的handleReceiveCallback函數中,從InputConsumer中獲得了鍵盤事件的內容(保存在本地變量inputEvent中)后,就開始要通知Java層的應用程序了。在前面分析應用程序注冊鍵盤消息接收通道的過程時,在Step 21中(NativeInputQueue.registerInputChannel),會把傳進來的對象inputHandlerObj保存在Connection對象中:
connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);
這個inputHandlerObj對象的類型為Java層的InputHandler對象,因此,這里首先把它取回來:
inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal);
取回來之后,我們要把作為參數來調用InputQueue類的dispatchKeyEvent靜態成員函數來通知應用程序,有鍵盤事件發生了,因此,先找到InputQueue類的靜態成員函數dispatchKeyEvent的ID:
dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;
在回調用這個InputQueue類的dispatchKeyEvent靜態成員函數之前,還要把前面獲得的inputEvent對象轉換成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
這個函數定義在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);
}
......
}
這個函數首先會創建一個FinishedCallback類型的對象finishedCallback,FinishedCallback是InputQueue的一個內部類,它繼承于Runnable類。這個finishedCallback對象是提供給當前Activity窗口的,當它處理完畢鍵盤事件后,需要通過消息分發的方式來回調這個finishedCallback對象,以及InputQueue類處理一個手尾的工作,后面我們會分析到。
這里的inputHandler對象是在前面分析應用程序注冊鍵盤消息接收通道的過程時,在Step 1(ViewRoot.setView)中傳進來的:
InputQueue.registerInputChannel(mInputChannel, mInputHandler,
Looper.myQueue());
它是ViewRoot類的一個成員變量mInputHandler。因此,這里將調用ViewRoot類的內部對象mInputHandler的成員函數handleKey來處理鍵盤事件。
Step 25. InputHandler.handleKey
這個函數定義在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);
}
......
};
......
}
這個函數首先調用其外部類ViewRoot的startInputEvent成員函數來把回調對象finishedCallback保存下來:
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
private void startInputEvent(Runnable finishedCallback) {
......
mFinishedCallback = finishedCallback;
}
......
}
然后再調用其外部類ViewRoot的dispatchKey成員函數來進一步處這個鍵盤事件。
Step 26. ViewRoot.dispatchKey
這個函數定義在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)它放到消息隊列中去處理,這個消息最后由ViewRoot類的deliverKeyEvent成員函數來處理。
Step 27. ViewRoot.deliverKeyEvent
這個函數定義在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在把這個鍵盤事件分發給當前激活的Activity窗口處理之前,首先會調用InputMethodManager的dispatchKeyEvent成員函數來處理這個鍵盤事件。InputMethodManager處理完這個鍵盤事件后,再回調用這里的mInputMethodCallback對象的finishedEvent成員函數來把鍵盤事件分發給當前激活的Activity窗口處理。當然,在把這個鍵盤事件分發給InputMethodManager處理之前,ViewRoot也會先把這個鍵盤事件分發給當前激活的Activity窗口的dispatchKeyEventPreIme成員函數處理。
Step 28. InputMethodManager.dispatchKeyEvent
這個函數定義在frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java文件中。這是一個輸入法相關的類,我們這里就不關注了,只要知道當輸入法處理完成之后,它就會調用ViewRoot類的mInputMehtodCallback對象的finishedEvent成員函數。
Step 29. InputMethodCallack.finishedEvent
這個函數定義在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);
}
}
......
}
......
}
這個函數最終調用ViewRoot的dispatchFinishedEvent來進一步處理。
Step 30. ViewRoot.dispatchFinishedEvent
這個函數定義在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)放在消息隊列中去,最后,這個消息由ViewRoot的handleFinishedEvent函數來處理。
Step 31. ViewRoot.handleFinishedEvent
這個函數定義在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就會調用deliverKeyEventToViewHierarchy函數來把這個鍵盤事件分發給當前激活的Activity窗口來處理。
Step 32. ViewRoot.deliverKeyEventToViewHierarchy
這個函數定義在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();
}
}
}
......
}
這個函數首先會調用ViewRoot類的成員變量mView的dispatchKeyEvent來處理這個鍵盤事件,然后最調用ViewRoot類的finishInputEvent來處理手尾工作。
ViewRoot類的成員變量mView的類型為DecorView,它是由ActivityThread類第一次Resume當前的Activity窗口時創建的,具體可以參考ActivityThread類的handleResumeActivity成員函數,這里就不關注了。
Step 33. DecorView.dispatchKeyEvent
這個函數定義在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java文件中,它是PhoneWindow類的一個內部類:
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函數返回的是當前應用程序的激活的Activity窗口的Window.Callback接口,一般它不為NULL,因此,這個函數會調用Activity類的dispatchKeyEvent來處理這個鍵盤事件。
Step 34. Activity.dispatchKeyEvent
這個函數定義在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轉發一下。注意,KeyEvent的成中函數dispatch的第一個參數的類型是KeyEvent.Callback,而Activity實現了這個接口,因此,這里可以傳this引用過去。
Step 35. KeyEvent.dispatch
這個函數定義在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;
}
......
}
這里就根據一個鍵是按下(ACTION_DOWN)、還是松開(ACTION_UP)或者是一個相同的鍵被多次按下和松開(ACTION_MULTIPLE)等不同事件類型來分別調用Activity的onKeyDown、onKeyUp和onKeyMultiple函數了。
Activity窗口處理完這個鍵盤事件后,層層返回,最后回到Step 32中,調用finishInputEvent事件來處理一些手尾工,下面我們將會看到這些手尾工是什么。
Step 36. ViewRoot.finishInputEvent
這個函數定義在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設置的,它是一個Runnable對象,實際類型是定義在InputQueue的內部類FinishedCallback,因此,這里調用它的run方法時,接下來就會調用InputQueue的內部類FinishedCallback的run成員函數:
public final class InputQueue {
......
private static class FinishedCallback implements Runnable {
......
public void run() {
synchronized (sLock) {
......
nativeFinished(mFinishedToken);
......
}
}
......
}
......
}
這里它調用外部類InputQueue的本地方法nativeFinished來進一步處理。
Step 37. InputQueue.nativeFinished
這個函數定義在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*/);
......
}
這個函數只是簡單只調用NativeInputQueue的finished方法來進一處處理。
Step 38. NativeInputQueue.finished
這個函數定義在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;
}
這個函數最重要的參數便是finishedToken了,通過它可以獲得之前通知Java層的InputQueue類來處理鍵盤事件的Connection對象,它的值是在上面的Step 21(NativeInputQueue.handleReceiveCallback)中生成的:
finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum);
函數generateFinishedToken的定義如下:
jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId,
uint16_t messageSeqNum) {
return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum);
}
它的實現很簡單,只是把receiveFd(前向管道的讀端文件描述符)、connectionId(Client端的InputChannel對應的Connection對象在NativeInputQueue中的索引)和messageSeqNum(鍵盤消息的序列號)三個數值通過移位的方式編碼在一個jlong值里面,即編碼在上面的finishedToken參數里面。
因此,在上面的finished函數里面,首先就是要對參數值finishedToken進行解碼,把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之后,就可以把相應的Connection對象取回來了:
ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
......
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
接下來就是調用這個connection對象中的inputConsumer對象來發送信號通知Server端的InputChannel,應用程序這一側處理完剛才發生的鍵盤事件了:
status_t status = connection->inputConsumer.sendFinishedSignal();
Step 39. InputConsumer.sendFinishedSignal
這個函數定義在frameworks/base/libs/ui/InputTransport.cpp文件中:
status_t InputConsumer::sendFinishedSignal() {
......
return mChannel->sendSignal(INPUT_SIGNAL_FINISHED);
}
這個函數的實現很簡單,只是調用其內部對象mChannel的sendSignal函數來執行發送信號的通知。前面我們已經說過,這里的mChannel的類型為InputChannel,它是注冊在應用程序一側的Client端InputChannel,它的成員函數sendSignal的定義我們在上面的Step 20中已經分析過了,這里不再詳述,不過,這里和上面Step 20不一樣的地方是,它里的通知方向是從反向管道的寫端(在應用程序這一側)到反向管道的讀端(在InputDispatcher這一側)。
前面我們在分析應用程序注冊鍵盤消息接收通道的過程時,在Step 18(InputDispatcher.registerInputChannel)中,說到InputDispatcher把一個反向管道的讀端文件描述符添加到WindowManagerService所運行的線程中的Looper對象中去,然后就會在這個反向管道的讀端上睡眠等待有這個管道有新的內容可讀。現在,InputConsumer往這個反向管道寫入新的內容了,于是,InputDispatcher就被喚醒過來了,喚醒過來后,它所調用的函數是InputDispatcher.handleReceiveCallback函數,這與前面的Step 21的邏輯是一樣的。
Step 40. InputDispatcher.handleReceiveCallack
這個函數定義在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
}
這個函數首先是通過傳進來的receiveFd參數(反向管道的讀端文件描述符)的值取得相應的Connection對象:
ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd);
......
sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
然后通過調用這個connection對象的內部對象inputPublisher的receiveFinishedSignal函數來確認是否真的收到鍵盤事件處理完成的信號,確認之后,就會調用InputDispatcher對象d的finishDispatchCycleLocked函數來執行一些善后工作。下面我們就依次分析這兩個過程。
Step 41. InputPublisher.receiverFinishedSignal
這個函數定義在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確認是否真的收到鍵盤事件分發的信號的邏輯是一致的,都是通過InputChannel的receiveSignal函數來確認是否在管道中收到了某一個約定的字符值,不過,這里約定的字符值為INPUT_SIGNAL_FINISHED。
回到前面的Step 40中,確認了是真的收到了鍵盤事件處理完成的信號后,就調用InputDispatcher的finishDispatchCycleLocked函數來執行一些善后工作了。
Step 42. InputDispatcher.finishDispatchCycleLocked
這個函數定義在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);
}
這個函數主要就是做了三件事情:
一是通知其它系統,InputDispatcher完成了一次鍵盤事件的處理:
// Notify other system components.
onDispatchCycleFinishedLocked(currentTime, connection);
二是調用相應的connection對象的內部對象inputPublisher來的reset函數來回收一些資源,它里面其實就是釋放前面在Step 18(InputPublisher.publishKeyEvent)使用的匿名共享內存了:
// 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();
三是調用InputDispatcher的startNextDispatchCycleLocked函數來處理下一個鍵盤事件:
startNextDispatchCycleLocked(currentTime, connection);
因為正在處理當前這個鍵盤事件的時候,很有可能又同時發生了其它的鍵盤事件,因此,這里InputDispatcher還不能停下來,需要繼續調用startNextDispatchCycleLocked繼續處理鍵盤事件,不過下一個鍵盤事件的處理過程和我們現在分析的過程就是一樣的了。
至此,InputManager分發鍵盤消息給應用程序的過程就分析完成了,這是一個比較復雜的過程,不過,只要我們抓住主要的線索,就不難理解了,現在我們就小結一下這個過程的四個主要線索:
A. 鍵盤事件發生,InputManager中的InputReader被喚醒,此前InputReader睡眠在/dev/input/event0這個設備文件上;
B. InputReader被喚醒后,它接著喚醒InputManager中的InputDispatcher,此前InputDispatcher睡眠在InputManager所運行的線程中的Looper對象里面的管道的讀端上;
C. InputDispatcher被喚醒后,它接著喚醒應用程序的主線程來處理這個鍵盤事件,此前應用程序的主線程睡眠在Client端InputChannel中的前向管道的讀端上;
D. 應用程序處理處理鍵盤事件之后,它接著喚醒InputDispatcher來執行善后工作,此前InputDispatcher睡眠在Server端InputChannel的反向管道的讀端上,注意這里與第二個線索處的區別。
4. 應用程序注銷鍵盤消息接收通道的過程分析
當Activity窗口創建時,它會向InputManager注冊鍵盤消息接收通道,而當Activity窗口銷毀時,它就會向InputManager注銷前面注冊的鍵盤消息接收通道了,本節內容就來看看應用程序注銷鍵盤消息接收通道的過程。
當我們按下鍵盤上的Back鍵時,當前激活的Activity窗口就會被失去焦點,但是這時候它還沒有被銷毀,它的狀態被設置為Stopped;當新的Activity窗口即將要顯示時,它會通知WindowManagerService,這時候WindowManagerService就會處理當前處理Stopped狀態的Activity窗口了,要執行的操作就是銷毀它們了,在銷毀的時候,就會注銷它們之前所注冊的鍵盤消息接收通道。
新的Activity窗口通知WindowManagerService它即將要顯示的過程比較復雜,但是它與我們本節要介紹的內容不是很相關,因此,這里就略過大部過程了,我們從ActvitiyRecord的windowsVisible函數開始分析。注意,這里的ActivityRecord是新的Activity窗口在ActivityManangerService的代表,而那些處于Stopped狀態的Activity窗口
會放在ActivityStack類的一個等待可見的mWaitingVisibleActivities列表里面,事實于,對于那些Stopped狀態的Activity窗口來說,它們是等待銷毀,而不是等待可見。
像前面一樣,我們先來看一張應用程序注銷鍵盤消息接收通道的過程的序列圖,然后根據這個序列圖來詳細分析互一個步驟:

點擊查看大圖
Step 1. ActivityRecord.windowsVisible
這個函數定義在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);
}
}
......
}
}
}
......
}
應用程序中的每一個Activity在ActivityManagerService都有一個代表ActivityRecord,它們以堆棧的形式組織在ActivityManaerService中的ActivityStack中。一個即將要顯示,但是還沒有顯示的Activity,它在ActivityManagerService中的ActivityRecord的成員變量nowVisible為false,而成員變量idle為ture,表示這個即將要顯示的Activity窗口處于空閑狀態。因此,在上面的這個函數中,會執行下面的語句:
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);
}
前面我們說過,當用戶按下鍵盤上的Back鍵時,當前激活的Activity記錄就被放在ActivityStack對象stack的成員變量mWaitingVisibleActivities中了,這時候就要對它進行處理了。首先是將它們的Activity記錄的waitingVisible設置為false,然后就把它們從ActivityStack對象stack的成員變量mWaitingVisibleActivities清空,最后向ActivityStack對象stack發送一個ActivityStack.IDLE_NOW_MSG消息。這個消息最終是由ActivityStack類的activityIdleInternal函數來處理的。
Step 2. ActivityStack.activityIdleInternal
這個函數定義在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 {
......
}
}
}
......
}
......
}
這個函數首先會調用processStoppingActivitiesLocked函數把所有處于Stopped狀態的Activity取回來,然后逐個分析它們,如果它們的ActivityRecord中的finishing成員變量為true,就說明這個Activity需要銷毀了,于是,就調用finishCurrentActivityLocked函數來銷毀它們。
Step 3. ActivityStack.finishCurrentActivityLocked
這個函數定義在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中傳進來的參數mode為FINISH_IMMEDIATELY,并且這個即將要被銷毀的Activity的狀態為Stopped,因此,接下來就會調用destroyActivityLocked函數來銷毀它。
Step 4. ActivityStack.destroyActivityLocked
這個函數定義在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應用程序啟動過程源代碼分析中,我們說到,每一個應用程序進程在ActivityManagerService中,都ProcessRecord記錄與之對應,而每一個Activity,都是運行在一個進程上下文中,因此,在ActivityManagerService中,每一個ActivityRecord的app成員變量都應該指向一個ProcessRecord記錄,于是,這里得到的hadApp為true。在ProcessRecord類中,有一個成員變量thread,它的類型為IApplicationThread。在文章
Android應用程序啟動過程源代碼分析中,我們也曾經說過,每一個應用程序在啟動的時候,它都會在內部創建一個ActivityThread對象,而在這個ActivityThread對象中,有一個成員變量mAppThread,它的類型為ApplicationThread,這是一個Binder對象,專門用來負責在應用程序和ActivityManagerService之間執行進程間通信工作的。應用程序在啟動的時候,就會將這個Binder對象傳遞給ActivityManagerService,而ActivityManagerService就會把它保存在相應的ProcessRecord記錄的thread成員變量中。因此,ProcessRecord記錄的thread成員變量其實就是ApplicationThread對象的遠程接口,于是,執行下面這個語句的時候:
r.app.thread.scheduleDestroyActivity(r, r.finishing,
r.configChangeFlags);
就會進入到ApplicationThread類中的scheduleDestroyActivity函數來。
Step 5. ApplicationThread.scheduleDestroyActivity
這個函數定義在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);
}
......
}
......
}
這個函數調用外部類ActivityThread的queueOrSendMessage函數來往應用程序的消息隊列中發送一個H.DESTROY_ACTIVITY消息,這個消息最終由ActivityThread類的handleDestroyActivity函數來處理。
Step 6. ActivityThread.handleDestroyActivity
這個函數定義在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);
}
......
}
......
}
......
}
......
}
這里首先調用performDestroyActivity來執行一些銷毀Activity的操作,期間就會調用Activity的onDestroy函數讓Activity本身有機會執行一些銷毀前的工作了。這里通過r.activity.getWindowManager函數返回的是一個LocalWindowManager對象,而通過r.activity.mDecor得到的是一個DecorView對象,這些都是在Activity啟動的時候設置好的。函數最后調用LocalWindowManager對象wm的removeViewImmediate函員來從LocalWindowManager移除這個DecorView對象。
Step 7. LocalWindowManager.removeViewImmediate
這個函數定義在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對象,這個函數只是簡單地調用WndowManagerImpl類的removeViewImmediate來進一步處理。
Step 8. WndowManagerImpl.removeViewImmediate
這個函數定義在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);
......
}
}
......
}
這個函數首先是找到這個view所屬的ViewRoot對象root,然后調用這個root對象的die函數來銷毀它。
Step 9. ViewRoot.die
這個函數定義在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傳進來的immediate參數為true,因此,這里直接調用doDie函數來進一步處理。
Step 10. ViewRoot.doDie
這個函數定義在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();
}
}
}
......
}
當我們把Activity窗口中的View添加到一個ViewRoot對象時,就會把它的成員變量mAdded設置為true,這樣就表示這個ViewRoot中有View存在,于是,這里就會調用dispatchDetachedFromWindow函數來進一步處理。
Step 11. ViewRoot.ispatchDetachedFromWindow
這個函數定義在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) {
}
......
}
......
}
前面在介紹應用程序注冊鍵盤消息接收通道的過程時,在Step 18,我們說到,ViewRoot類中的mInputQueueCallback為null,表示由這個ViewRoot自己來管理鍵盤輸入事件,因此,這里首先會調用InputQueue的unregisterInputChannel函數來注銷注冊在應用程序這一側的Client端InputChannel,然后再調用sWindowSession的remove函數來注銷注冊在InputManager這一側的Server端InputChannel,這個邏輯是和前面介紹應用程序注冊鍵盤消息接收通道的邏輯相對應的,前面分別注冊了這兩個InputChannel,現在Activity要銷毀了,當然就要把它們注銷了。
我們先來看注銷注冊在應用程序這一側的Client端InputChannel,然后再回過頭來分析注銷注冊在InputManager這一側的Server端InputChannel。
Step 12. InputQueue.unregisterInputChannel
這個函數定義在frameworks/base/core/java/android/view/InputQueue.java文件中:
public final class InputQueue {
......
public static void unregisterInputChannel(InputChannel inputChannel) {
......
synchronized (sLock) {
......
nativeUnregisterInputChannel(inputChannel);
}
}
......
}
這個函數只是簡單地調用本地方法nativeUnregisterInputChannel來執行具體的操作。
Step 13. InputQueue.nativeUnregisterInputChannel
這個函數定義在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);
......
}
這里調用NativeInputQueue的成員函數unregisterInputChannel來進一步處理。
Step 14. NativeInputQueue.unregisterInputChannel
這個函數定義在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;
}
真正的注銷工作就是這里實現的了,讀者可以對照前面介紹應用程序注冊鍵盤消息接收通道過程中的Step 21(NativeInputQueue.registerInputChannel)來分析,它首先是將在之前創建的Connection對象從NativeInputQueue中的mConnectionByReceiveFd向量中刪除:
ssize_t connectionIndex = getConnectionIndex(inputChannel);
......
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
然后再把這個Client端InputChannel的前向管道的讀端文件描述符從應用程序主線程中的Looper對象中刪除:
connection->looper->removeFd(inputChannel->getReceivePipeFd());
這樣,這個Activity窗口以后就不會接收到鍵盤事件了。
最后將Connection對象中的回調對象inputHandlerOjbGlobal對象刪除:
env->DeleteGlobalRef(connection->inputHandlerObjGlobal);
connection->inputHandlerObjGlobal = NULL;
回憶一下前面我們在分析InputManager分發鍵盤消息給應用程序處理時,曾經說到,每當有鍵盤事件發生時,InputManager首先就會調用NativeInputQueue類的handleReceiveCallback函數。在這個handleReceiveCallback函數里面,NativeInputQueue會找到相應的Connection對象,然后把它里面的內部對象inputHandlerOjbGlobal作為參數來調用Java層的InputQueue類的dispatchKeyEvent函數來通知應用程序,有鍵盤事件發生了。在InputQueue類的dispatchKeyEvent函數里面,就是通過這個inputHandlerOjbGlobal對象來直正通知到當前激活的Activity窗口來處理這個鍵盤事件的。
注冊在應用程序這一側的Client端InputChannel被注銷以后,回到前面的Step 11中,我們繼續分析注銷注冊在InputManager這一側的Server端InputChannel。
Step 15. WindowManagerService.Session.remove
這個函數定義在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);
}
......
}
......
}
這個函數只是簡單地調用其外部類WindowManagerService的removeWindow函數來進一步執行操作。
Step 16. WindowManagerService.removeWindow
這個函數定義在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);
}
}
......
}
回憶一下前面我們在分析應用程序注冊鍵盤消息管道的過程時,在Step 11(WindowManagerService.addWindow)中,WindowManagerService為這個即將要激活的Activity窗口創建了一個WindowState對象win,創建的時候,使用了從ViewRoot中傳過來的兩個參數,分別是一個Session對象session和一個IWindow對象client。
在這個函數中,ViewRoot傳過來的兩個參數session和client和上面說的兩個參數是一致的,因此,這個函數首先通過參數session和client得到一個WindowState對象win,然后調用removeWindowLocked來把它從WindowManagerService刪除。
Step 17. WindowManagerService.removeWindowLocked
這個函數定義在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();
......
}
......
}
我們忽略了這個函數的其它邏輯,只關注注銷之前注冊的Server端InputChannel的邏輯,這里,注銷的操作就是調用win的disposeInputChannel進行的了。
Step 18. WindowState.disposeInputChannel
這個函數定義在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;
}
}
......
}
......
}
上面說到,在前面分析應用程序注冊鍵盤消息管道的過程時,在Step 11(WindowManagerService.addWindow)中,為當前這個Activity窗口創建了一個WindowState對象,接著創建了一個輸入管道后,把Server端的InputChannel保存了在這個WindowState對象的成員變量mInputChannel中,因此,這里,就可以把它取回來,然后調用mInputManager對象的unregisterInputChannel函數來把它注銷掉了。
Step 19. InputManager.unregisterInputChannel
這個函數定義在frameworks/base/services/java/com/android/server/InputManager.java文件中:
public class InputManager {
......
public void unregisterInputChannel(InputChannel inputChannel) {
......
nativeUnregisterInputChannel(inputChannel);
}
......
}
這個函數很簡單,它調用本地方法nativeUnregisterInputChannel來進一步處理。
Step 20. InputManager.nativeUnregisterInputChannel
這個函數定義在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);
......
}
這個函數首先調用android_view_InputChannel_getInputChannel函數根據Java層的InputChannel對象找到C++層的InputChannel對象,然后調用NativeInputManager的unregisterInputChannel函數來執行注銷的操作。
Step 21. NativeInputManager.unregisterInputChannel
這個函數定義在frameworks/base/services/jni/com_android_server_InputManager.cpp文件中:
status_t NativeInputManager::unregisterInputChannel(JNIEnv* env,
const sp<InputChannel>& inputChannel) {
......
return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel);
}
這個函數與前面分析應用程序注冊鍵盤消息通道的Step 17(NativeInputManager.registerInputChannel)相對應,主要是調用InputDispatcher對象的unregisterInputChannel函數來執行真正注銷的操作。
Step 22. InputDispatcher.unregisterInputChannel
這個函數定義在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注銷應用程序一側的Client端InputChannel是差不多的,只不過這里是從InputDispatcher中把Server端的InputChannel注銷掉。首先是根據傳進來的參數inputChannel找到它在InputDispatcher中對應的Connection對象在mConnectionsByReceiveFd中的索引,然后把它從mConnectionsByReceiveFd中刪除:
ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
......
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
最后,還需要把這個InputChannel中的反向管道讀端文件描述符從InputDispatcher的內部對象mLooper中刪除,因為這個文件描述符是在前面注冊Server端的InputChannel時加入到mLooper對象去的,具體可以參考上面分析應用程序注冊鍵盤消息接收通道的過程中的Step 18(InputDispatcher.registerInputChannel)。
這樣, 應用程序注銷鍵盤消息接收通道的過程就分析完成了,整個應用程序鍵盤消息處理機制也分析完成了,這是一個比較復雜的過程,要完全理解它還需要花費一些努力和時間,不過,理解了這個過程之后,對Android應用程序框架層的理解就更進一步了。
作者:Luoshengyang 發表于2011-10-24 0:59:47
原文鏈接
posted on 2012-04-17 21:32
mixer-a 閱讀(4896)
評論(0) 編輯 收藏