Android native进程间通信实例-binder篇之——解决实际问题inputreader内建类清楚缓存
我在实际开发中,遇到一个问题,在电容屏驱动中没有发送input_sync 给上层,导致电容屏有的数据缓存在inputreader 中,会导致系统一系列奇怪问题发生,
至于为什么驱动不发送input_sync ,是因为项目某个功能的框架没有搭好导致的,总之这次不能从驱动中解决这个问题,这次为了弥补这个过失,
就需要在特定的情况下强制把电容屏在inputreader 的缓存清除,好了,这次binder 又要闪亮登场了!
1. 熟悉Inputreader 源码获取清除缓存接口
说实话,没有具体跟踪调试过这部分源码,直接从0开始生硬的看代码确实费力,我尽量简洁地说这块源码,以及我是如何找到清除缓存的接口的。
a. 首先把 frameworks\native\services\inputflinger 这部分代码添加到sourceinsight 中。
b. 只需要大致明白,EventHub.cpp 是直接获取驱动报上来的原始数据,然后InputReader.cpp 对这份数据进行处理保存在一段缓存队列中,InputDispatcher.cpp
从队列中取数据,再发送给上层窗口等等。
所以,只需要阅读InputReader.cpp 代码即可,因为要清空的缓存就在其中。
怎么办,这个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 值保持一致即可。
如果觉得代码或者实现的方式有什么不妥的地方请多多指教,谢谢。
希望大家多多吐槽,大家一起共同进步!!