美文网首页
`InputManagerService`原理

`InputManagerService`原理

作者: 陆元伟 | 来源:发表于2020-09-06 14:28 被阅读0次

InputManagerService原理

SystemServerstartOtherServices里面开启IMS

private void startOtherServices() {
        ...
        InputManagerService inputManager = null;
        ...
        traceBeginAndSlog("StartInputManagerService");
        inputManager = new InputManagerService(context);
        traceEnd();

        ...

        traceBeginAndSlog("StartInputManager");
        inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
        inputManager.start();
        traceEnd();

}

初始化的时候,初始化了一个Handler,用的DisplayThreadLooper,把消息列表传入native方法里面,调用了nativeInit初始化的方法

 public InputManagerService(Context context) {
    this.mContext = context;
    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

  
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

   

    LocalServices.addService(InputManagerInternal.class, new LocalService());
}

初始化 EventHubInputManager,其中 EventHub 的本质要封装了 epollinotify,监听设备写入的事件并读取到缓冲中进行简单解析

static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {

    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    ...

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

epoll 初始化完成后,当输入设备被写入数据后,epoll_wait 就会返回数据,并可以对其进行解析

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);

    // 新建一个 epoll
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);

    // 初始化 inotify
    mINotifyFd = inotify_init();
    // 监听设备的创建与删除
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s.  errno=%d",
            DEVICE_PATH, errno);

    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    // 增加需要监听的设备 fd
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
     ...
}

继续看start方法,start方法里面调用了nativeStart方法的初始化,和其他系统服务一样把当前对象添加到了Watchdog里面监听,因为系统服务一旦挂了,整个android系统就无法运行,就会强制重启,其原理就是通过添加到Watchdog去监测。具体Watchdog原理可以看这篇从源码角度看 WatchDog 机制

public void start() {
    Slog.i(TAG, "Starting input manager");
    nativeStart(mPtr);

    // Add ourself to the Watchdog monitors.
    Watchdog.getInstance().addMonitor(this);

    ...
}

nativeStart主要是调用native里面inputManager的start方法

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}

InputManager 启动了两个 native 线程,一个是InputReadThread,负责从驱动读取Event,一个是InputDispatcherThread,负责分发。

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);

    return OK;
}

事件的读取,InputReaderThread,调用InputReader的loopOnce方法

    bool InputReaderThread::threadLoop() {
        mReader->loopOnce();
        return true;
    }

loopOne方法中,EventHub.getEvents 如果获取到了数据才会进行到下一个状态中,大部分时间里,如果没有输入事件 getEvents 将会产生阻塞,不会消耗 CPU 资源。
InputReader并没有直接去访问设备节点,而是通过EventHub来处理
EventHub通过读取/dev/input/下的相关文件来判断是否有新事件,并在EventHub.getEvents处返回,类似MessageQueue里面的next方法一样。

    void InputReader::loopOnce() {
        ...
        // 从 EventHub 中读取数据
        size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    
        { // acquire lock
            AutoMutex _l(mLock);
            mReaderIsAliveCondition.broadcast();
    
            if (count) {
                  // 如果有数据则进行处理
                processEventsLocked(mEventBuffer, count);
            }
    
            ...
    
            if (oldGeneration != mGeneration) {
                inputDevicesChanged = true;
                getInputDevicesLocked(inputDevices);
            }
        } // release lock
    ...
        // 讲添加进缓存队列中的事件一齐插入到 InputDispatcher 的队列中
        mQueuedListener->flush();
    }

现在来看事件的分发,在InputDispatcherThread线程里面,同样调用mDispatcherdispatchOnce方法

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}

方法里面调用了dispatchOnceInnerLocked方法

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        AutoMutex _l(mLock);
        mDispatcherIsAliveCondition.broadcast();
...
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

...
    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

里调用了dispatchMotionLocked方法

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    if (! mPendingEvent) {
        if (mInboundQueue.isEmpty()) {
        ...
        } else {
             mPendingEvent = mInboundQueue.dequeueAtHead();
        }
    }
    ...
    case EventEntry::TYPE_MOTION: {
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
...
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }        
}

dispatchMotionLocked方法里面调用了,findTouchedWindowTargetsLocked方法,确定当前接收事件的有焦点的接收方(也就是客户端需要处理事件的窗口),其中还包括对 ANR 的判断 ,如果监测到派发的时间超过了 5s 还没有处理完成的话,native 就会通知 java 层需要触发 ANR 找到接收事件作用的客户端后保存在inputTargets` 列表中

方法调用dispatchEventLocked方法

bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
    Vector<InputTarget> inputTargets;
...    
    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);
    }   

    dispatchEventLocked(currentTime, entry, inputTargets);   
}

那么,InputDispatcher为什么会有当前窗口的句柄呢?我们知道窗口的管理是WindowManagerService,也就是说拿到WMS的引用应该就可以获取到当前的窗口是哪个,所以IMS里面只要有个WMS引用就可以,事实是什么样呢,继续看代码

IMS并没有直接持有WMS,而是有个mFocusedWindow属性,这个就是我们客户端添加窗口是添加到WMS的,

private IWindow mFocusedWindow;

在setInputWindows方法里面赋值的,那这个setInputWindow又是谁赋值的?

 public void setInputWindows(InputWindowHandle[] windowHandles,
            InputWindowHandle focusedWindowHandle) {
        final IWindow newFocusedWindow =
            focusedWindowHandle != null ? focusedWindowHandle.clientWindow : null;
        if (mFocusedWindow != newFocusedWindow) {
            mFocusedWindow = newFocusedWindow;
            if (mFocusedWindowHasCapture) {
                setPointerCapture(false);
            }
        }
        nativeSetInputWindows(mPtr, windowHandles);
    }

InputMonitoer的内部类UpdateInputForAllWindowsConsumer里面,mService即WMS,mInputManager即IMS,直接调用了WMS的IMS的setInputWindows的方法,也就是说WMS是直接持有IMS的引用的。

 private final class UpdateInputForAllWindowsConsumer implements Consumer<WindowState> {

        private void updateInputWindows(boolean inDrag) {
          ...

            // Send windows to native code.
            mService.mInputManager.setInputWindows(mInputWindowHandles, 
        }
}


 private WindowManagerService(Context context, InputManagerService inputManager,
        boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
        WindowManagerPolicy policy) {
    mInputManager = inputManager; // Must be before createDisplayContentLocked.
}

继续往调用的地方找,在InputMonitor的方法updateInputWindowsLw里面

void updateInputWindowsLw(boolean force) {
        ...
        mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);

}

继续往调用的地方找,在WMS的addWindow方法里面调用,addWindow不正是我们客户端添加窗口的调用的方法

   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) {
        ...

            if (focusChanged) {
                mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
            }
            mInputMonitor.updateInputWindowsLw(false /*force*/);

          ...

        return res;
    }

虽然WMS持有IMS,但是它不是直接操作,而是通过InputMonitor,把窗口传递到IMS

继续看InputDispatcher::dispatchEventLocked方法

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
...

    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);
        
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            //获取到的 Connection
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
...
    }
}


void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
...

    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

获取到当前窗口,是为了知道当前是哪个客户端需要接收事件。

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
...
    case EventEntry::TYPE_MOTION: {
        MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);

        ... 
        // Publish the motion event.
        status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
                motionEntry->deviceId, motionEntry->source,
                dispatchEntry->resolvedAction, motionEntry->actionButton,
                dispatchEntry->resolvedFlags, motionEntry->edgeFlags,
                motionEntry->metaState, motionEntry->buttonState,
                xOffset, yOffset, motionEntry->xPrecision, motionEntry->yPrecision,
                motionEntry->downTime, motionEntry->eventTime,
                motionEntry->pointerCount, motionEntry->pointerProperties,
                usingCoords);
        break;
    }        
...       
}


InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) :
        status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
        monitor(monitor),
        inputPublisher(inputChannel), inputPublisherBlocked(false) {
}

获取到的 Connection,调用 InputPublishser 的publishMotionEvent 方法将事件传输到客户端.这里看一下 Connection 的实现,其实就是封装了 InputChannel 成员变量与 window 句柄的类

那么InputChannel什么时候设置过来的呢

在WMS的addWindow方法里面

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        InputChannel outInputChannel) {
     ...
     if (outInputChannel != null && (attrs.inputFeatures
                & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
            String name = win.makeInputChannelName();
            InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
            win.setInputChannel(inputChannels[0]);
            inputChannels[1].transferTo(outInputChannel);

            mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
        }   
     ...
}

openInputChannelPair 会调用 native 方法初始化 socketpair,registerInputChannel 则是将 inputChannel 句柄传入 native 给 InputDispatcher

再来看InputPublisher.publishMotionEvent方法

status_t InputPublisher::publishMotionEvent(...) {
    InputMessage msg;
   ...
    for (uint32_t i = 0; i < pointerCount; i++) {
        msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
        msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
    }
    return mChannel->sendMessage(&msg);
}

status_t InputChannel::sendMessage(const InputMessage* msg) {
    size_t msgLength = msg->size();
    ssize_t nWrite;
    do {
        nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
    } while (nWrite == -1 && errno == EINTR);
    ...
}

通过将信息封装成 InputMessage,随后使用 InputChannel.sendMessage 将信息发送到客户端`

同样的应用程序这边的InputChannel是什么时候初始化的呢

ViewRootImplsetView方法里面

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(),
                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                mAttachInfo.mOutsets, mInputChannel);

   if (mInputChannel != null) {
        if (mInputQueueCallback != null) {
            mInputQueue = new InputQueue();
            mInputQueueCallback.onInputQueueCreated(mInputQueue);
        }
        mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                Looper.myLooper());
    }
}

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

ViewRootImpl.setView 时,与WMS 通信完成之后会去初始化 InputChannel 的客户端

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
...

    mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
            inputChannel, mMessageQueue);

    mCloseGuard.open("dispose");
}

通过调用 nativeInit 在 native 层进行初始化
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject inputChannelObj, jobject messageQueueObj) {
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
...

    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    status_t status = receiver->initialize();
...
    return reinterpret_cast<jlong>(receiver.get());
}

status_t NativeInputEventReceiver::initialize() {
    setFdEvents(ALOOPER_EVENT_INPUT);
    return OK;
}

void NativeInputEventReceiver::setFdEvents(int events) {
    if (mFdEvents != events) {
        mFdEvents = events;
        int fd = mInputConsumer.getChannel()->getFd();
        if (events) {
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
        } else {
            mMessageQueue->getLooper()->removeFd(fd);
        }
    }
}

真正的实现是在 NativeInputEventReceiver.initialize 方法进行的,最终使用的是传入的 MessageQueue 中的 Looper 来监听 InputChannel 客户端的 fd 实现的,本质上还是使用了 epoll 机制来监听 fd 的可读状态。当事件到来时,会调用回调方法 handleEvent 来处理事件

从源码角度看 InputManagerService
从源码角度看 WatchDog 机制

相关文章

  • `InputManagerService`原理

    InputManagerService原理 在SystemServer的startOtherServices里面开...

  • InputManagerService

    2InputManagerService 从名称可以理解,输入管理服务 谈到Service,特别系统的Servic...

  • InputManagerService

    概述 InputManagerService 是Android里面一个重要的service。它用于管理整个系统的输...

  • InputManagerService

    Input框架 每增加一个WindowState.java,WMS都会通过registerInputChannel...

  • InputManagerService

    InputManagerService服务创建 /frameworks/base/services/core/ja...

  • 按键 | InputManagerService

    本文主要讲一下inputmanager相关的,即驱动把数据上报到用户空间后,用户空间到应用这么个流程,内核的inp...

  • Android Input子系统核心服务

    基于Android 7.0源码分析 核心服务的类图 InputManagerService负责管理Android输...

  • android输入系统

    InputManagerService(IMS) Linux内核,接受输入设备的中断,并将原始事件的数据写入设备节...

  • Android | InputManagerService与输入

    前言 事件分发机制是Android中的基础而重要的知识,一般认为Activity#dispatchKeyEvent...

  • InputManagerService 启动流程分析

    和你一起终身学习,这里是程序员 Android 经典好文推荐,通过阅读本文,您将收获以下知识点: 一、前言二、启动...

网友评论

      本文标题:`InputManagerService`原理

      本文链接:https://www.haomeiwen.com/subject/ncscektx.html