Android native進程間通訊實例-binder篇之——解決實際問題inputreader內建類清楚緩存

我在實際開發中,遇到一個問題,在電容屏驅動中沒有發送input_sync 給上層,致使電容屏有的數據緩存在inputreader 中,會致使系統一系列奇怪問題發生,android

至於爲何驅動不發送input_sync ,是由於項目某個功能的框架沒有搭好致使的,總之此次不能從驅動中解決這個問題,此次爲了彌補這個過失,shell

就須要在特定的狀況下強制把電容屏在inputreader 的緩存清除,好了,此次binder 又要閃亮登場了!緩存

 

1. 熟悉Inputreader 源碼獲取清除緩存接口app

說實話,沒有具體跟蹤調試過這部分源碼,直接從0開始生硬的看代碼確實費力,我儘可能簡潔地說這塊源碼,以及我是如何找到清除緩存的接口的。框架

a. 首先把 frameworks\native\services\inputflinger 這部分代碼添加到sourceinsight 中。函數

 

b. 只須要大體明白,EventHub.cpp 是直接獲取驅動報上來的原始數據,而後InputReader.cpp 對這份數據進行處理保存在一段緩存隊列中,InputDispatcher.cppui

從隊列中取數據,再發送給上層窗口等等。this

因此,只須要閱讀InputReader.cpp 代碼便可,由於要清空的緩存就在其中。spa

怎麼辦,這個cpp 有7千多行代碼,不一樣android 版本說不定有8千多行呢?設計

別慌!粗略的看一下,發現有個類叫作 MultiTouchInputMapper ,電容屏不就是多點觸控麼,直接添加相關調試log, 能夠清楚這塊調用流程。總之,MultiTouchInputMapper 

裏面有個重要的實現叫作 void MultiTouchInputMapper::reset(nsecs_t when) ,就是它會清空緩存。

 

c. 熟悉代碼後發現 MultiTouchInputMapper 與 InputMapper 有密切的關係,若是實在以爲看代碼嫌煩,直接搜出全部的reset ,能夠發現void InputDevice::reset(nsecs_t when) 最終會掉進

MultiTouchInputMapper 裏面,感受這就是惟一的通路了,雖然會誤傷到SingleTouchInputMapper ,可是對項目沒有影響就無所謂了,畢竟SingleTouchInputMapper 中也沒有什麼數據好清空,若是對個人設計思想有異議請大膽說出來吧!

誤傷SingleTouchInputMapper 的InputDevice::reset 代碼以下,若是不想誤傷能夠把下面實現進行修改,或者只調用MultiTouchInputMapper 的reset 接口也行,我這麼作主要是懼怕只清空一部分不能解決問題,因此後續調試決定統一清空。

void InputDevice::reset(nsecs_t when) {
    size_t numMappers = mMappers.size();
    for (size_t i = 0; i < numMappers; i++) {
        InputMapper* mapper = mMappers[i];
        mapper->reset(when);
    }

    mContext->updateGlobalMetaState();

    notifyReset(when);
}

  

d. 下一步就是搜尋InputDevice 這個類了,能夠很快找到調用的方式。

首先聲明獲取device: InputDevice* device = mDevices.valueAt(deviceIndex);

而後清空數據:device->reset(when); 

 

e. 添加處理代碼

void InputReader::clearCTPData(nsecs_t when, int32_t deviceId) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    if (deviceIndex < 0) {
        ALOGW("Discarding event for unknown deviceId %d.", deviceId);
        return;
    }

    InputDevice* device = mDevices.valueAt(deviceIndex);
    if (device->isIgnored()) {
        //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return;
    }
    
    device->reset(when);
}

  

發現裏面有個參數比較陌生,deviceId, 調試過input 設備的朋友應該清楚,在adb shell 下輸入getevent 就會冒出好多掛載的input 設備信息,其中就包括了deviceId,固然要用代碼獲取也是能夠的,這部分下一節討論。

 

2. 添加binder 服務

由上面添加的clearCTPData 這個接口可知,這個處理是在InputReader 類裏面新加的一個方法。調用它就須要有一個指針指向當前的InputReader ,好的,有了這個想法就開始寫代碼吧。

首先在InputReader.cpp 中InputReader::InputReader 的構造函數中添加我們的binder 指針,binder 調用ontransct的類服務也須要重寫一下,就命名爲MyInputreaderService 吧。

 

分三步走,

第一部:

把clearCTPData的代碼添加到InputReader.cpp 和 InputReader.h 中,代碼剛纔有貼過,聲明直接放在class InputReader 裏面便可。

 

第二部 :

InputReader.cpp 中在構造函數裏面添加binder 的服務,代碼以下:

InputReader::InputReader(const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& policy,
        const sp<InputListenerInterface>& listener) :
        mContext(this), mEventHub(eventHub), mPolicy(policy),
        mGlobalMetaState(0), mGeneration(1),
        mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
        mConfigurationChangesToRefresh(0) {
    mQueuedListener = new QueuedInputListener(listener);

    { // acquire lock
        AutoMutex _l(mLock);

        sp<IBinder> sendBinder = new ByInputreaderService(this);

        defaultServiceManager()->addService(String16("my.inputreader"), sendBinder);


        refreshConfigurationLocked(0);
        updateGlobalMetaStateLocked();
    } // release lock
}

  

第三部:

完成ByInputreaderService 的功能,我主要借鑑getevent的源碼作了一個簡單的獲取deviceID的功能,同時用InputReader 構造函數中傳入的this 來搞事情(調用clearCTPData)

class ByInputreaderService : public BBinder {
public:
	InputReader *parent;
	int mCTPDeviceId;
	nsecs_t mWhen;
	
	ByInputreaderService(InputReader *p) : parent(p)
	{
		mCTPDeviceId = 1;
		getCtpFd();
	}

	~ByInputreaderService()
	{

	}

	int scan_input_device(int fd)
	{
		char name[80];
		name[sizeof(name) - 1] = '\0';

		if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
			ALOGI("could not get device name for %s\n", strerror(errno));
			name[0] = '\0';
		}

		ALOGI("getCtpFd device name is %s\n", name);
		if(strcmp("cyttsp5_mt", name) == 0)
		{
			ALOGI("getCtpFd cyttsp5_mt !!!\n");
			return 0;
		}

		return -1;
	}

	int getCtpFd(void)
	{
		int fd = 0;
		int device_type = 0;
		char devname[50];
		char *filename;
		DIR *dir;
		struct dirent *de;
		dir = opendir("/dev/input");
		if(dir == NULL)
		return -1;

		strcpy(devname, "/dev/input");
		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);

			fd = open(devname, O_RDWR);
			if (fd < 0) 
			{
				ALOGI("[getCtpFd] open device failed! path: %s\n",devname);
				continue;
			}
			device_type = scan_input_device(fd);
			if(device_type != 0)
			{
				mCTPDeviceId ++;
				close(fd);
				ALOGI("[getCtpFd] scan device failed! path: %s -- %d\n",devname, device_type);
				continue;
			}
			else
			{
				ALOGI("[getCtpFd] scan device success! path: %s -- %d\n",devname, device_type);
				break;
			}
		}
		closedir(dir);

		close(fd);
		return fd;    
	}


	status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
	{
		ALOGI("ByInputreaderService %d \n", code);

		if(CLEARINPUTDATA == code)
		{
			mWhen = systemTime(SYSTEM_TIME_MONOTONIC);
			parent->clearCTPData(mWhen, mCTPDeviceId);	        
		}
		return NO_ERROR;
	}


};

  

CLEARINPUTDATA 這個宏隨便定義,總之客戶端要和這個code 值保持一致便可。

 

若是以爲代碼或者實現的方式有什麼不妥的地方請多多指教,謝謝。

但願你們多多吐槽,你們一塊兒共同進步!!

相關文章
相關標籤/搜索