Android R PointerEventDispatcher 触摸事件监听帮助类

Posted pecuyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android R PointerEventDispatcher 触摸事件监听帮助类相关的知识,希望对你有一定的参考价值。

文章托管在gitee上 Android Notes , 同步csdn

PointerEventDispatcher 介绍

PointerEventDispatcher类的功能如同名字描述的那样,它是一个触摸事件分发类. 使用者通过registerInputEventListener(PointerEventListener listener) 方法向它注册事件监听,当有事件到来时,它的onInputEvent(InputEvent event)方法会被调用到, 然后将事件分发给所有的监听者. 当不需要监听Pointer事件,调用
unregisterInputEventListener(PointerEventListener listener).

原理分析

PointerEventDispatcher 继承自InputEventReceiver, 因此它可以使用传进来的inputChannel构建一个与inputManagerService传递事件的通道. 当对端派发事件时,本地native端的InputChannel会收到相关事件, 并向上传递此事件, 回调它的onInputEvent(InputEvent event)方法.

    public class PointerEventDispatcher extends InputEventReceiver 
      // 注册的Pointer监听列表
      private final ArrayList<PointerEventListener> mListeners = new ArrayList<>();
      private PointerEventListener[] mListenersArray = new PointerEventListener[0];

      public PointerEventDispatcher(InputChannel inputChannel) 
          super(inputChannel, UiThread.getHandler().getLooper()); // 注意这个关键的super, 参数是inputChannel
      

InputEventReceiver 构造函数创建事件通道

    /**
     * Creates an input event receiver bound to the specified input channel.
     *
     * @param inputChannel The input channel.
     * @param looper The looper to use when invoking callbacks.
     */
    public InputEventReceiver(InputChannel inputChannel, Looper looper) 
        if (inputChannel == null) 
            throw new IllegalArgumentException("inputChannel must not be null");
        
        if (looper == null) 
            throw new IllegalArgumentException("looper must not be null");
        

        mInputChannel = inputChannel;
        mMessageQueue = looper.getQueue();
        // 初始化native的InputChannel
        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                inputChannel, mMessageQueue);

        mCloseGuard.open("dispose");
    

注册监听

    /**
     * Add the specified listener to the list.
     * @param listener The listener to add.
     */
    public void registerInputEventListener(PointerEventListener listener) 
        synchronized (mListeners) 
            if (mListeners.contains(listener))  // 不可重复注册
                throw new IllegalStateException("registerInputEventListener: trying to register" +
                        listener + " twice.");
            
            mListeners.add(listener);
            mListenersArray = null;
        
    

事件分发

    @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;
                // 将mListeners拷贝到mListenersArray
                // 这一步的作用应该是创建一个固定长度的数组,不被修改 减少同步,
                // 不然整个分发过程都需要加锁mListeners
                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);   // 分发事件
                
            
         finally 
          // 发送事件派发结束的反馈, 注意第二个参数为false, 表示没有被处理
          finishInputEvent(event, /*handled*/ false);
        
    

取消注册

    /**
    * Remove the specified listener from the list.
    * @param listener The listener to remove.
    */
    public void unregisterInputEventListener(PointerEventListener listener) 
      synchronized (mListeners) 
          if (!mListeners.contains(listener)) 
              throw new IllegalStateException("registerInputEventListener: " + listener +
                      " not registered.");
          
          mListeners.remove(listener);
          mListenersArray = null;
      
    

清理

    /** Dispose the associated input channel and clean up the listeners. */
    @Override
    public void dispose() 
        super.dispose();
        synchronized (mListeners) 
            mListeners.clear();
            mListenersArray = null;
        
    

应用

在DisplayContent的构造方法中创建了PointerEventDispatcher, 并注册了一些监听

    // DisplayContent#<init>
    ...
    // app transition相关
    mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
    mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
    mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
    mAppTransitionController = new AppTransitionController(mWmService, this);
    mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);

    // 关键: 创建inputChannel, 使用 nativeRegisterInputMonitor 注册monitor
    final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
            "PointerEventDispatcher" + mDisplayId, mDisplayId);
    // 创建PointerEventDispatcher
    mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);

    // Tap Listeners are supported for:
    // 1. All physical displays (multi-display).
    // 2. VirtualDisplays on VR, AA (and everything else).
    mTapDetector = new TaskTapPointerEventListener(mWmService, this);
    registerPointerEventListener(mTapDetector); // 注册tap事件到PointerEventDispatcher
    registerPointerEventListener(mWmService.mMousePositionTracker);
    if (mWmService.mAtmService.getRecentTasks() != null) 
        registerPointerEventListener(
                mWmService.mAtmService.getRecentTasks().getInputListener());
    

看一下 DisplayConent#registerPointerEventListener

    // 向mPointerEventDispatcher注册监听
    void registerPointerEventListener(@NonNull PointerEventListener listener) 
        mPointerEventDispatcher.registerInputEventListener(listener);
    

PointerLocationView

另外一个例子是开发者选项的 PointerLocationView 的显示, 这个实现在 DisplayPolicy.
路径是frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java

enablePointerLocation

开启PointerLocationView 的显示

    private void enablePointerLocation() 
        if (mPointerLocationView != null) 
            return;
        

        mPointerLocationView = new PointerLocationView(mContext);
        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
                | 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);   // 添加窗口显示
        mDisplayContent.registerPointerEventListener(mPointerLocationView);  // 最终还是注册到了 mPointerEventDispatcher
    

disablePointerLocation

关闭 PointerLocationView 的显示

    private void disablePointerLocation() 
        if (mPointerLocationView == null) 
            return;
        

        mDisplayContent.unregisterPointerEventListener(mPointerLocationView);  // 移除监听
        final WindowManager wm = mContext.getSystemService(WindowManager.class);
        wm.removeView(mPointerLocationView); // 移除窗口
        mPointerLocationView = null;
    

InputManagerService#monitorInput

下面看一个比较关键的部分, 就是InputManagerService#monitorInput的实现,这个方法会创建一对InputChannel,native层的实现是socket pair.
然后将一端注册到native的InputDispatcher, 另一端返回给 PointerEventDispatcher来创建自身, 从而建立了两者事件派发的通道.

      /// @frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
      /**
      * Creates an input channel that will receive all input from the input dispatcher.
      * @param inputChannelName The input channel name.
      * @param displayId Target display id.
      * @return The input channel.
      */
      public InputChannel monitorInput(String inputChannelName, int displayId) 
      if (inputChannelName == null) 
          throw new IllegalArgumentException("inputChannelName must not be null.");
      

      if (displayId < Display.DEFAULT_DISPLAY) 
          throw new IllegalArgumentException("displayId must >= 0.");
      
      // 创建InputChannel对
      InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
      // 注册inputChannels[0]到 InputDispatcher
      nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, false /*isGestureMonitor*/);
      // 这个地方的作用是释放native层的NativeInputChannel的对象, 置空java层InputChannel的mPtr
      inputChannels[0].dispose(); // don't need to retain the Java object reference
      return inputChannels[1];  // 返回另一端
      

nativeRegisterInputMonitor

注意这个方法与nativeRegisterInputChannel的区别 , 它没有调用 android_view_InputChannel_setDisposeCallback 设置DisposeCallback , 这个对上面的 inputChannels[0].dispose() 操作有较大影响.

      /// @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);
          if (inputChannel == nullptr) 
              throwInputChannelNotInitialized(env);
              return;
          

          if (displayId == ADISPLAY_ID_NONE) 
              std::string message = "InputChannel used as a monitor must be associated with a display";
              jniThrowRuntimeException(env, message.c_str());
              return;
          
            // 通过 NativeInputManager 注册
          status_t status = im->registerInputMonitor(env, inputChannel, displayId, isGestureMonitor);

          if (status) 
              std::string message = StringPrintf("Failed to register input channel.  status=%d", status);
              jniThrowRuntimeException(env, message.c_str());
              return;
          
      

NativeInputManager::registerInputMonitor

      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 lock
            std::scoped_lock _l(mLock);

            if (displayId < 0) 
                ALOGW("Attempted to register input monitor without a specified display.");
                return BAD_VALUE;
            

            if (inputChannel->getConnectionToken() == nullptr) 
                ALOGW("Attempted to register input monitor without an identifying token.");
                return BAD_VALUE;
            
            // 创建 Connection
            sp<Connection> connection = new Connection(inputChannel, true /*monitor*/, mIdGenerator);

            // 保存各种信息
            const int fd = inputChannel->getFd();
            mConnectionsByFd[fd] = connection;
            mInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel;

            auto& monitorsByDisplay =
                    isGestureMonitor ? mGestureMonitorsByDisplay : mGlobalMonitorsByDisplay;
            monitorsByDisplay[displayId].emplace_back(inputChannel);
            // 监听此InputChannel的fd,接收Input事件处理的反馈
            mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
        
        // Wake the looper because some connections have changed.
        mLooper->wake();
        return OK;
    

InputChannel#dispose

    /**
    * Disposes the input channel.
    * Explicitly releases the reference this object is holding on the input channel.
    * When all references are released, the input channel will be closed.
    */
    public void dispose() 
      nativeDispose(false);  // 调用native方法处理
    

对应的jni方法如下

    static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) 
        NativeInputChannel* nativeInputChannel =
                android_view_InputChannel_getNativeInputChannel(env, obj);
        if (nativeInputChannel) 
            if (finalized) 
                ALOGW("Input channel object '%s' was finalized without being disposed!",
                        nativeInputChannel->getInputChannel()->getName().c_str());
            

            nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
            // java层InputChannel的mPtr置为nullprt
            android_view_InputChannel_setNativeInputChannel(env, obj, nullptr);
            // 释放NativeInputChannel
            delete nativeInputChannel;
        
    

NativeInputChannel#invokeAndRemoveDisposeCallback

由上面可知,nativeRegisterInputMonitor没有设置DisposeCallback, 因此此函数实际上就没有执行操作.

    void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) 
        if (mDisposeCallback) 
            mDisposeCallback(env, obj, mInputChannel, mDisposeData);
            mDisposeCallback = nullptr;
            mDisposeData = nullptr;
        
    

由上可知 InputChannel#dispose()方法释放了NativeInputChannel. 另外,java层InputChannels[0] 由于没有任何引用,也会很快被JVM回收.

以上是关于Android R PointerEventDispatcher 触摸事件监听帮助类的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Android Studio 将我所有的引用从 R 更改为 android.R?

Android:android.R.id.content 是做啥用的?

Android - 如何获取 android.R.anim.slide_in_right

同时导入 R(android.R 和 <my_package>.R)

com.android.internal.R包怎么导入android里面的。

Android 11(R)版本对于Toast的一些改变