Android11 monitorInput 介绍

在开始本文之前,先回顾下 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中得到输入事件,进而做自己的操作?比如实现点击屏幕某个区域位置,进入工厂菜单之类的。

相关推荐
带电的小王2 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡2 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道2 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库3 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道4 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe4 小时前
Android Hook - 动态加载so库
android
居居飒5 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He8 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗8 小时前
Android笔试面试题AI答之Android基础(1)
android
qq_397562319 小时前
android studio更改应用图片,和应用名字。
android·ide·android studio