深入Android S (12.0) 探索Framework之输入系统IMS的构成与启动

文章目录

  • 前言
  • 一、输入系统的基本组成部分
  • 二、输入系统相关源码分析
    • 1、IMS 构建
      • 1.1、SystemServer # startOtherServices()
      • 1.2、InputManagerService
      • 1.3、NativeInputManager # nativeInit()
      • 1.4、NativeInputManager
      • 1.5、InputManager
      • 1.6、InputDispatcher
      • 1.7、InputReader
      • 1.8、EventHub
      • 1.9、小结
    • 2、IMS 启动
      • 2.1、IMS # start()
      • 2.2、NativeInputManager # nativeStart()
      • 2.3、InputManager # start()
      • 2.4、InputDispatcher # start()
      • 2.5、InputReader # start()
      • 2.6、InputThread
    • 3、IMS 系统就绪
  • 三、总结
    • 1、IMS 启动时序图
    • 2、IMS 成员关系图
  • 参考

前言

Android 输入系统(Input System) 的工作原理,包括:输入设备的管理输入事件的加工方式及派发流程。首先输入设备包括:触摸屏,键盘,鼠标和手柄等,其中触摸屏与键盘是 Android 最普遍也是最标准的输入设备。当用户操作输入设备时,Linux 内核接收到相应的硬件中断,然后将中断加工成原始的输入事件数据并写入其对应的设备节点中,在用户空间可以通过输入系统内部的读取函数将原始事件数据读出,并进行一系列的翻译加工成 Android 输入事件,然后在所有的窗口中寻找合适的事件接收者,并派发给它来消费该输入事件。可见,输入系统在整个输入事件处理过程中起到了承上启下的衔接作用。


一、输入系统的基本组成部分

IMS 的总体流程与参与者
上图展示了输入事件的处理流程以及输入系统中最基本的参与者,下面简要介绍一下各个参与者:

  • Linux 内核:接受输入设备的中断,并将原始输入事件的数据写入设备节点中;
  • 设备节点:内核与 InputManagerService 的桥梁,它将原始事件的数据暴露给用户空间,以便 InputManagerService 可以从中读取事件;
  • InputManagerService:Android 系统服务,以后简称 IMS,其分为 Java 层和 Native 层两部分。Java 层负责与 WindowManagerService 通信。而 Native 层则是InputReaderInputDispatcher 两个输入系统关键组件的运行容器;
  • EventHub:直接访问所有的设备节点。并且正如其名字所描述的,它通过一个名为 getEvents( ) 的函数将所有输入系统相关的待处理的底层事件返回给使用者,包括原始输入事件、设备节点的增删等。
  • InputReaderIMS 中的关键组件之一,运行于一个独立的线程中,负责管理输入设备的列表与配置,以及进行输入事件的加工处理。通过其线程循环不断地通过 getEvent( ) 函数从 EventHub 中将事件取出并进行处理。对于设备节点的增删事件,将会更新输入设备列表与配置。对于原始输入事件,InputReader 对其进行翻译、组装、封装为包含更多信息、更具可读性的输入事件,然后交给 InputDispatcher 进行派发;
  • InputReaderPolicy:为 InputReader 的事件加工处理提供一些策略配置,例如键盘布局信息等;
  • InputDispatcherIMS 中的另一个关键组件,也运行于一个独立的线程中。InputDispatcher 中保管了来自 WindowManagerService 的所有窗口的信息,其收到来自 InputReader 的输入事件后,会在其保管的窗口中寻找合适的窗口,并将事件派发给此窗口;
  • InputDispatcherPolicy:为 InputDispatcher 的派发过程提供策略控制。例如截取某些特定的输入事件用作特殊用途,或者阻止将某些事件派发给目标窗口。一个典型的例子就是 HOME 键InputDispatcherPolicy 截取到 PhoneWindowManager 中进行处理,并阻止窗口收到 HOME 键按下的事件;
  • WindowManagerService:虽不是输入系统中的成员,但却对 InputDispatcher 的正常工作起到了至关重要的作用。当新建窗口时,WMS 为新窗口和 IMS 之间创建了事件传递所用的通道。另外,WMS 还将所有窗口的信息,包括窗口的可点击区域、焦点窗口等信息,实时地更新到 IMSInputDispatcher 中,使得 InputDispatcher 可以正确地将事件派发到指定的窗口;
  • ViewRootImpl:对某些窗口,如壁纸窗口、SurfaceView 的窗口来说,窗口就是输入事件派发的终点。而对其他的如 Activity、对话框等使用了 Android 控件系统的窗口来说,输入事件的终点是控件 ViewViewRootImpl 将窗口所接收的输入事件沿着控件树将事件派发给感兴趣的控件 View

二、输入系统相关源码分析

我们知道,Zygote 进程创建并启动后,在 fork 出的子进程 SystemServer 的初始化过程中启动 Android 系统所有的 Service 服务,这些系统服务分为三大类:引导服务核心服务其他服务,具体的启动流程可参考探索Framework之SystemServer进程的启动详解。

输入系统服务 IMS 是在启动其他服务里面启动的,接下来从源码的角度来继续探索分析。

1、IMS 构建

SystemServer 类中找到启动其他服务 startOtherServices() 方法的代码,提取主要的逻辑代码进行分析,源码如下:

1.1、SystemServer # startOtherServices()

xref: /frameworks/base/services/java/com/android/server/SystemServer.java

public final class SystemServer implements Dumpable {......private void startOtherServices(@NonNull TimingsTraceAndSlog t) {final Context context = mSystemContext;WindowManagerService wm = null;......InputManagerService inputManager = null;......try {......// 启动 InputManagerService 服务t.traceBegin("StartInputManagerService"); // 新建 InputManagerService 对象inputManager = new InputManagerService(context);......t.traceBegin("StartWindowManagerService"); // 启动 WindowManagerService 服务mSystemServiceManager.startBootPhase(t, SystemService.PHASE_WAIT_FOR_SENSOR_SERVICE);// 使用新建的 IMS 对象来构建 WMS 对象wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);......// 将 InputManagerService 发布到 ServiceManager 以便调用者可以访问 IMS 提供的接口ServiceManager.addService(Context.INPUT_SERVICE, inputManager,/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);t.traceBegin("SetWindowManagerService"); // ActivityManagerService 设置 WindowManagerServicemActivityManagerService.setWindowManager(wm);t.traceBegin("StartInputManager"); // 设置向 WMS 发起回调的 callback 对象inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());inputManager.start(); // 启动 InputManagerService,具体见......} catch (Throwable e) {......// 日志输出并抛出异常}......final InputManagerService inputManagerF = inputManager;t.traceBegin("MakeInputManagerServiceReady");try {if (inputManagerF != null) {// 输入系统 IMS 准备就绪inputManagerF.systemRunning();}} catch (Throwable e) {reportWtf("Notifying InputManagerService running", e);}......}......
}

IMS 的启动流程可以分为以下三个步骤:

  1. 构建 IMS 实例对象,并建立上层与底层的映射关系。
  2. 启动 IMS,其内部就是启动 native 层输入系统的几个重要参与者(后续会分析)。
  3. IMS 系统就绪,此时 Java 层会同步一些配置给 native 层输入系统。

首先是 IMS 实例对象的构建,分析查看 IMS 类的源码

1.2、InputManagerService

xref: /frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public class InputManagerService extends IInputManager.Stubimplements Watchdog.Monitor {......private final Context mContext;private final InputManagerHandler mHandler;......private static native long nativeInit(InputManagerService service,Context context, MessageQueue messageQueue);......public InputManagerService(Context context) {this.mContext = context;// 获取 DisplayThread 的 Looper 创建 IMS 内部的 InputManagerHandler 对象this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());mStaticAssociations = loadStaticInputPortAssociations();mUseDevInputEventForAudioJack =context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="+ mUseDevInputEventForAudioJack);// 每一个分为 Java 和 Native 两部分的对象在创建时都会有一个 native 函数// 在创建 Java 层对象的同时 native 层也创建一个,注意:使用的是同一个 Looper 对象// mPtr 指向底层创建的 NativeInputManager 对象mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());String doubleTouchGestureEnablePath = context.getResources().getString(R.string.config_doubleTouchGestureEnableFile);mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :new File(doubleTouchGestureEnablePath);// 新建 IMS 的本地系统服务 LocalService,其继承自 InputManagerInternal 抽象接口,并加入到 LocalServices 中LocalServices.addService(InputManagerInternal.class, new LocalService());}
}

方法中,获取 DisplayThreadLooper,新建 InputManagerHandler 对象,然后调用 native 层的 nativeInit() 函数,创建NativeInputManager 对象,最后新建 IMS 的本地系统服务 LocalService,其继承自 InputManagerInternal 抽象接口,并加入到 LocalServices 中。

DisplayThreadsystem_server 进程中是单例的,且只能被 WindowManagerDisplayManagerInputManager 使用。

1.3、NativeInputManager # nativeInit()

xref: /frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static const JNINativeMethod gInputManagerMethods[] = { // JNI 注册的映射关系/* name, signature, funcPtr */{"nativeInit","(Lcom/android/server/input/InputManagerService;Landroid/content/Context;Landroid/os/""MessageQueue;)J",(void*)nativeInit},
};static jlong nativeInit(JNIEnv*env, jclass /* clazz */,jobject serviceObj, jobject contextObj, jobject messageQueueObj) {// 由传入的 Java 层的 MessageQueue 转换获取 native 层的 MessageQueuesp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);if (messageQueue == nullptr) {jniThrowRuntimeException(env, "MessageQueue is not initialized.");return 0;}// 新建 NativeInputManager 对象,此对象将是 native 层组件与 Java 层 IMS 进行通信的桥梁NativeInputManager * im = new NativeInputManager(contextObj, serviceObj,messageQueue -> getLooper());im -> incStrong(0);// 返回指向 NativeInputManager 对象的指针给 Java 层的 IMS,IMS 将其保存在 mPtr 成员变量中return reinterpret_cast < jlong > (im);
}

通过 JNI 注册的映射关系,找到 native 层的 nativeInit() 函数,首先由传入的 Java 层的 MessageQueue 转换获取 native 层的 NativeMessageQueue 对象,然后新建 NativeInputManager 对象。

1.4、NativeInputManager

xref: /frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

class NativeInputManager : public virtual RefBase,public virtual InputReaderPolicyInterface,public virtual InputDispatcherPolicyInterface,public virtual PointerControllerPolicyInterface {public:NativeInputManager(jobject contextObj, jobject serviceObj, const sp<Looper>& looper);inline sp<InputManagerInterface> getInputManager() const { return mInputManager; }private:sp<InputManagerInterface> mInputManager;  jobject mServiceObj; // IMS 对象sp<Looper> mLooper; // Looper 对象Mutex mLock;struct Locked {......例如,mLocked.showTouches 是// 如果为 True,则启用指针手势bool pointerGesturesEnabled;// 由开发者选项中 "Show taps" 决定的,其功能是在屏幕上显示一个触摸点bool showTouches;......} mLocked GUARDED_BY(mLock);std::atomic<bool> mInteractive;......static inline JNIEnv* jniEnv() {return AndroidRuntime::getJNIEnv();}
};NativeInputManager::NativeInputManager(jobject contextObj,jobject serviceObj, const sp<Looper>&looper) :mLooper(looper),mInteractive(true) {JNIEnv * env = jniEnv();// 保存 Java 层的 InputManagerService 对象mServiceObj = env -> NewGlobalRef(serviceObj);{	// mLocked 的类型是 struct Locked,这里初始化了一些参数,这些参数会被 Java 层改变AutoMutex _l (mLock);mLocked.systemUiLightsOut = false;mLocked.pointerSpeed = 0;mLocked.pointerGesturesEnabled = true;mLocked.showTouches = false;mLocked.pointerCapture = false;mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;}mInteractive = true;// 创建了 native 层的 InputManager,它才是底层输入系统的服务InputManager * im = new InputManager(this, this);mInputManager = im;// 将 InputManager 注册到 ServiceManager 中defaultServiceManager()->addService(String16("inputflinger"), im);
}
  1. NativeInputManager 的构造函数中,创建一个全局引用,并通过 mServiceObj 指向 Java 层的 IMS 对象,便于后续可以通过 mServiceObj 调用 JavaIMS 对象的方法。
  2. 初始化参数,这里要注意一个结构体变量 mLocked,它的一些参数都是由 Java 层控制的。
  3. 然后将自己作为参数来新建 InputManager 对象,并将 InputManager 注册到 ServiceManager 中,InputManager 才是 native 层输入系统的服务。

注意:由 NativeInputManager 类的声明可以看到,其实现了 InputReaderPolicyInterfaceInputDispatcherPolicyInterface 两个接口。

1.5、InputManager

xref: /frameworks/native/services/inputflinger/InputManager.h

class InputManager : public InputManagerInterface, public BnInputFlinger {
protected:~InputManager() override;public:InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);......
private:sp<InputReaderInterface> mReader;sp<InputClassifierInterface> mClassifier;sp<InputDispatcherInterface> mDispatcher;
};

xref: /frameworks/native/services/inputflinger/InputManager.cpp

InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {// 创建 InputDispatcher 对象,使用 InputDispatcherPolicyInterface 接口,用于对事件进行分发mDispatcher = createInputDispatcher(dispatcherPolicy);// 创建 InputClassifier 对象,使用 InputListenerInterface,用于对事件分类mClassifier = new InputClassifier(mDispatcher);// 创建 InputReader 对象,使用 InputReaderPolicyInterface 和 InputListenerInterface// 其通过 EventHub 监听"/dev/input"事件,获取事件,然后把事件加工后,发送给 InputClassfiermReader = createInputReader(readerPolicy, mClassifier);
}

InputManager 内部创建了三个子模块:InputReaderInputClassifierInputDispatcher,其作用如下:

  1. InputReader:负责从 EventHub 中获取事件,然后把事件加工后,发送给 InputClassfier
  2. InputClassifer:负责把事件发送给 InputDispatcher,但是它会对触摸事件进行一个分类工作。
  3. InputDispatcher:对进行事件分发。

此外,在上一小节的分析中,我们知道在构建 InputManager 实例对象时使用了两个 this 参数,而 InputManager 构造函数需要的两个接口参数正是由 NativeInputManager 实现的,而具体使用这两个接口的不是 InputManager 自身,而是它内部的子模块 InputDispatcherInputReader

InputDispatcherInputReader 在构建时都传递了 NativeInputManager 对象参数,并赋值到各自的 mPolicy 变量,后续可直接通过 mPolicy 调用 JavaIMS 对象方法,因此 InputManagerJava 层通信的能力是由子模块 InputDispatcherInputReader 实现的。

接下来首先来看看 InputDispatcher 是如何通过 createInputDispatcher() 函数创建的,详见接下来两节的源码分析。

1.6、InputDispatcher

xref: /frameworks/native/services/inputflinger/dispatcher/InputDispatcherFactory.cpp

sp<InputDispatcherInterface> createInputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) {return new android::inputdispatcher::InputDispatcher(policy);
}

方法很简单,内部直接新建 InputDispatcher 对象,再继续查看 InputDispatcher 的构造函数:

xref: /frameworks/native/services/inputflinger/dispatcher/InputDispatcher.h

class InputDispatcher : public android::InputDispatcherInterface {
protected:~InputDispatcher() override;public:explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
......
private:std::unique_ptr<InputThread> mThread;sp<InputDispatcherPolicyInterface> mPolicy;sp<Looper> mLooper;sp<InputReporterInterface> mReporter;
};

xref: /frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy): mPolicy(policy),mPendingEvent(nullptr),mLastDropReason(DropReason::NOT_DROPPED),mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),mAppSwitchSawKeyDown(false),mAppSwitchDueTime(LONG_LONG_MAX),mNextUnblockedEvent(nullptr),mDispatchEnabled(false),mDispatchFrozen(false),mInputFilterEnabled(false),// mInTouchMode will be initialized by the WindowManager to the default device config.// To avoid leaking stack in case that call never comes, and for tests,// initialize it here anyways.mInTouchMode(true),mMaximumObscuringOpacityForTouch(1.0f),mFocusedDisplayId(ADISPLAY_ID_DEFAULT),mFocusedWindowRequestedPointerCapture(false),mWindowTokenWithPointerCapture(nullptr),mLatencyAggregator(),mLatencyTracker(&mLatencyAggregator),mCompatService(getCompatService()) {mLooper = new Looper(false); // 新建自己的 Looper 对象mReporter = createInputReporter(); // 新建 InputReporter 对象mKeyRepeatState.lastKeyEntry = nullptr;policy->getDispatcherConfiguration(&mConfig);
}

在调用 InputDispatcher 的构造函数构建实例对象的同时将入参 policy 赋值给 mPolicy 进行保存 (这里入参 policy 即是 NativeInputManager 对象)。其次新建自己的 Looper 对象,然后使用类似创建 InputDispatcher 的 createInputReporter() 函数新建 InputReporter 对象,代码比较简单,不再深入追踪,感兴趣的可自行查看。

1.7、InputReader

接着来看看 InputReader 是如何通过 createInputReader() 函数创建的,一起跟着源码来学习。
xref: /frameworks/native/services/inputflinger/reader/InputReaderFactory.cpp

sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,const sp<InputListenerInterface>& listener) {// 创建 EventHub 对象传入到 InputReader 的构造函数中来新建 InputReader 对象return new InputReader(std::make_unique<EventHub>(), policy, listener);
}

该方法里面,在新建 InputReader 对象时,结合 InputReader 类的构造函数可知,第一个参数是 EventHub 的实例对象,那么 EventHub 对象是怎么创建的呢?

这里需要知道一些 C++ 有关的知识,std::make_unique 的语法如下:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args);

std::make_unique:是 C++11 标准引入的一个模版函数,用于动态分配指定类型的内存,并返回一个指向分配内存的唯一指针 (即 std::unique_ptr)。语法中,T 是指定的类型,Args 是可变长模板参数包,用于传递给指定类型的构造函数的参数。在调用 std::make_unique 时,通过 Args 包传入构造函数的参数会被转发给类型 T 的构造函数,以生成相应的对象实例。该函数返回的指针是一个 std::unique_ptr 类型,表示一个拥有指向动态内存的所有权的对象。

xref: /frameworks/native/services/inputflinger/reader/include/InputReader.h

class InputReader : public InputReaderInterface {
public:InputReader(std::shared_ptr<EventHubInterface> eventHub,const sp<InputReaderPolicyInterface>& policy,const sp<InputListenerInterface>& listener);virtual ~InputReader();
protected:// 在循环过程的每次迭代中,InputReader 读取并处理一条来自 EventHub 的传入消息void loopOnce();private:std::unique_ptr<InputThread> mThread;std::shared_ptr<EventHubInterface> mEventHub;sp<InputReaderPolicyInterface> mPolicy;sp<QueuedInputListener> mQueuedListener;
};

xref: /frameworks/native/services/inputflinger/reader/InputReader.cpp

InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,const sp<InputReaderPolicyInterface>& policy,const sp<InputListenerInterface>& listener): mContext(this),mEventHub(eventHub),mPolicy(policy),mGlobalMetaState(0),mLedMetaState(AMETA_NUM_LOCK_ON),mGeneration(1),mNextInputDeviceId(END_RESERVED_ID),mDisableVirtualKeysTimeout(LLONG_MIN),mNextTimeout(LLONG_MAX),mConfigurationChangesToRefresh(0) {mQueuedListener = new QueuedInputListener(listener);{ // acquire lockstd::scoped_lock _l(mLock);refreshConfigurationLocked(0);updateGlobalMetaStateLocked();} // release lock
}

与构建 InputDispatcher 对象类似,在调用 InputReader 的构造函数构建实例对象的同时将入参 policy 赋值给 mPolicyeventHub 对象赋值给 mEventHub 保存,同时新建 QueuedInputListener 监听对象。

通过前一小节的分析可知,EventHub 实例对象是通过调用 std::make_unique() 函数来创建的,那接下来一起去看看 EventHub 具体都做了些什么?

1.8、EventHub

xref: /frameworks/native/services/inputflinger/reader/include/EventHub.h

class EventHub : public EventHubInterface {
public:EventHub();
private:int32_t mNextDeviceId;BitSet32 mControllerNumbers;std::unordered_map<int32_t, std::unique_ptr<Device>> mDevices;std::vector<std::unique_ptr<Device>> mOpeningDevices;std::vector<std::unique_ptr<Device>> mClosingDevices;int mEpollFd;int mINotifyFd;int mWakeReadPipeFd;int mWakeWritePipeFd;int mInputWd;int mVideoWd;// 一次最多可处理的信号fd的数量static const int EPOLL_MAX_EVENTS = 16;// 挂起的 epoll 事件数组和下一个要处理的事件的索引struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];size_t mPendingEventCount;size_t mPendingEventIndex;bool mPendingINotify;
};

xref: /frameworks/native/services/inputflinger/reader/EventHub.cpp

EventHub::EventHub(void): mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),mNextDeviceId(1),mControllerNumbers(),mNeedToSendFinishedDeviceScan(false),mNeedToReopenDevices(false),mNeedToScanDevices(true),mPendingEventCount(0),mPendingEventIndex(0),mPendingINotify(false) {ensureProcessCanBlockSuspend();// 创建 Epoll 对象,mEpollFd 为 Epoll 对象的描述符mEpollFd = epoll_create1(EPOLL_CLOEXEC);mINotifyFd = inotify_init(); // 创建并初始化 INotify 对象// DEVICE_PATH值为"/dev/input",监听该目录下的设备节点创建与删除操作,然后通过 read 函数读取事件mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);......struct epoll_event eventItem = {};eventItem.events = EPOLLIN | EPOLLWAKEUP;eventItem.data.fd = mINotifyFd;//epoll_ctl 将事件监听添加到 Epoll 对象中,参数值 EPOLL_CTL_ADD:表示增加事件int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);int wakeFds[2];result = pipe(wakeFds);mWakeReadPipeFd = wakeFds[0];mWakeWritePipeFd = wakeFds[1];result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);eventItem.data.fd = mWakeReadPipeFd;result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);......
}

EventHub 的构造函数主要工作有:

  1. 新建并初始化 EpollINotify 对象等。
  2. 调用 inotify_add_watch 函数,监听 “/dev/input” 目录下的设备节点创建与删除操作,然后通过 read() 函数读取事件。
  3. INotify 添加到 Epoll 中,作为一个监控对象。
  4. 创建管道,将管道读取端的可读事件添加到 Epoll 中,使 epoll_wait() 函数返回,唤醒 InputReader 线程。

至此,IMSJava 层和 native 层的实例对象都已创建完成,并且在这个过程中,输入系统的重要参与者也均创建完成。

1.9、小结

IMS的结构体系
Java 层的 IMS 的主要工作是为 ReaderPolicyDispatcherPolicy 提供实现,以及与 Android 其他系统服务进行协作,其中最主要的协作者是 WMS

NativeInputManager 位于 IMSJNI 层,负责 Native 层的组件与 Java 层的 IMS 之间的相互通信。同时为 InputReaderInputDispatcher 提供了策略请求的接口。策略请求被他转发给 Java 层的 IMS,由 IMS 进行最终的决策定夺。

InputManagerInputReaderInputDispatcher 的运行容器,在启动 InputReaderInputDispatcher 时,分别新建自己的运行线程 InputThreadImpl 并启动运行。

2、IMS 启动

在上一节的 SystemServer # startOtherServices() 方法中,在构建完 IMS 后,IMS 系统中的各个重要参与者仍处于待命状态,需调用 IMS # start() 函数来启动 IMS,继续追踪源码分析:

2.1、IMS # start()

xref: /frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public class InputManagerService extends IInputManager.Stubimplements Watchdog.Monitor {......private static native void nativeStart(long ptr);......public void start() {Slog.i(TAG, "Starting input manager");// 启动 native 层的 IMSnativeStart(mPtr);Watchdog.getInstance().addMonitor(this);// 监听Settings.System.POINTER_SPEED,这个表示手指的速度registerPointerSpeedSettingObserver();// 监听Settings.System.SHOW_TOUCHES,这个表示是否在屏幕上显示触摸坐标registerShowTouchesSettingObserver();registerAccessibilityLargePointerSettingObserver();registerLongPressTimeoutObserver();registerMaximumObscuringOpacityForTouchSettingObserver();registerBlockUntrustedTouchesModeSettingObserver();// 监听用户切换mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {updatePointerSpeedFromSettings();updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();updateDeepPressStatusFromSettings("user switched");}}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);// 从数据库获取值,并传递给 native 层updatePointerSpeedFromSettings();updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();updateDeepPressStatusFromSettings("just booted");updateMaximumObscuringOpacityForTouchFromSettings();updateBlockUntrustedTouchesModeFromSettings();}......
}

IMS 的启动过程如下:

  1. 启动 native 层输入系统,其实就是启动刚刚说到的 InputReaderInputDispatcher
  2. 注册监听广播,因为这些广播与输入系统的配置有关,当接收到这些广播,会更新配置到 native 层。
  3. 直接读取配置,更新到 native 层输入系统。

2.2、NativeInputManager # nativeStart()

xref: /frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static const JNINativeMethod gInputManagerMethods[] = { // JNI 注册的映射关系/* name, signature, funcPtr */{"nativeStart", "(J)V", (void*)nativeStart},
};static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {// 将 Java 层保存的 NativeInputManager 对象的指针转换成 NativeInputManager 对象NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);// 查看 1.4 NativeInputManager 的源码可知,获取到 InputManager 对象,然后调用其 start() 函数status_t result = im->getInputManager()->start();if (result) {jniThrowRuntimeException(env, "Input manager could not be started.");}
}

首先将 Java 层保存 NativeInputManager 对象的指针 mPtr 转换成 NativeInputManager 对象,然后调用 NativeInputManager # getInputManager() 函数获取到 InputManager 对象,接着调用 InputManager # start() 函数继续启动流程。

reinterpret_cast 的功能可以分为两类:1、指针和整数之间的转换;2、不同类型的指针/成员指针/引用之间的转换。

2.3、InputManager # start()

xref: /frameworks/native/services/inputflinger/InputManager.cpp

status_t InputManager::start() {// 启动承载 InputDispatcher 的线程status_t result = mDispatcher->start();if (result) {ALOGE("Could not start InputDispatcher thread due to error %d.", result);return result;}// 启动承载 InputReader 的线程result = mReader->start();if (result) {ALOGE("Could not start InputReader due to error %d.", result);mDispatcher->stop();return result;}return OK;
}

InputManager 的启动过程很简单,调用 InputDispatcherInputReader 的 start() 函数,启动承载它们运行的线程,来看一下它们是如何启动线程的。

2.4、InputDispatcher # start()

xref: /frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp

status_t InputDispatcher::start() {if (mThread) {return ALREADY_EXISTS;}mThread = std::make_unique<InputThread>("InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });return OK;
}

在方法内,首先判断 mThread 是否已存在,存在则直接返回,不存在则通过 std::make_unique 函数来构建 InputThread 的实例对象,但没有看到启动线程的代码逻辑,带着这个疑问,我们再去看看 InputReader 的 start() 方法。

2.5、InputReader # start()

xref: /frameworks/native/services/inputflinger/reader/InputReader.cpp

status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}mThread = std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });return OK;
}

方法的代码逻辑跟 InputDispatcher 的差不多,也是没有看到启动线程的代码逻辑,既然都是新建 InputThread 对象,那就具体来看一下 InputThread 类。

2.6、InputThread

xref: /frameworks/native/services/inputflinger/include/InputThread.h

class InputThread {
public:explicit InputThread(std::string name, std::function<void()> loop,std::function<void()> wake = nullptr);virtual ~InputThread();bool isCallingThread();
private:std::string mName; // 线程名std::function<void()> mThreadWake;sp<Thread> mThread; // 承载 InputDispatcher\InputReader 运行的线程
};

xref: /frameworks/native/services/inputflinger/InputThread.cpp

class InputThreadImpl : public Thread {
public: // explicit 关键字的作用就是防止类构造函数的隐式自动转换,且只对有一个参数的类构造函数有效explicit InputThreadImpl(std::function<void()> loop): Thread(/* canCallJava */ true), mThreadLoop(loop) {}~InputThreadImpl() {}private:std::function<void()> mThreadLoop; // 存储一个可调用对象,这里指的是 lambda 表达式bool threadLoop() override {mThreadLoop();return true;}
};InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake): mName(name), mThreadWake(wake) {// 使用封装的可调用对象 loop 新建 InputThreadImpl 对象mThread = new InputThreadImpl(loop);// 启动 InputThreadImpl 线程mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}

std::function:是一个通用的函数封装类,它可以存储、复制和调用任意可调用对象,包括函数指针、函数对象、成员函数指针和lambda 表达式等。通过使用 std::function 作为函数参数,我们可以实现更加灵活的函数调用方式,提高代码的可读性和可维护性。

由代码可知 InputThread 类其本身不是一个线程,其内部是 InputThreadImpl 类来实现线程的具体功能。使用封装的可调用对象 loop 构建 InputThreadImpl 对象,然后调用其 run() 函数来启动线程。

InputThreadImpl 类继承自 Thread 类,而 C++Thread 类提供了一个名为 threadLoop() 的纯虚函数,当线程开始运行后,将会在内建的线程循环中不断地调用 threadLoop() 函数,直到此函数返回 false,则退出线程循环结束线程。但从 InputThreadImpl 类的定义可以看出,threadLoop() 函数会一直保持循环(返回值始终为 true),并且每一次循环,会调用一次 mThreadLoop() 函数,而 mThreadLoop() 函数是由 InputDispacherInputReader 在启动时封装好传入的可调用对象。

到这里,终于搞明白了,在 InputDispatcher 启动时,会创建一个线程,然后循环调用 dispatchOnce() 函数,同样 InputReader 启动时,也会创建一个线程,然后循环调用 loopOnce() 函数。

3、IMS 系统就绪

上面两节已完成 IMS 及其重要参与者的构建,并启动了 IMS 系统,接下来就是通知系统 IMS 系统已完成启动并准备就绪,具体看一下源码
xref: /frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

public class InputManagerService extends IInputManager.Stubimplements Watchdog.Monitor {......// IMS 系统内部的 Handler,用来处理键盘等输入设备有关的消息private final InputManagerHandler mHandler;private WiredAccessoryCallbacks mWiredAccessoryCallbacks; // 有线连接的设备回调private boolean mSystemReady; // 标志系统是否准备完毕private NotificationManager mNotificationManager; // 通知管理......public void systemRunning() {......mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);synchronized (mLidSwitchLock) {mSystemReady = true;......int switchState = getSwitchState(-1 /* deviceId */, InputDevice.SOURCE_ANY, SW_LID);for (int i = 0; i < mLidSwitchCallbacks.size(); i++) {LidSwitchCallback callback = mLidSwitchCallbacks.get(i);callback.notifyLidSwitchChanged(0 /* whenNanos */, switchState == KEY_STATE_UP);}}// 监听广播,通知 native 层加载键盘布局IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);filter.addAction(Intent.ACTION_PACKAGE_REMOVED);filter.addAction(Intent.ACTION_PACKAGE_CHANGED);filter.addAction(Intent.ACTION_PACKAGE_REPLACED);filter.addDataScheme("package");mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {// 其内部继续调用 reloadKeyboardLayouts() 函数updateKeyboardLayouts();}}, filter, null, mHandler);// 监听广播,通知 native 层加载设备别名filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);mContext.registerReceiver(new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {reloadDeviceAliases();}}, filter, null, mHandler);// 通过 InputManagerHandler 发送消息来通知 native 层加载键盘布局和加载设备别名mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);// 如果与系统连接的有线设备的回调不为空,则须回调通知其输入系统 IMS 已准备完毕if (mWiredAccessoryCallbacks != null) { mWiredAccessoryCallbacks.systemReady();}}private void reloadKeyboardLayouts() {if (DEBUG) {Slog.d(TAG, "Reloading keyboard layouts.");}// 调用 native 层函数来加载键盘布局nativeReloadKeyboardLayouts(mPtr);}private void reloadDeviceAliases() {if (DEBUG) {Slog.d(TAG, "Reloading device names.");}// 调用 native 层函数来加载设备别名nativeReloadDeviceAliases(mPtr);}......
}

注册监听广播,通知 native 层加载键盘布局、设备别名,最后通过 JNI 调用 native 层的函数来加载键盘布局、设备别名。此外,如果与系统连接的有线设备注册的回调不为空,则需回调通知其输入系统 IMS 已准备就绪。


三、总结

1、IMS 启动时序图

IMS 启动时序图

2、IMS 成员关系图

IMS 成员关系图
最后结合 IMS 的启动时序图和成员关系图可以更深刻的理解 IMS 系统的构成与启动过程,下一篇文章来继续探索输入系统的重要组成成员,等待后续吧!

参考

  1. 深入理解Android:卷III

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/180027.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全小白自学

一、网络安全应该怎么学&#xff1f; 1.计算机基础需要过关 这一步跟网安关系暂时不大&#xff0c;是进入it行业每个人都必须掌握的基础能力。 计算机网络计算机操作系统算法与数据架构数据库 Tips:不用非要钻研至非常精通&#xff0c;可以与学习其他课程同步进行。 2.渗透技…

2015年五一杯数学建模C题生态文明建设评价问题解题全过程文档及程序

2015年五一杯数学建模 C题 生态文明建设评价问题 原题再现 随着我国经济的迅速发展&#xff0c;生态文明越来越重要&#xff0c;生态文明建设被提到了一个前所未有的高度。党的十八大报告明确提出要大力推进生态文明建设&#xff0c;报告指出“建设生态文明&#xff0c;是关系…

【Python】torch.exp()和 torch.sigmoid()函数详解和示例

本文对torch.exp&#xff08;&#xff09;和 torch.sigmoid&#xff08;&#xff09;函数进行原理和示例讲解&#xff0c;以帮助大家理解和使用。 目录 torch.exp函数原理运行示例 torch.sigmoid&#xff08;&#xff09;函数原理运行示例torch.sigmoid相关知识 结合运行 torc…

2023网络安全产业图谱

1. 前言 2023年7月10日&#xff0c;嘶吼安全产业研究院联合国家网络安全产业园区&#xff08;通州园&#xff09;正式发布《嘶吼2023网络安全产业图谱》。 嘶吼安全产业研究院根据当前网络安全发展规划与趋势发布《嘶吼2023网络安全产业图谱》调研&#xff0c;旨在进一步了解…

Net6.0或Net7.0项目升级到Net8.0 并 消除.Net8中SqlSugar的警告

本文基于NetCore3.1或Net6.0项目升级到Net7.0&#xff0c;参考连接&#xff1a;NetCore3.1或Net6.0项目升级到Net7.0-CSDN博客 所有项目按照此步骤操作一遍&#xff0c;完成后再将所有引用的包&#xff08;即 *.dll&#xff09;更新升级到最新版&#xff08;注意&#xff1a;有…

在 CentOS 7 上安装 MySQL 8

在 CentOS 7 上安装 MySQL 8 步骤 1: 添加 MySQL Yum 存储库 首先&#xff0c;我们需要添加 MySQL Yum 存储库。打开终端并执行以下命令&#xff1a; sudo yum install -y https://repo.mysql.com/mysql80-community-release-el7-3.noarch.rpm步骤 2: 导入 MySQL GPG 公钥 …

python-爬虫(可直接使用)

爬虫&#xff08;Web Scraping&#xff09;是指通过编程自动化地获取互联网上的信息的过程。爬虫的目的通常是从网页中抓取数据&#xff0c;进行数据分析、处理或展示。以下是爬虫的基本流程和一些重要的概念&#xff1a; 爬虫基本流程&#xff1a; 确定目标&#xff1a; 确定要…

【Rust】快速教程——自定义类型、数字转枚举、Cargo运行

前言 超过一定的年龄之后&#xff0c;所谓人生&#xff0c;无非是一个不断丧失的过程而已。宝贵的东西&#xff0c;会像梳子豁了齿一样从手中滑落下去。你所爱的人会一个接着一个&#xff0c;从身旁悄然消逝。——《1Q84》 \;\\\;\\\; 目录 前言自定义类型数字转枚举Cargo.tom…

CorelDRAW Graphics Suite2023破解版含2024最新注册机下载

CorelDRAW Graphics Suite2023是Corel公司的平面设计软件&#xff1b;该软件是Corel出品的矢量图形制作工具软件&#xff0c;这个图形工具给设计师提供了矢量动画、页面设计、网站制作、位图编辑和网页动画等多种功能。在日常科研绘图中&#xff0c;若较为轻量&#xff0c;通常…

处理器及微控制器:XCZU15EG-2FFVC900I 可编程单元

XCZU15EG-2FFVC900I参数&#xff1a; Zynq UltraScale™ MPSoC 系列基于 Xilinx UltraScale™ MPSoC 架构。该 Zynq UltraScale™ MPSoC 器件集成了功能丰富的 64 位四核或双核 Arm Cortex-A53 和双核 Arm Cortex-R5F 处理系统&#xff08;基于 Xilinx UltraScale™ MPSoC 架…

六、Lua运算符

文章目录 一、Lua 运算符&#xff08;一&#xff09;算术运算符&#xff08;二&#xff09;关系运算符&#xff08;三&#xff09;逻辑运算符&#xff08;四&#xff09;其他运算符 二、运算符优先级 一、Lua 运算符 运算符是一个特殊的符号&#xff0c;用于告诉解释器执行特定…

443. 压缩字符串

这篇文章会收录到 : 算法通关村第十二关-黄金挑战字符串冲刺题-CSDN博客 压缩字符串 描述 : 给你一个字符数组 chars &#xff0c;请使用下述算法压缩&#xff1a; 从一个空字符串 s 开始。对于 chars 中的每组 连续重复字符 &#xff1a; 如果这一组长度为 1 &#xff0c;…

MySQL- CRUD

一、INSERT 添加 公式 INSERT INTO table_name [(column [, column...])] VALUES (value [, value...]); 示例&#xff1a; CREATE TABLE goods (id INT ,good_name VARCHAR(10),price DOUBLE ); #添加数据 INSERT INTO goods (id,good_name,price ) VALUES (20,华为手机,…

万界星空科技生产管理mes系统种的工艺确认流程

MES工艺流程是制造执行系统的核心部分&#xff0c;它涵盖了整个生产过程&#xff0c;包括物料管理、生产计划、生产执行、质量管理、维修保养等方面&#xff0c;可以有效地提高生产效率和产品质量。 一、确认追溯模型&#xff1a; 以工艺文件为确认对象&#xff0c;以产品生产…

【算法】一个简单的整数问题(树状数组、差分)

题目 给定长度为 N 的数列 A&#xff0c;然后输入 M 行操作指令。 第一类指令形如 C l r d&#xff0c;表示把数列中第 l∼r 个数都加 d。 第二类指令形如 Q x&#xff0c;表示询问数列中第 x 个数的值。 对于每个询问&#xff0c;输出一个整数表示答案。 输入格式 第一行…

sqli-labs靶场详解(less29-less31)

less-29 提示有最好的防火墙 小白原因 这种题型没见过 先自己测试一下 ?id1 to use near 1 预计可以使用报错注入 和单引号有关的注入点 ?id1 and 11 成功 ?id1 and 12 失败 确实是单引号字符型注入点 ?id1;%00 id1%27;%00 获取到了%00空字符&#xff08;原因就是服务器获取…

基于SSM的社区团购系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

虚幻学习笔记6—摄像机控制

一、前言 摄像机在虚幻中的应用是最常见的。如通常在游戏或应用中会常常出现需要切换不同视角的情况、摄像机拉近缩小等&#xff0c;这个在虚幻中是怎么实现的呢。 二、实现视点切换 2.1、提前设置场景的视点&#xff1a;如图2.1.1所示添加一个摄像机视点到关卡场景中&#x…

【傻瓜级JS-DLL-WINCC-PLC交互】3.JS-DLL进行交互

思路 JS-DLL-WINCC-PLC之间进行交互&#xff0c;思路&#xff0c;先用Visual Studio创建一个C#的DLL控件&#xff0c;然后这个控件里面嵌入浏览器组件&#xff0c;实现JS与DLL通信&#xff0c;然后DLL放入到WINCC里面的图形编辑器中&#xff0c;实现DLL与WINCC的通信。然后PLC与…

「计算机网络」Cisco Packet Tracker计算机网络仿真器的使用

介绍 Cisco Packet Tracker&#xff1a;网络仿真工具&#xff0c;用于模拟网络配置。 &#xff08;一&#xff09;通过 带外管理 配置交换机&#xff08;Switch&#xff09; 带外&#xff1a;Out-of-Band, OOB写在前面&#xff1a;如何打开Console页面 1、模式转换 用户执行模…