Android應用程式鍵盤(Keyboard)訊息處理機制分析
在Android系統中,鍵盤按鍵事件是由WindowManagerService服務來管理的,然後再以訊息的形式來分發給應用程式處理,不過和普通訊息不一樣,它是由硬體中斷觸發的;在上一篇文章《Android應用程式訊息處理機制(Looper、Handler)分析》中,我們分析了Android應用程式的訊息處理機制,本文將結合這種訊息處理機制來詳細分析Android應用程式是如何獲得鍵盤按鍵訊息的。
《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!
在系統啟動的時候,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)的啟動過程原始碼分析
瞭解了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函式的實現。<