深入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 设置 WindowManagerService
        	mActivityManagerService.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.Stub
		implements 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 层的 MessageQueue
	sp<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"事件,获取事件,然后把事件加工后,发送给 InputClassfier
	mReader = 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 lock
        std::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.Stub
        implements Watchdog.Monitor {
	......
	private static native void nativeStart(long ptr);
	......
 	public void start() {
        Slog.i(TAG, "Starting input manager");
        // 启动 native 层的 IMS
        nativeStart(mPtr);
        Watchdog.getInstance().addMonitor(this);
		// 监听Settings.System.POINTER_SPEED,这个表示手指的速度
        registerPointerSpeedSettingObserver();
        // 监听Settings.System.SHOW_TOUCHES,这个表示是否在屏幕上显示触摸坐标
        registerShowTouchesSettingObserver();
        registerAccessibilityLargePointerSettingObserver();
        registerLongPressTimeoutObserver();
        registerMaximumObscuringOpacityForTouchSettingObserver();
        registerBlockUntrustedTouchesModeSettingObserver();
		// 监听用户切换
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public 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.Stub
		implements 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() {
            @Override
            public void onReceive(Context context, Intent intent) {
            	// 其内部继续调用 reloadKeyboardLayouts() 函数
                updateKeyboardLayouts();
            }
        }, filter, null, mHandler);
		// 监听广播,通知 native 层加载设备别名
        filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);
        mContext.registerReceiver(new BroadcastReceiver() {
            @Override
            public 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CodeTON Round #7 (Div. 1 + Div. 2)

A.jagged Swaps 题意&#xff1a; 给出一个包含 n n n个数字的序列&#xff0c;每次可以选择一个同时大于左右两边相邻的数字&#xff0c;将这个数字与它右边的数字交换&#xff0c;问能否在经过若干次操作后使序列变为升序。 分析&#xff1a; 由于交换只能向后进行&#…

神经网络:脑科学中功能MRI成像的应用及其一些相关概念

文章目录 一、MRI成像简介核磁共振成像&#xff08;MRI&#xff09;侵入式成像功能磁共振成像&#xff08;fMRI&#xff09;血氧水平依赖&#xff08;BOLD&#xff09;效应对比基线状态代理指标 二、fMRI具有延迟性及其解决方案原因解决方法 三、fMRI 数据处理1. 数据预处理2. …

YOLOv5算法进阶改进(5)— 主干网络中引入SCConv | 即插即用的空间和通道维度重构卷积

前言:Hello大家好,我是小哥谈。SCConv是一种用于减少特征冗余的卷积神经网络模块。相对于其他流行的SOTA方法,SCConv可以以更低的计算成本获得更高的准确率。它通过在空间和通道维度上进行重构,从而减少了特征图中的冗余信息。这种模块的设计可以提高卷积神经网络的性能。�…

23种设计模式之C++实践

23种设计模式之C++实践 1. 简介2. 基础知识3. 设计模式(一)创建型模式1. 单例模式1.2 饿汉式单例模式1.3 懒汉式单例模式比较IoDH单例模式总结2. 简单工厂模式简单工厂模式总结3. 工厂方法模式工厂方法模式总结4. 抽象工厂模式抽象工厂模式总结5. 原型模式原型模式总结6. 建造…

Django大回顾-2 之 Django的基本操作、路由层,MTV和MVC模型

【1】MTV和MVC模型 MVC与MTV模型 --->所有web框架其实都遵循mvc架构 MVC模型 MVC 本来坨在一起的代码&#xff0c;拆到不同的位置 模型(M&#xff1a;数据层)&#xff0c;控制器(C&#xff1a;逻辑判断)和视图(V&#xff1a;用户看到的)三层 他们之间以一种插件式…

C++ 背包理论基础01 + 滚动数组

背包问题的重中之重是01背包 01背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 每一件物品其实只有两个状态&#xff0c;取或者不…

sprintboot快速初始化【Springboot】

1.首先选择创建项目 2.填写对应的项目信息 一定要勾选maven&#xff0c;否则没有pom文件&#xff0c;选择next 3.选择应用场景 点击 create&#xff0c;DIEA就会根据你的选择自动创建项目骨架&#xff1b; 4.创建一个控制层 随便创建一个控制层&#xff0c;测试一下项目是否…

智能优化算法应用:基于阴阳对算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于阴阳对算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于阴阳对算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.阴阳对算法4.实验参数设定5.算法结果6.参考文献7.…

js相同字符串截取拼接

原数据 const list [999-1234567801,999-1234567802,999-1234567803, ]; const list1 [999-1234567899,999-1234567900,999-1234567901, ];期望数据 999-1234567801/2/3 //list 999-1234567899/900/901 //list1处理代码 // 连续号码处理 export const formatNumber (tick…

Python实现性能自动化测试

一、思考❓❔ 1.什么是性能自动化测试? 性能 系统负载能力超负荷运行下的稳定性系统瓶颈自动化测试 使用程序代替手工提升测试效率性能自动化 使用代码模拟大批量用户让用户并发请求多页面多用户并发请求采集参数&#xff0c;统计系统负载能力生成报告 2.Python中的性能自动…

Python 装饰器与偏函数

目录 装饰器 概念 简单的装饰器 复杂点的装饰器 通用装饰器 定义通用装饰器 使用装饰器 偏函数 引入类库 应用 总结 装饰器 概念 装饰器就是个闭包&#xff1b;把一个函数当做参数&#xff0c;返回一个修改过功能的函数&#xff1b; 本质上是一个返回函数的函数。…

堆排序(详解)

在上篇文章中&#xff0c;我们说利用堆的插入和删除也可以排序数据&#xff0c;但排序的只是堆里面的数组&#xff1b;同时每次排序数据都要单独写一个堆的实现&#xff0c;很不方便&#xff0c;这次就来着重讲讲如何使用堆排序。 1.建堆 给了你数据&#xff0c;要利用堆对数据…

echarts修改tooltip默认的圆点图标为其他样式

业务需求&#xff0c;默认是圆点&#xff0c;需要把线的由圆点改为线 红色线是理论&#xff0c;点是历史理论&#xff0c;绿色线是实际&#xff0c; 点是历史实际&#xff0c;在series里的顺序也是这样排的。 打印出来的params里的marker就是圆点&#xff0c;改这段代码就可以了…

C#面向对象

过程类似函数只能执行没有返回值 函数不仅能执行&#xff0c;还可以返回结果 1、面向过程 a 把完成某一需求的所有步骤 从头到尾 逐步实现 b 根据开发需求&#xff0c;将某些 功能独立 的代码 封装 成一个又一个 函数 c 最后完成的代码就是顺序的调用不同的函数 特点 1、…

vue项目多个不同的服务器请求地址管理

vue项目多个不同的服务器请求地址管理 在vue项目开发过程中,获取不同的数据可能会出现需要请求多个不同服务器地址的域名,这个时候需要对不同域名的请求地址进行管理以及跨域的代理。 一、单服务器域名地址的跨域代理和请求配置: 跨域配置: 在vue项目的vue.config.js文件…

MYSQL 8.X Linux-Generic 通用版本安装

下载对应版本MySQL :: Download MySQL Community Server (Archived Versions) 这里我选择的是Linux - Generic (glibc 2.12) (x86, 64-bit), TAR 解压到服务器 只需要里面的mysql-8.0.24-linux-glibc2.12-x86_64.tar.xz 在目录下创建需要的文件夹 这里我改名为mysql-8.0.24…

贪心算法(新坑)

贪心入门 概述&#xff1a; 贪心算法是一种在每一步选择中都采取当前最优解的策略&#xff0c;希望最终能够得到全局最优解的算法。简单来说&#xff0c;它会不断地做出局部最优的选择&#xff0c;相信通过这种选择最终能够达到全局最优。 举个例子来说明。假设你要从一个迷…

智能优化算法应用:基于回溯搜索算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于回溯搜索算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于回溯搜索算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.回溯搜索算法4.实验参数设定5.算法结果6.参考…

fiddler测试弱网别再去深山老林测了,这样做就能达到弱网效果了!

弱网测试 概念&#xff1a;弱网看字面意思就是网络比较弱&#xff0c;我们通称为信号差&#xff0c;网速慢。 意义&#xff1a;模拟在地铁、隧道、电梯和车库等场景下使用APP &#xff0c;网络会出现延时、中断和超时等情况。 添加图片注释&#xff0c;不超过 140 字&#xf…

【双指针】三数之和

三数之和 在做这道题之前&#xff0c;建议建议先将两数之和做完再做&#xff0c;提升更大~ 文章目录 三数之和题目描述算法原理解法一解法二思路如下&#xff1a;处理细节问题&#xff1a; 代码编写Java代码编写C代码编写 15. 三数之和 - 力扣&#xff08;LeetCode&#xff0…