在前面的小節中,我們以按鍵為例,深入的講解了android的輸入系統,該小節講Reader線程對觸摸屏的相關處理。下面是兩個reader線程的流程圖,分别針對按鍵與觸摸屏:
針對按鍵:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPR1kMjpXT4FkeNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1AjMwUTO1MTMxIzMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
針對觸摸屏:
架構分析
如果有興趣,大家可以詳細的分析一下源代碼,該處隻做簡單的介紹:從前面的學習中,我們知道Reader線程是一個大的循環,這個大循環中不停的從輸入裝置中讀取資料,這個工作由evenHub.cpp完成,我們打開inputReader.cpp,找到LooOnce函數,
void InputReader::loopOnce() {
/*擷取資料*/
mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE)
他是如何擷取資料的呢?在前面的小節我們分析過,會使用epoll_wait與inotify監測/dev/input目錄,主要分為分為兩大類:
1.有輸入裝置接入或者拔出(增加或者減少節點):
a.增加裝置節點,會構造一個newDevice的類,用來表示這個裝置,該類包含呢open,ioctol(如 讀取kl,idc,kcm配置檔案)等。
2.節點有資料能夠被讀取(有裝置上報輸入事件):
在這裡通過mEventHub->getEvents擷取的都是事件,也就是說他還沒有處理,如當有裝置接入時,就能獲得這個事件,但是獲得之後需要怎麼處理呢?可能處理方式如下:
1.添加裝置,用于epoll_wait監測:調用addDeviceLocked構造InputDevice對象,檢視inputReader.cpp源代碼:
void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
inputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),controllerNumber, identifier, classes);
// Keyboard-like devices.
if (keyboardSource != 0) {
device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
}
// Touchscreens and touchpad devices.
if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
device->addMapper(new MultiTouchInputMapper(device));
從上面我們可以看到,首先device = new InputDevice,建立一個裝置如果是按鍵,調用的是:
如果是觸摸屏調用的是:
分别添加一個按鍵裝置,觸摸屏裝置。這裡的KeyboardInputMapper與 MultiTouchInputMapper至關重要,他們将是處理資料的核心。
2.移除裝置,從epoll_wait中移除,不再對其進行監測。
3.當epoll_wait監測到有資料之後,即擷取資料
然後做簡單的處理,那麼他是怎麼處理資料的呢?回到InputReader::loopOnce方法:
void InputReader::loopOnce() {
/*擷取事件*/
mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
/*處理事件*/
processEventsLocked(mEventBuffer, count);
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
device->process(rawEvents, count);
該處的device就是前面構造的device = new InputDevice,之前提到過,其内包含了open,ioctlr等。那麼device->process(rawEvents, count);是如何處理的呢?:
device->process(rawEvents, count);
mapper->process(rawEvent);
這裡的maper就是前面的:
device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
device->addMapper(new MultiTouchInputMapper(device));
是以說KeyboardInputMapper,MultiTouchInputMapper是與硬體通信的核心。下面我們把重點放在觸摸屏的mapper->process(rawEvent)上面,來進行進一步分析。
觸摸屏相關
我們在inputReader.cpp中可以找到MultiTouchInputMapper::MultiTouchInputMapper方法。在分析源碼之前,我們想象一下Reader線程會做什麼事情:
大家都知道觸摸屏和我們的顯示屏是兩個裝置,不一定相同,那麼他肯定有一個轉化過程
1.觸摸屏的坐标不等于LCD的坐标,如觸摸屏的分辨率高于顯示屏的分辨率,我們需要按照一定比例進行轉換。
2.對于觸摸屏,在浏覽圖檔的時候,我們是可以使用兩個手指對其進行縮放的,這些就是對手勢的識别,這是APP的工作,輸入系統隻是上報那些點。
根據分析,MultiTouchInputMapper要做的事情為,1.記錄原始資料。2.處理(轉換坐标)3.發送給Dispatcher線程。
下面我們分析源代碼,假設我們的手指同時畫了兩條線,在我們的觸摸屏gslx680.c檔案中,可以找到函數report_data中的以下内容:
input_report_abs(ts->input, ABS_MT_PRESSURE, id);
input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, 1);
input_report_abs(ts->input, ABS_MT_POSITION_X, x);
input_report_abs(ts->input, ABS_MT_POSITION_Y, y);
input_mt_sync(ts->input);
這是上報一個點,report_data函數被循環調用,上報多個點,最後調用input_sync(ts->input)同步所有點,即代表此時刻上報完成,下一個時刻也是同樣原理。
我們找到:
/*該函數會被循環執行*/
void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
TouchInputMapper::process(rawEvent);//該函數對于我們的觸摸屏機會沒有作用
/*下面三個函數,對于我們的觸摸屏沒有任何作用*/
mCursorButtonAccumulator.process(rawEvent);/*處理滑鼠按鍵*/
mCursorScrollAccumulator.process(rawEvent);/*處理滑鼠滾輪*/
mTouchButtonAccumulator.process(rawEvent);/*處理觸摸屏按鍵*/
/*遇到SYN_REPORT,進行同步,代表同一時刻已經上報完成*/
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
sync(rawEvent->when);
/*這是一個虛函數,其實作為void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {*/
syncTouch(when, next);
int32_t trackingId = inSlot->getTrackingId();//獲得一個ID
mPointerIdBits = newPointerIdBits;//如果沒有ID則配置設定一個
mMultiTouchMotionAccumulator.process(rawEvent);
} else {
Slot* slot = &mSlots[mCurrentSlot];
switch (rawEvent->code) {
case ABS_MT_POSITION_X:
slot->mInUse = true;
slot->mAbsMTPositionX = rawEvent->value;
break;
.......
........
case ABS_MT_WIDTH_MINOR:
slot->mInUse = true;
slot->mAbsMTWidthMinor = rawEvent->value;
slot->mHaveAbsMTWidthMinor = true;
break;
case ABS_MT_ORIENTATION:
slot->mInUse = true;
slot->mAbsMTOrientation = rawEvent->value;
break;
}
} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
mCurrentSlot += 1;
/*對資料進行處理,坐标轉換等等*/
processRawTouches(false /*timeout*/);
cookAndDispatch(mCurrentRawState.when);
cookPointerData();//把觸摸屏坐标轉化為lcd坐标
/*分發給dispatch線程*/
dispatchTouches(when, policyFlags);
/* Copy current touch to last touch in preparation for the next cycle:把目前的值記錄為上一次的值。
其目的是為了,如果驅動沒有上報id,隻能根據這個資料才能給新的觸電配置設定id*/
mLastRawState.copyFrom(mCurrentRawState);
mLastCookedState.copyFrom(mCurrentCookedState);
這就是一個總的分析流程。