天天看點

06.輸入系統:第10課第23節_輸入系統_多點觸摸驅動程式_Reader線程

在前面的小節中,我們以按鍵為例,深入的講解了android的輸入系統,該小節講Reader線程對觸摸屏的相關處理。下面是兩個reader線程的流程圖,分别針對按鍵與觸摸屏:

針對按鍵:

06.輸入系統:第10課第23節_輸入系統_多點觸摸驅動程式_Reader線程

針對觸摸屏:

06.輸入系統:第10課第23節_輸入系統_多點觸摸驅動程式_Reader線程

架構分析

如果有興趣,大家可以詳細的分析一下源代碼,該處隻做簡單的介紹:從前面的學習中,我們知道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);
 	
           

這就是一個總的分析流程。

繼續閱讀