我在實際開發中,遇到一個問題,在電容屏驅動中沒有發送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 值保持一致便可。
若是以爲代碼或者實現的方式有什麼不妥的地方請多多指教,謝謝。
但願你們多多吐槽,你們一塊兒共同進步!!