在开始本文之前,先回顾下 Android 11 输入系统之InputDispatcher和应用窗口建立联系一文的知识点
- systemserver进程创建socketpair,得到两个文件句柄分别放入InputChanel中
-其中 一个InputChanel通过binder传给应用端,应用端拿到之后,创建WindowInputEventReceiver对象,该对象继承自InputEventReceiver,在InputEventReceiver的构造方法中,会去将InputChanel中的fd注册到looper中 - 另一个InputChanel给InputDispatcher线程,InputDispatcher线程也会将InputChanel的fd注册到looper中
- 后续通过这两个fd进行socket通讯
monitorInput 的使用
以系统源码中的PointerEventDispatcher为例
//frameworks\base\services\core\java\com\android\server\wm\DisplayContent.java
private final PointerEventDispatcher mPointerEventDispatcher;
final InputChannel inputChannel = mWmService.mInputManager.monitorInput("PointerEventDispatcher" + mDisplayId, mDisplayId);//1
mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);//2
注释1处调用monitorInput,得到一个InputChannel 对象。注释2处,PointerEventDispatcher类是继承自InputEventReceiver的,在InputEventReceiver的构造函数中,会将得到的InputChannel 对象中的fd添加到looper中,这部分内容可以去看文章开头提到的那篇文章。接下来看看注释1处monitorInput的内部实现
monitorInput 内部实现
monitorInput的实现在IMS中
//frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
public InputChannel monitorInput(String inputChannelName, int displayId) {//省略InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);//1nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, false /*isGestureMonitor*/);//2inputChannels[0].dispose(); // don't need to retain the Java object referencereturn inputChannels[1];//3}
注释1处,内部会创建socketpair得到两个fd,并分别放入两个InputChanel中,注释2处调用nativeRegisterInputMonitor去注册inputChannels[0],注释3处将inputChannels[1]返回给该方法的调用者。
nativeRegisterInputMonitor
//frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
static void nativeRegisterInputMonitor(JNIEnv* env, jclass /* clazz */,jlong ptr, jobject inputChannelObj, jint displayId, jboolean isGestureMonitor) {NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,inputChannelObj);//省略status_t status = im->registerInputMonitor(env, inputChannel, displayId, isGestureMonitor);//省略
}status_t NativeInputManager::registerInputMonitor(JNIEnv* /* env */,const sp<InputChannel>& inputChannel, int32_t displayId, bool isGestureMonitor) {ATRACE_CALL();return mInputManager->getDispatcher()->registerInputMonitor(inputChannel, displayId, isGestureMonitor);
}
InputDispatcher::registerInputMonitor
//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
status_t InputDispatcher::registerInputMonitor(const sp<InputChannel>& inputChannel,int32_t displayId, bool isGestureMonitor) {{ // acquire lockstd::scoped_lock _l(mLock);//省略sp<Connection> connection = new Connection(inputChannel, true /*monitor*/, mIdGenerator);//创建connection const int fd = inputChannel->getFd();mConnectionsByFd[fd] = connection;//添加进mConnectionsByFdmInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel;auto& monitorsByDisplay =isGestureMonitor ? mGestureMonitorsByDisplay : mGlobalMonitorsByDisplay;//传入的是falsemonitorsByDisplay[displayId].emplace_back(inputChannel);//放入mGlobalMonitorsByDisplaymLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);//添加进looper}// Wake the looper because some connections have changed.mLooper->wake();return OK;
}
除了创建Connection对象,并将fd添加进looper中外,还会将该inputChannel添加进mGlobalMonitorsByDisplay链表中。到这monitorInput的工作就完成了。
后续事件分发的时候,要将事件分发给这些connection,还需要将connection对应的inputChannel添加进inputTargets中,来看一下这个过程
添加到inputTargets
//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, MotionEntry* entry,DropReason* dropReason, nsecs_t* nextWakeupTime) {//省略if (isPointerEvent) {// Pointer event. (eg. touchscreen)injectionResult =findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,&conflictingPointerActions);//查找应用窗口} else {// Non touch event. (eg. trackball)injectionResult =findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);}//省略// Add monitor channels from event's or focused display.addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
//省略
addGlobalMonitoringTargetsLocked
//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,int32_t displayId, float xOffset,float yOffset) {std::unordered_map<int32_t, std::vector<Monitor>>::const_iterator it =mGlobalMonitorsByDisplay.find(displayId);if (it != mGlobalMonitorsByDisplay.end()) {const std::vector<Monitor>& monitors = it->second;for (const Monitor& monitor : monitors) {addMonitoringTargetLocked(monitor, xOffset, yOffset, inputTargets);}}
}
可以看出,addGlobalMonitoringTargetsLocked就是遍历mGlobalMonitorsByDisplay链表,然后调用addMonitoringTargetLocked将其元素加入到inputTargets中。加入到inputTargets后,后面的事件分发流程参考Android11 InputDispatcher 分发事件流程分析一文
回到PointerEventDispatcher类中,当有事件产生的时候,会调用其onInputEvent方法。可以总结出:并不一定需要窗口也能接收到输入事件。
monitorInput在系统源码中的应用
在调试Input模块的时候,经常需要借助于开发者选项中的指针位置,打开开发者选项的指针位置开关,触摸屏幕的时候,同时会在屏幕上画出线条,我们来看下其接收触摸事件的过程
打开指针位置的开关,实际上操作的是Settings数据库中的POINTER_LOCATION
@Overridepublic boolean onPreferenceChange(Preference preference, Object newValue) {final boolean isEnabled = (Boolean) newValue;Settings.System.putInt(mContext.getContentResolver(),Settings.System.POINTER_LOCATION, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF);return true;
}
而在WMS中,对其状态进行了监听,有数据变化时,调用updatePointerLocation方法
//frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
void updatePointerLocation() {ContentResolver resolver = mContext.getContentResolver();final boolean enablePointerLocation = Settings.System.getIntForUser(resolver,Settings.System.POINTER_LOCATION, 0, UserHandle.USER_CURRENT) != 0;if (mPointerLocationEnabled == enablePointerLocation) {return;}mPointerLocationEnabled = enablePointerLocation;synchronized (mGlobalLock) {final PooledConsumer c = PooledLambda.obtainConsumer(DisplayPolicy::setPointerLocationEnabled, PooledLambda.__(),mPointerLocationEnabled);mRoot.forAllDisplayPolicies(c);c.recycle();}}
setPointerLocationEnabled
//frameworks\base\services\core\java\com\android\server\wm\DisplayPolicy.java
void setPointerLocationEnabled(boolean pointerLocationEnabled) {if (!supportsPointerLocation()) {return;}mHandler.sendEmptyMessage(pointerLocationEnabled? MSG_ENABLE_POINTER_LOCATION : MSG_DISABLE_POINTER_LOCATION);
}
对于打开指针位置开关,发送MSG_ENABLE_POINTER_LOCATION 消息。接收到消息后,调用enablePointerLocation处理
private void enablePointerLocation() {mPointerLocationView = new PointerLocationView(mContext);//新建PointerLocationView,mPointerLocationView.setPrintCoords(false);final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.MATCH_PARENT);lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE//设置了不能就收触摸事件的flag| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;lp.setFitInsetsTypes(0);lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;if (ActivityManager.isHighEndGfx()) {lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;lp.privateFlags |=WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;}lp.format = PixelFormat.TRANSLUCENT;lp.setTitle("PointerLocation - display " + getDisplayId());lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;final WindowManager wm = mContext.getSystemService(WindowManager.class);wm.addView(mPointerLocationView, lp);//添加viewmDisplayContent.registerPointerEventListener(mPointerLocationView);//1}
可以看出,打开指针位置实际上是在顶层添加了一个view,该view设置了不能接收触摸事件,但是我们在触摸屏幕的时候,确实是画出了线条,说明是接收到了触摸事件的,这是怎么回事?要搞懂这个,继续看注释1处的registerPointerEventListener方法
//frameworks\base\services\core\java\com\android\server\wm\DisplayContent.java
//monitorInput的使用
private final PointerEventDispatcher mPointerEventDispatcher;
final InputChannel inputChannel = mWmService.mInputManager.monitorInput("PointerEventDispatcher" + mDisplayId, mDisplayId);//1
mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);//2//registerPointerEventListener
void registerPointerEventListener(@NonNull PointerEventListener listener) {mPointerEventDispatcher.registerInputEventListener(listener);
}//frameworks\base\services\core\java\com\android\server\wm\PointerEventDispatcher.java
public void registerInputEventListener(PointerEventListener listener) {synchronized (mListeners) {//省略mListeners.add(listener);mListenersArray = null;}}
将传进来的listener添加到mListeners容器中。当有事件到来时,调用PointerEventDispatcher的onInputEvent方法
//frameworks\base\services\core\java\com\android\server\wm\PointerEventDispatcher.java
@Overridepublic void onInputEvent(InputEvent event) {try {if (event instanceof MotionEvent&& (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {final MotionEvent motionEvent = (MotionEvent) event;PointerEventListener[] listeners;synchronized (mListeners) {if (mListenersArray == null) {mListenersArray = new PointerEventListener[mListeners.size()];mListeners.toArray(mListenersArray);}listeners = mListenersArray;}for (int i = 0; i < listeners.length; ++i) {listeners[i].onPointerEvent(motionEvent);//取出listener,调用其onPointerEvent方法}}} finally {finishInputEvent(event, false);}}
注册listener时传入的是mPointerLocationView(PointerLocationView对象),而PointerLocationView实现了PointerEventListener,即当有事件到来时调用其onPointerEvent方法
@Overridepublic void onPointerEvent(MotionEvent event) {final int action = event.getAction();//画出线条}
可以看出,PointerLocationView能够接收到输入事件,也是利用了monitorInput机制。
总结与思考
monitorInput内部实现原理其实是和开头提到的那篇文章的原理是一样的。只是该InputChanel添加到InputTargets中是无条件的,即无论如何都能接收到输入事件。那我们是不是可以通过monitorInput,实现在后台service中得到输入事件,进而做自己的操作?比如实现点击屏幕某个区域位置,进入工厂菜单之类的。