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

news2024/11/19 19:38:29

文章目录

  • 前言
  • 一、输入系统的基本组成部分
  • 二、输入系统相关源码分析
    • 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

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

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

相关文章

Vue路由嵌套和携带参数的几种方法

1、路由嵌套 路由嵌套逻辑&#xff1a; router.index.js中使用children嵌套子路由 //该文件专门用于创建整个文件的路由器 import VueRouter from vue-routerimport About from "/pages/About"; import Home from "/pages/Home"; import News from "…

蓝桥杯物联网竞赛_STM32L071_5_串口接收发送数据

理论&#xff1a; 串口采取异步通信&#xff0c;即不依赖时钟节拍来接收或发送数据&#xff0c;而是采用互相约定的波特率传输数据。 波特率与单位时间传输的比特数有关&#xff0c;波特率越大传输的数据越多 传输一个比特花费的时间T 1 / 比特率 接受和发送数据的时候需要…

vue中的插槽用法(动态插槽)

vue中提供了一种通讯方式叫插槽>分为&#xff1a;默认插槽、具名插槽(作用域插槽) 1. 当一个组件有不确定的结构时, 就需要使用slot技术了 2. 注意: 插槽内容是在父组件中编译后, 再传递给子组件 3. 如果决定结构的数据在父组件, 那用默认slot或具名slot (1) 当只有一个不…

WPF创建进度条

使用wpf做一个原生的进度条&#xff0c;进度条上面有值&#xff0c;先看效果。 功能就是点击按钮&#xff0c;后台处理数据&#xff0c;前台显示处理数据的变化&#xff0c;当然还可以对进度条进行美化和关闭的操作&#xff0c;等待后台处理完毕数据&#xff0c;然后自动关闭。…

OPENWRT路由配置IPV6公网访问

前提&#xff1a;已经拥有ipv6地址&#xff0c;不会配置ipv6的同学可以看我的上一篇文章。 一、光猫配置 1.1、修改光猫连接方式变为桥接&#xff1a; 其中需要注意的点为&#xff1a; 1.需要将原先的xxxx_VID_41的连接删掉&#xff0c;然后按照下面的配置进行 2.删掉之前…

相关性分析和作图

相关的类型 1. Pearson、Spearman和Kendall相关 Pearson 积差相关系数衡量了两个定量变量之间的线性相关程度。&#xff08;连续&#xff09; Spearman等级相关系数则衡量分级定序变量之间的相关程度。&#xff08;分类&#xff09; Kendall’s Tau 相关系数也是一种非参数的…

数据结构 | 查找

基本概念 关键字&#xff1a;数据元素中唯一标识该元素的某个数据项的值&#xff0c;使用基于关键字的查找&#xff0c;查找结果应该是唯一的。例如&#xff0c;在由一个学生元素构成的数据集合中&#xff0c;学生元素中“学号”这一数据项的值唯一地标识一名学生。 查找表&a…

【深入解析git和gdb:版本控制与调试利器的终极指南】

【本节目标】 1. 掌握简单gdb使用于调试 2. 学习 git 命令行的简单操作, 能够将代码上传到 Github 上 1.Linux调试器-gdb使用 1.1.背景 程序的发布方式有两种&#xff0c;debug模式和release模式release模式不可被调试&#xff0c;debug模式可被调试Linux gcc/g出来的二进制…

钉钉直播不了检查防火墙配置没有拦截应用测试直通都放行的,电脑还可以ping通直播域名,就是开始不了直播

环境: 防火墙 AF8.0.17 Win10 专业版 问题描述: 钉钉直播不了检查防火墙配置没有拦截应用测试直通都放行的,电脑还可以ping通直播域名,就是开始不了直播 钉钉直播不了 不能直播电脑电脑可以ping通直播域名 防火墙查了3个域名都没有拦截,AF测试应用直通都放行的 解…

CSS问题:如何实现瀑布流布局?

前端功能问题系列文章&#xff0c;点击上方合集↑ 序言 大家好&#xff0c;我是大澈&#xff01; 本文约2500字&#xff0c;整篇阅读大约需要4分钟。 本文主要内容分三部分&#xff0c;如果您只需要解决问题&#xff0c;请阅读第一、二部分即可。如果您有更多时间&#xff…

新!2023初中生古诗文大会阅读专辑的考点Word版和在线模拟题来了

如六分成长之前的文章中所述&#xff0c;小学生古诗文大会复选&#xff08;复赛&#xff09;的出题趋势表明&#xff0c;有一些题目出自小学生古诗文阅读专辑&#xff0c;大家纷纷表示考得很细、很难。 所以合理推测&#xff0c;初中生古诗文大会复选&#xff08;复赛&#xf…

如何运行C/C++程序

一、在线运行C/C 码曰 - 让代码在云端多飞一会&#xff1a;这是一个支持C/C&#xff0c;Java&#xff0c;Python等多种语言的在线编程&#xff0c;编译运行&#xff0c;粘贴分享的平台。你可以在这里输入你的代码&#xff0c;点击运行按钮&#xff0c;就可以看到输出结果。你也…

Redis(二):常见数据类型:String 和 哈希

引言 Redis 提供了 5 种数据结构&#xff0c;理解每种数据结构的特点对于 Redis 开发运维⾮常重要&#xff0c;同时掌握每 种数据结构的常⻅命令&#xff0c;会在使⽤ Redis 的时候做到游刃有余。 Redis 的命令有上百种&#xff0c;我们不可能全部死记硬背下来&#xff0c;但是…

9.2 Windows驱动开发:内核解析PE结构导出表

在笔者的上一篇文章《内核特征码扫描PE代码段》中LyShark带大家通过封装好的LySharkToolsUtilKernelBase函数实现了动态获取内核模块基址&#xff0c;并通过ntimage.h头文件中提供的系列函数解析了指定内核模块的PE节表参数&#xff0c;本章将继续延申这个话题&#xff0c;实现…

vue实现动态路由菜单!!!

目录 总结一、步骤1.编写静态路由编写router.jsmain.js注册 2.编写permisstions.js权限文件编写permisstions.jsaxios封装的APIstore.js状态库system.js Axios-APIrequest.js axios请求实例封装 3.编写菜单树组件MenuTree.vue 4.主页中使用菜单树组件 总结 递归处理后端响应的…

java基础-IO

1、基础概念 1.1、文件(File) 文件的读写可以说是开发中必不可少的部分&#xff0c;因为系统会存在大量处理设备上的数据&#xff0c;这里的设备指硬盘&#xff0c;内存&#xff0c;键盘录入&#xff0c;网络传输等。当然这里需要考虑的问题不仅仅是实现&#xff0c;还包括同步…

人工智能|机器学习——机器学习如何判断模型训练是否充分

一、查看训练日志 训练日志是机器学习中广泛使用的训练诊断工具&#xff0c;每个 epoch 或 iterator 结束后&#xff0c;在训练集和验证集上评估模型&#xff0c;并以折线图的形式显示模型性能和收敛状况。训练期间查看模型的训练日志可用于判断模型训练时的问题&#xff0c;例…

IOC DI入门

1.加上Component&#xff0c;控制翻转&#xff0c;将service和dao都交给IOC容器管理&#xff0c;成为IOC容器中的bean。用哪个类就在哪个类上面加component。 2.加上autowired。依赖注入。controller依赖于service&#xff0c;service依赖于dao。加上时&#xff0c;IOC容器会提…

Taro3+Vue3重构Mpvue小程序项目踩坑记

1、Taro小程序编译时报错&#xff1b; 原因:页面中存在小程序识别不了的标签&#xff1b;如div解决方法&#xff1a; 将div标签替换成小程序可识别的标签&#xff1b; 安装Taro中提供的插件:tarojs/plugin-html, 使其可被识别&#xff1b; 插件安装教程参考Taro官网&#xff1…

Matlab 点云曲率计算(之二)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 之前已经讨论过许多关于计算曲率的问题,这里使用一个通过拟合三次曲面方程的方式来计算曲率,计算过程如下图所示: 二、实现代码 %********