当前位置: 首页 > article >正文

漏学Input知识系列之“偷”走了其他窗口的事件pilferPointers

背景:

学员群里近来老是学员朋友问到一个触摸事件相关实际场景问题:
如何在全局监听到某个类型触摸事件比如NavigationBar导航手势的上滑动,不会让这个上滑动事件不影响app。

简单说就是触摸事件正常情况会传递给导航手势和顶部Activity,但是很多情况下导航手势都对Activity的触摸事件不感兴趣,但是一旦是Activity底部开始上滑这个动作,那么导航手势就感兴趣,他要开始处理整体的上滑逻辑了,自然这个上滑事件再传递给Activity就不合适了,那么系统是怎么做到的不再传递给Activity呢?

这个就是本文带大家了解的pilferPointers方法,“偷”走了其他窗口的事件。这里用了一个表达很到位的英文单词pilfer即“偷”。为啥叫做“偷”不叫“拦截”,因为拦截事件一般是指上层策略压根事件不让给你,“偷”事件则表达事件之前可能是传递给你的,本来也一直要传递给你,但是因为导航手势业务把给你事件都偷走了。

具体如下图所示:在这里插入图片描述

直观理解“偷”事件

下面以App的Activity角度的触摸事件情况来理解这个“偷”事件
到Activity的dispatchTouchEvent方法中加入相关的打印查看:
在这里插入图片描述正常Activity非底部上滑

在这里插入图片描述
Activity底部上滑触发导航手势
导航手势上滑
也就是直观上其实可以看出这个pilferPointers方法是怎么“偷”事件的。原理如下:
在导航手势还没有识别到自己上滑动作前,其实所有的触摸事件是已经正常传递给了Activity,但是一旦导航手势识别了自己有上滑动作,那么就会独占这个上滑事件,不给Activity,但是前面的DOWN事件已经传递给了Activity,为了事件完整性就需要系统给一个CANCEL事件给Activity。这里是不是额知道了一个CANCEL事件产生的场景。

通过其他日志查看“偷”事件方式:
通过查看events日志
adb logcat -b events | grep cancel

test@test:~/aosp15$ adb logcat -b events -c;adb logcat -b events | grep cancel
01-10 10:44:53.298   546  2344 I input_cancel: [59551b2 com.example.anrdemo/com.example.anrdemo.MainActivity (server),reason=input channel stole pointer stream]
01-10 10:44:53.298   546  2344 I input_cancel: [[Gesture Monitor] edge-swipe (server),reason=input channel stole pointer stream]
01-10 10:44:53.298   546  2344 I input_cancel: [[Gesture Monitor] onehanded-touch (server),reason=input channel stole pointer stream]

可以看到上面就有被偷每个窗口input_cancel这样事件产生,产生原因是input channel stole pointer stream。

也可以使用如下命令过滤stealing关键字

adb logcat | grep stealing
可以看到如下日志输出,这里看到了

01-10 10:49:03.629   546  2344 I InputDispatcher: Channel [Gesture Monitor] swipe-up (server) is stealing input gesture for device 12 from [512067 com.android.launcher3/com.android.launcher3.uioverrides.QuickstepLauncher, dca494a com.android.systemui.wallpapers.ImageWallpaper, [Gesture Monitor] edge-swipe, [Gesture Monitor] onehanded-touch]

这个日志就明显展示了,事件被 swipe-up这个Monitor独占了,把事件从其他window中偷走了。

使用pilferPointers及源码分析
以导航手势上滑为例,剖析一下使用

相关的pilferPointers调用堆栈如下

pilferPointers:59, InputMonitor (android.view)
pilferPointers:55, InputMonitorCompat (com.android.systemui.shared.system)
notifyGestureStarted:382, OtherActivityInputConsumer (com.android.quickstep.inputconsumers)
onMotionEvent:339, OtherActivityInputConsumer (com.android.quickstep.inputconsumers)
onMotionEvent:116, NavHandleLongPressInputConsumer (com.android.quickstep.inputconsumers)
onMotionEvent:138, OneHandedModeInputConsumer (com.android.quickstep.inputconsumers)
onInputEvent:937, TouchInteractionService (com.android.quickstep)
$r8$lambda$JDK2db7a_ZtzI6D1EJ5hYukunXo:-1, TouchInteractionService (com.android.quickstep)
onInputEvent:0, TouchInteractionService$$ExternalSyntheticLambda5 (com.android.quickstep)
onInputEvent:78, InputChannelCompat$InputEventReceiver$1 (com.android.systemui.shared.system)
dispatchInputEvent:295, InputEventReceiver (android.view)
nativeConsumeBatchedInputEvents:-1, InputEventReceiver (android.view)
consumeBatchedInputEvents:259, InputEventReceiver (android.view)
onBatchedInputEventPending:58, BatchedInputEventReceiver (android.view)
nativePollOnce:-1, MessageQueue (android.os)
next:346, MessageQueue (android.os)
loopOnce:189, Looper (android.os)
loop:317, Looper (android.os)
main:8705, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:580, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:886, ZygoteInit (com.android.internal.os)

这个全局的monitor就是swipe-up即导航手势
InputMonitor { inputChannel = [Gesture Monitor] swipe-up (client), host = android.view.IInputMonitorHost S t u b Stub StubProxy@f9a7d10, surface = Surface(name=[Gesture Monitor] swipe-up)/@0x1425009 }

对应的代码如下:
在满足对应上滑条件后会调用notifyGestureStarted方法
在这里插入图片描述在notifyGestureStarted方法中主要是调用到了pilferPointers方法
在这里插入图片描述
这里使用其实很简单只需要在识别到满足条件的触摸事件后调用pilferPointers方法既可以。

下面重点分析一下pilferPointers的源码
frameworks/base/core/java/android/view/InputMonitor.java

    public void pilferPointers() {
        try {
            mHost.pilferPointers();
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

这里主要掉到了 mHost的pilferPointers,这里的mHost是IInputMonitorHost类型,属于一个跨进程通讯,是在InputMonitor构造时候进行赋值的

在这里插入图片描述

这里的InputMonitor又是在InputManagerService中调用monitorGestureInput时候构造的

frameworks/base/services/core/java/com/android/server/input/InputManagerService.java在这里插入图片描述
所以上面 mHost.pilferPointers()调用就是调用到了InputManagerService中的InputMonitorHost方法:
在这里插入图片描述
这里又会传递一个自己的mInputChannelToken,调用到Native层面,最后通过如下方法调用到了InputDispatcher中
在这里插入图片描述

具体InputDispatcher中的pilferPointers方法如下:

status_t InputDispatcher::pilferPointers(const sp<IBinder>& token) {
    std::scoped_lock _l(mLock);
    return pilferPointersLocked(token);
}

status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
    const std::shared_ptr<Connection> requestingConnection = getConnectionLocked(token);
    //根据上层传递token,找到对应的window
    auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
  

    ScopedSyntheticEventTracer traceContext(mTracer);
    for (const DeviceId deviceId : deviceIds) {
        TouchState& state = *statePtr;
        TouchedWindow& window = *windowPtr;
        // Send cancel events to all the input channels we're stealing from.
        //准备好cancel事件
        CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
                                   "input channel stole pointer stream", traceContext.getTracker());
        options.deviceId = deviceId;
        options.displayId = displayId;
        std::vector<PointerProperties> pointers = window.getTouchingPointers(deviceId);
        std::bitset<MAX_POINTER_ID + 1> pointerIds = getPointerIds(pointers);
        options.pointerIds = pointerIds;

        std::string canceledWindows;
        for (const TouchedWindow& w : state.windows) {
        		 //除了token对应window,其他window都要收到cancel
            if (w.windowHandle->getToken() != token) {
                synthesizeCancelationEventsForWindowLocked(w.windowHandle, options);
                canceledWindows += canceledWindows.empty() ? "[" : ", ";
                canceledWindows += w.windowHandle->getName();
            }
        }
        canceledWindows += canceledWindows.empty() ? "[]" : "]";
        //这个就是打印
        LOG(INFO) << "Channel " << requestingConnection->getInputChannelName()
                  << " is stealing input gesture for device " << deviceId << " from "
                  << canceledWindows;

        // Prevent the gesture from being sent to any other windows.
        // This only blocks relevant pointers to be sent to other windows
        window.addPilferingPointers(deviceId, pointerIds);
			//对state中保存的接受事件window要进行移除掉,只剩下token对应window,那样事件就不会派发给cancelwindow
        state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
    }
    return OK;
}

总结

“偷”走了其他窗口的事件,本质实现就是调用pilferPointers方法,实现让其他窗口收到CANCEL事件,同时派发的窗口列表中移除非token的window,后续事件派发时候,只派发给token对应的window。

文章来源:https://mp.weixin.qq.com/s/l_mQK1xs8d4dp7VMkl0kyw

更多framework实战干货,请关注下面“千里马学框架”


http://www.kler.cn/a/487723.html

相关文章:

  • 【学习资源】MBSE和工业软件
  • Ollama私有化部署大语言模型LLM
  • 人工智能学习路线全链路解析
  • 基于大数据可视化+django+爬虫的李宁品牌销售数据分析系统设计和实现(源码+论文+部署讲解等)
  • Ubuntu安装vscode
  • 相对误差与相对误差限
  • C++ 多线程异步操作
  • Windows 安装 Docker 和 Docker Compose
  • 如何用 Python 实现简单的 AI 模型?
  • jQuery UI 主题
  • 怎么抓取ios 移动app的https请求?
  • vue封装axios请求
  • Openssl1.1.1s rpm包构建与升级
  • Vue.js组件开发-如何动态更改图表类型
  • LabVIEW实现动态水球图的方法
  • 青少年编程与数学 02-006 前端开发框架VUE 14课题、生命周期
  • 科大讯飞前端面试题及参考答案 (下)
  • 【PyTorch入门】使用PyTorch构建一个简单的图像分类模型
  • [大模型]本地离线运行openwebui+ollama容器化部署
  • 游戏语音的历史是什么样的?