Android系統層的input裝置解析
轉載說明:
這邊文章正好和http://blog.csdn.net/zangcf/article/details/51129867轉載互補,另一個角度講述輸入系統。
前言:這篇從2011年寫到2012年,呵呵,2012來臨了,祝大家新年快樂,心想事成。
上一篇從linux核心角度分析input驅動,那麼android怎麼獲取input資訊呢?本文重點討論這個話題。
在Java層,處理input型別訊息在InputManager.java檔案裡,當然首先要找到源頭,即InputManager類由誰來建立?在WindowManagerService.java這個介面視窗管理服務檔案裡。
WindowManagerService類的建構函式:
private WindowManagerService(Context context, PowerManagerService pm,
boolean haveInputMethods, boolean showBootMsgs) {
…..
mInputManager = new InputManager(context, this);
…..
mInputManager.start();
.….
}
…..省掉與InputManager無關的部分,建立InputManager物件,然後呼叫其start方法來監控input事件。
進入InputManager.java檔案,先看看它的建構函式,之後分析start方法。
public InputManager(Context context, WindowManagerService windowManagerService) {
this.mContext = context;
this.mWindowManagerService = windowManagerService;
this.mCallbacks = new Callbacks();//建立回撥物件
Looper looper = windowManagerService.mH.getLooper();//建立looper
Slog.i(TAG, "Initializing input manager");
nativeInit(mContext, mCallbacks, looper.getQueue());//呼叫本地方法nativeInit來//進行C++層的初始化操作
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);//watchdog監視器
}
在這裡,重點關注nativeInit。進入C++層,在com_android_server_InputManager.cpp檔案:
static JNINativeMethod gInputManagerMethods[] = {
/* name, signature, funcPtr */
{ "nativeInit", "(Landroid/content/Context;"
"Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
(void*) android_server_InputManager_nativeInit },// nativeInit與java層的關//聯
{ "nativeStart", "()V",
(void*) android_server_InputManager_nativeStart },// nativeStart與java層的關聯
………..
}
在這裡,分析android_server_InputManager_nativeInit函式.
static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
jobject contextObj, jobject callbacksObj, jobject messageQueueObj) {
// gNativeInputManager為空,便建立物件.
if (gNativeInputManager == NULL) {
sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);// sp<Looper>: Looper類強指標
gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper);//建立NativeInputManager物件
} else {
LOGE("Input manager already initialized.");
jniThrowRuntimeException(env, "Input manager already initialized.");
}
}
通過android_server_InputManager_nativeInit函式完成Looper和NativeInputManager初始化。
重點關注NativeInputManager類建構函式:
NativeInputManager::NativeInputManager(jobject contextObj,
jobject callbacksObj, const sp<Looper>& looper) :
mLooper(looper) {
JNIEnv* env = jniEnv();
mContextObj = env->NewGlobalRef(contextObj);
mCallbacksObj = env->NewGlobalRef(callbacksObj);
{
AutoMutex _l(mLock);
mLocked.displayWidth = -1;
mLocked.displayHeight = -1;
mLocked.displayExternalWidth = -1;
mLocked.displayExternalHeight = -1;
mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
}
sp<EventHub> eventHub = new EventHub();//new一個EventHub物件
mInputManager = new InputManager(eventHub, this, this);//建立InputManager物件
}
這個函式建立一個EventHub物件,然後把它作為引數來建立InputManager物件。特別注意,InputManager是在C++裡,具體在InputManager.cpp裡。EventHub類在EventHub.cpp裡,這個類和input事件獲取有關。
首先是去InputManager.cpp檔案,下面是InputManager類的建構函式:
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變數中。注意eventHub和mDispatcher都作為引數建立InputReader物件。後面還用initialize來初始化。下面是initialize函式的定義:
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
它建立兩個執行緒,一個是InputReaderThread執行緒,負責input事件的獲取;另一個是InputDispatcherThread執行緒,負責input訊息的傳送。
先回頭解決在開始InputManager.java的mInputManager.start()這個start方法。看究竟怎麼啟動。
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart();//呼叫本地方法nativeStart
…..
}
重點關注nativeStart。進入C++層,在com_android_server_InputManager.cpp檔案:
static JNINativeMethod gInputManagerMethods[] = {
/* name, signature, funcPtr */
{ "nativeInit", "(Landroid/content/Context;"
"Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
(void*) android_server_InputManager_nativeInit },// nativeInit與java層的關//聯
{ "nativeStart", "()V",
(void*) android_server_InputManager_nativeStart },// nativeStart與java層的關聯
………..
}
關注android_server_InputManager_nativeStart函式:
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.");
}
先看getInputManager:
inline sp<InputManager> getInputManager() const { return mInputManager; }
getInputManager是InputManager類,它的start方法繼承InputManager類的方法(注意是InputManager.cpp的InputManager類)。
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這兩個執行緒。前面提到了建立這兩個執行緒。也建立了InputDispatcher和InputReader物件。下面就這兩個物件做分解.
下面是class InputDispatcherThread : public Thread {
public:
explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
~InputDispatcherThread();
private:
virtual bool threadLoop();
sp<InputDispatcherInterface> mDispatcher;
};
由於它是Thread子類,於是繼承它的run方法,進入run方法後會呼叫threadLoop(),在Thread類中它是虛擬函式,得由子類來複寫,如下所示:
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
啟動mDispatcher->dispatchOnce();
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
dispatchOnceInnerLocked(&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();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
函式功能:dispatchOnceInnerLocked函式處理input輸入訊息,mLooper->pollOnce是等待下一次輸入事件。
mLooper->pollOnce(timeoutMillis):
這個請看Looper.cpp檔案中的Looper::pollOnce()函式。Looper裡主要通過linux管道方式實現程序間通訊,通過epoll機制實現外界事件請求作出響應。
接著,來分析InputReaderThread的啟動。
class InputReaderThread : public Thread {
public:
InputReaderThread(const sp<InputReaderInterface>& reader);
virtual ~InputReaderThread();
private:
sp<InputReaderInterface> mReader;
virtual bool threadLoop();//loop
};
在這裡直接到InputReader.cpp檔案
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
往下走,
void InputReader::loopOnce() {
int32_t timeoutMillis;
{ // acquire lock
…….
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
……
}
這個函式主要通過EventHub的getEvents方法來獲取input事件。
接下來進入到EventHub.cpp檔案。一睹getEvents方法。
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;
}
}
}
#if HAVE_INOTIFY
// readNotify() will modify mFDs and mFDCount, so this must be done after
// processing all other events.
if(mFDs[0].revents & POLLIN) {
readNotify(mFDs[0].fd);
mFDs[0].revents = 0;
continue; // report added or removed devices immediately
}
#endif
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函式來開啟系統輸入裝置。打開了這些輸入裝置檔案後,就可以對這些輸入裝置進行是監控了。如果不是第一次進入到這個函式,那麼就會分析當前有沒有input事件發生,如果有,就返回這個事件,否則就會進入等待狀態,等待下一次input事件的發生。這個函式很關鍵,上次分析input核心驅動,通過這個函式讀取(開啟裝置檔案形式)由input驅動傳送過來的事件資訊。
這一節分解到這裡,下回再分解。