IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android10 InputManagerService事件输入输出 -> 正文阅读

[移动开发]Android10 InputManagerService事件输入输出

? ? ? ? InputManagerService管理着屏幕点击以及硬件按钮事件的输入输出,InputManagerService的实现是在native代码中,想要对事件进行处理,那就一定要通过InputManagerService进行注册或是监听。如果A应用要想获取到输入事件,那要怎么和InputManagerService连接起来呢?答案是Socket,其中InputChannel就是对其进行封装,InputChannel的实现同样是native代码,实现的类是NativeInputChannel,类路径:frameworks/base/core/jni/android_view_InputChannel.cpp,这里就直接看下InputChannel的openInputChannelPair():

    /**
     * 创建一个新的输入通道对。 一个通道提供给输入调度程序,另一个通道提供给应用程序的输入列。  
     * @param name通道对的描述性(非唯一)名称。  
     * @return 一对输入通道。 第一个通道被指定为服务器通道,应该用于发布输入事件。 第二个通道被指                
     * 定为客户端通道,用于使用输入事件。  
     */
    public static InputChannel[] openInputChannelPair(String name) {
        if (name == null) {
            throw new IllegalArgumentException("name must not be null");
        }

        if (DEBUG) {
            Slog.d(TAG, "Opening input channel pair '" + name + "'");
        }
        return nativeOpenInputChannelPair(name);
    }

    private static native InputChannel[] nativeOpenInputChannelPair(String name);

openInputChannelPair()会创建一对输入通道,一端用于服务器通道,也就是InputManagerService;一端用于客户端,也就是应用这一端。对InputChannel有个初步的了解后,接下来就看主角com.android.server.input.InputManagerService:

public class InputManagerService extends IInputManager.Stub
        implements Watchdog.Monitor {

    // Pointer to native input manager service object.
    private final long mPtr;
    //所有输入事件在分发前,会优先派发到这个回调中处理
    private WindowManagerCallbacks mWindowManagerCallbacks;
    //内部会初始化两个线程,一个用于读取底层的输入事件,一个用于将事件派发到应用层
    private static native long nativeInit(InputManagerService service,
            Context context, MessageQueue messageQueue);
    //前面说InputChannel时会创建一对输入通过对,这里就是将其中的一个注册到底层,也就是所说的服务端
    //InputChannel只会收到注册页面的输入事件
    private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
            int displayId);
    //解注册页面注册的输入事件
    private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
    //这个方法是添加监听,InputChannel会收到所有的输入事件,
    //其中isGestureMonitor表示收到的事件是否是手势,系统的全局手势就是通过这个方法注册的
    private static native void nativeRegisterInputMonitor(long ptr, InputChannel inputChannel,
            int displayId, boolean isGestureMonitor);
    //注入模拟屏幕的点击事件,比如实现全局的返回按钮
    private static native int nativeInjectInputEvent(long ptr, InputEvent event,
            int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
            int policyFlags);

    public InputManagerService(Context context) {
        ... ...
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
        ... ...
    }

    //这个回调是在SystemServer中设置的,其实现是InputManagerCallback,最终会调用到PhoneWindowManager
    public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
        mWindowManagerCallbacks = callbacks;
    }

    /**
     * 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[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
        // Give the output channel a token just for identity purposes.
        inputChannels[0].setToken(new Binder());
        nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, false /*isGestureMonitor*/);
        inputChannels[0].dispose(); // don't need to retain the Java object reference
        return inputChannels[1];
    }

    /**
     * Creates an input monitor that will receive pointer events for the purposes of system-wide
     * gesture interpretation.
     *
     * @param inputChannelName The input channel name.
     * @param displayId Target display id.
     * @return The input channel.
     */
    @Override // Binder call
    public InputMonitor monitorGestureInput(String inputChannelName, int displayId) {
        if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
                "monitorInputRegion()")) {
            throw new SecurityException("Requires MONITOR_INPUT permission");
        }

        Objects.requireNonNull(inputChannelName, "inputChannelName must not be null.");

        if (displayId < Display.DEFAULT_DISPLAY) {
            throw new IllegalArgumentException("displayId must >= 0.");
        }


        final long ident = Binder.clearCallingIdentity();
        try {
            InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
            InputMonitorHost host = new InputMonitorHost(inputChannels[0]);
            inputChannels[0].setToken(host.asBinder());
            nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId,
                    true /*isGestureMonitor*/);
            return new InputMonitor(inputChannelName, inputChannels[1], host);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    /**
     * Registers an input channel so that it can be used as an input event target.
     * @param inputChannel The input channel to register.
     * @param inputWindowHandle The handle of the input window associated with the
     * input channel, or null if none.
     */
    public void registerInputChannel(InputChannel inputChannel, IBinder token) {
        if (inputChannel == null) {
            throw new IllegalArgumentException("inputChannel must not be null.");
        }

        if (token == null) {
            token = new Binder();
        }
        inputChannel.setToken(token);

        nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);
    }

    /**
     * Unregisters an input channel.
     * @param inputChannel The input channel to unregister.
     */
    public void unregisterInputChannel(InputChannel inputChannel) {
        if (inputChannel == null) {
            throw new IllegalArgumentException("inputChannel must not be null.");
        }

        nativeUnregisterInputChannel(mPtr, inputChannel);
    }

    @Override // Binder call
    public boolean injectInputEvent(InputEvent event, int mode) {
        return injectInputEventInternal(event, mode);
    }

    private boolean injectInputEventInternal(InputEvent event, int mode) {
        ... ...
        final int pid = Binder.getCallingPid();
        final int uid = Binder.getCallingUid();
        final long ident = Binder.clearCallingIdentity();
        final int result;
        try {
            result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
                    INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
        ... ...
    }

    // Native callback.
    // 有输入事件时,这里会最先调用到
    private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
    }

    // Native callback.
    // 如果interceptKeyBeforeQueueing没有处理,在事件分发给应用端前会调用到
    private long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
        return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
    }

}

这里先把InputManagerService的底层实现看成一个沙盒,下一篇文章再聊,这里先来聊聊上面列出的这些方法,类中有一个类型是WindowManagerCallbacks的mWindowManagerCallbacks成员变量,其实现类是InputManagerCallback,但最终调用到的是PhoneWindowManager,事件在分发给应用前,会分别调用interceptKeyBeforeQueueing()和interceptKeyBeforeDispatching(),这也就是说PhoneWindowManager是最先处理输入事件的地方。

? ? ? ? 这里先说个小插曲,interceptKeyBeforeQueueing()这个方法为什么是这样命名的,最开始看到这个名字还是挺疑惑的,后面看了底层代码才明白,InputManagerService的底层实现开启了两个线程,一个用于读输入事件(读线程),一个用于将读取的到事件分发(分发线程),读线程如何将事件传递到分发线程呢,这里就会涉及到队列(Queue),所以现在看到这个interceptKeyBeforeQueueing()是不是就很明白了。

对于上面其他的方法,先来看下方法的命名,monitorXXX()、registerXXX()、unregisterXXX()、injectXXX(),可以分为三类:

1、带有monitorXXX()的方法,表示只要有输入事件就会接收到;

2、injectXXX()就是模拟按键发送事件了;

3、registerXXX()和unRegisterXXX()这是成对出现的,这对方法有什么作用呢?一个页面要想接收到输入事件,那就必须调用registerXXX()进行对接收事件的注册,页面销毁了就调用解注册;

下面就来看下android源码中对这些方法的使用:

1、frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java,这是对系统手势处理的类,来看下它内部的使用:

    private void updateIsEnabled() {
        ... ...
        // Register input event receiver
        mInputMonitor = InputManager.getInstance().monitorGestureInput("edge-swipe", mDisplayId);
        mInputEventReceiver = new InputChannelCompat.InputEventReceiver(mInputMonitor.getInputChannel(), 
Looper.getMainLooper(),Choreographer.getInstance(), this::onInputEvent);
        ... ...
    }

调用monitor监听后,当有输入事件输入,就会调用到这里的onInputEvent()方法。

2、对于injectInputEvent()方法,在InputManager中有@hide标识,也就是只能在系统中使用,那现在要怎么才能使用呢?在上一篇文章Android10 AppComponentFactory源码梳理有提到android.app.Instrumentation这个类,来看下它是怎么使用的:

    //需要传入的是KeyEvent中Keycode的常量,比如:KeyEvent.KEYCODE_BACK就是执行返回按键的功能
    public void sendKeyDownUpSync(int key) {        
        sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
        sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
    }

    public void sendKeySync(KeyEvent event) {
        validateNotAppThread();

        long downTime = event.getDownTime();
        long eventTime = event.getEventTime();
        int source = event.getSource();
        if (source == InputDevice.SOURCE_UNKNOWN) {
            source = InputDevice.SOURCE_KEYBOARD;
        }
        if (eventTime == 0) {
            eventTime = SystemClock.uptimeMillis();
        }
        if (downTime == 0) {
            downTime = eventTime;
        }
        KeyEvent newEvent = new KeyEvent(event);
        newEvent.setTime(downTime, eventTime);
        newEvent.setSource(source);
        newEvent.setFlags(event.getFlags() | KeyEvent.FLAG_FROM_SYSTEM);
        InputManager.getInstance().injectInputEvent(newEvent,
                InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
    }

所以,要想实现按键功能就可以通过上面的方法。

3、下面重点来看下registerXX(),这个对于每个显示的页面都会调用到,只是在frameworks层调用了,对于应用层来说无感而已,应用层的所有的处理流程都是从ViewRootImpl开始的,当界面显示时,会调用ViewRootImpl.setView():

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        ... ...
        if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            mInputChannel = new InputChannel();
        }
        ... ...
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
        ... ...
        mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
        ... ...

    }

先是new了一个InputChannel,但是并没有去创建通道对,也就是说这个InputChannel还没有初始化,这里的mWindowSession是通过WindowManagerService.openSession()返回,其实现是Session类,调用它的addToDisplay()最终调用到的是WindowManagerService.addWindow():

WindowManagerService
    public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
        ... ...
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
        ... ...
        win.openInputChannel(outInputChannel);
        ... ...

    }

WindowState
    void openInputChannel(InputChannel outInputChannel) {
        if (mInputChannel != null) {
            throw new IllegalStateException("Window already has an input channel.");
        }
        String name = getName();
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
        mInputChannel = inputChannels[0];
        mClientChannel = inputChannels[1];
        mInputWindowHandle.token = mClient.asBinder();
        if (outInputChannel != null) {
            //初始化应用端的InputChannel,实际就是初始化mPtr,这个变量通过native代码可以转化成指针
            mClientChannel.transferTo(outInputChannel);
            mClientChannel.dispose();
            mClientChannel = null;
        } else {
            // If the window died visible, we setup a dummy input channel, so that taps
            // can still detected by input monitor channel, and we can relaunch the app.
            // Create dummy event receiver that simply reports all events as handled.
            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
        }
        mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
    }

这里通过InputChannel创建了一对通道对inputChannels[0]和inputChannels[1],然后将inputChannels[1]和应用端的InputChannel关联起来,将inputChannels[0]和服务端(事件原始分发)关联起来,这样就将原始输入事件和应用界面关联起来了,再回到ViewRootImpl.setView(),之后还创建了一个WindowInputEventReceiver对象,这一看就是接受输入事件的了,WindowInputEventReceiver基础自InputEventReceiver,这里主要来看下InputEventReceiver:

public abstract class InputEventReceiver {

    private long mReceiverPtr;

    // We keep references to the input channel and message queue objects here so that
    // they are not GC'd while the native peer of the receiver is using them.
    private InputChannel mInputChannel;
    private MessageQueue mMessageQueue;

    private final SparseIntArray mSeqMap = new SparseIntArray();

    private static native long nativeInit(WeakReference<InputEventReceiver> receiver,
            InputChannel inputChannel, MessageQueue messageQueue);

    public InputEventReceiver(InputChannel inputChannel, Looper looper) {
        ... ...
        mInputChannel = inputChannel;
        mMessageQueue = looper.getQueue();
        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                inputChannel, mMessageQueue);
    }

    // Called from native code.
    @SuppressWarnings("unused")
    @UnsupportedAppUsage
    private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event);
    }

    @UnsupportedAppUsage
    public void onInputEvent(InputEvent event) {
        finishInputEvent(event, false);
    }

    public final void finishInputEvent(InputEvent event, boolean handled) {
        if (event == null) {
            throw new IllegalArgumentException("event must not be null");
        }
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to finish an input event but the input event "
                    + "receiver has already been disposed.");
        } else {
            int index = mSeqMap.indexOfKey(event.getSequenceNumber());
            if (index < 0) {
                Log.w(TAG, "Attempted to finish an input event that is not in progress.");
            } else {
                int seq = mSeqMap.valueAt(index);
                mSeqMap.removeAt(index);
                nativeFinishInputEvent(mReceiverPtr, seq, handled);
            }
        }
        event.recycleIfNeededAfterDispatch();
    }
}

主要的实现也是native代码,这里就不下看了,感兴趣的可以自行查看c++的实现类NativeInputEventReceiver,c++层代码处理完成后会调用这里的dispatchInputEvent()方法,转而调用onInputEvent(),在往回看下WindowInputEventReceiver的实现:

    final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
            List<InputEvent> processedEvents;
            try {
                processedEvents =
                    mInputCompatProcessor.processInputEventForCompatibility(event);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            if (processedEvents != null) {
                if (processedEvents.isEmpty()) {
                    // InputEvent consumed by mInputCompatProcessor
                    finishInputEvent(event, true);
                } else {
                    for (int i = 0; i < processedEvents.size(); i++) {
                        enqueueInputEvent(
                                processedEvents.get(i), this,
                                QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
                    }
                }
            } else {
                //正常是会执行到这里
                enqueueInputEvent(event, this, 0, true);
            }
        }

    }

这样一个流程下来,就通过InputChannel把事件传递到了应用界面,接下去就是通过enqueueInputEvent()传递给view进行处理了,在事件处理完成后,会调用到InputEventReceiver.finishInputEvent(),通知服务端事件处理完成,这样一次事件的分发就算是完成了,这里就不在往下看了。

这里在总结下

? ? ? ? InputManagerService可以理解为通往底层输入事件的一个大门,要想获得事件的处理,可以通过以下几种方式:

? ? ? ? 1、向InputManagerService中设置回调,在SystemServer中有设置,最终在PhoneWindowManager中实现事件处理;

? ? ? ? 2、通过InputManagerService发送模拟按键事件,比如返回键,可以通过Instrumentation;

? ? ? ? 3、通过InputManagerService注册或者添加监听,注册一般用于普通应用,每个启动的页面都有注册,添加监听一般用于系统应用,比如系统手势,就是在SystemUi中实现的;

下一篇文章再聊聊InputWindowManagerService的底层实现,这篇就到这了。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-21 12:18:02  更:2021-10-21 12:18:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:32:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码