在开始本文之前,先回顾下 Android 11 输入系统之InputDispatcher和应用窗口建立联系一文的知识点
- systemserver进程创建socketpair,得到两个文件句柄分别放入InputChanel中
-其中 一个InputChanel通过binder传给应用端,应用端拿到之后,创建WindowInputEventReceiver对象,该对象继承自InputEventReceiver,在InputEventReceiver的构造方法中,会去将InputChanel中的fd注册到looper中 - 另一个InputChanel给InputDispatcher线程,InputDispatcher线程也会将InputChanel的fd注册到looper中
- 后续通过这两个fd进行socket通讯
monitorInput 的使用
以系统源码中的PointerEventDispatcher为例
c
//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中
c
//frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
public InputChannel monitorInput(String inputChannelName, int displayId) {
//省略
InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);//1
nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, false /*isGestureMonitor*/);//2
inputChannels[0].dispose(); // don't need to retain the Java object reference
return inputChannels[1];//3
}
注释1处,内部会创建socketpair得到两个fd,并分别放入两个InputChanel中,注释2处调用nativeRegisterInputMonitor去注册inputChannels[0],注释3处将inputChannels[1]返回给该方法的调用者。
nativeRegisterInputMonitor
c
//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
c
//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
status_t InputDispatcher::registerInputMonitor(const sp<InputChannel>& inputChannel,
int32_t displayId, bool isGestureMonitor) {
{ // acquire lock
std::scoped_lock _l(mLock);
//省略
sp<Connection> connection = new Connection(inputChannel, true /*monitor*/, mIdGenerator);//创建connection
const int fd = inputChannel->getFd();
mConnectionsByFd[fd] = connection;//添加进mConnectionsByFd
mInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel;
auto& monitorsByDisplay =
isGestureMonitor ? mGestureMonitorsByDisplay : mGlobalMonitorsByDisplay;//传入的是false
monitorsByDisplay[displayId].emplace_back(inputChannel);//放入mGlobalMonitorsByDisplay
mLooper->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
c
//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
c
//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
c
@Override
public 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方法
c
//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
c
//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处理
c
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);//添加view
mDisplayContent.registerPointerEventListener(mPointerLocationView);//1
}
可以看出,打开指针位置实际上是在顶层添加了一个view,该view设置了不能接收触摸事件,但是我们在触摸屏幕的时候,确实是画出了线条,说明是接收到了触摸事件的,这是怎么回事?要搞懂这个,继续看注释1处的registerPointerEventListener方法
c
//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方法
c
//frameworks\base\services\core\java\com\android\server\wm\PointerEventDispatcher.java
@Override
public 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方法
c
@Override
public void onPointerEvent(MotionEvent event) {
final int action = event.getAction();
//画出线条
}
可以看出,PointerLocationView能够接收到输入事件,也是利用了monitorInput机制。
总结与思考
monitorInput内部实现原理其实是和开头提到的那篇文章的原理是一样的。只是该InputChanel添加到InputTargets中是无条件的,即无论如何都能接收到输入事件。那我们是不是可以通过monitorInput,实现在后台service中得到输入事件,进而做自己的操作?比如实现点击屏幕某个区域位置,进入工厂菜单之类的。