InputManagerService启动流程分析

程序员Android

共 11806字,需浏览 24分钟

 ·

2022-11-04 03:45

b3eff740c292a7e4ce755636bff75525.webp

和你一起终身学 习,这里是程序员Android

经典好文推荐,通过阅读本文,您将收获以下知识点:

一、前言
二、启动流程
2.1 创建输入系统
2.2 启动输入系统
2.3 输入系统就绪

一、前言

之前写过几篇关于输入系统的文章,但是还没有写完,后来由于工作的变动,这个事情就一直耽搁了。而现在,在工作中,遇到输入系统相关的事情也越来越多,其中有一个非常有意思的需求,因此是时候继续分析 InputManagerService。

InputManagerService 系统文章,基于 Android 12 进行分析。

本文将以 IMS 简称 InputManagerService。

二、启动流程

InputManagerService 是一个系统服务,启动流程如下

    // SystemServer.java

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
// ..

// 1. 创建
inputManager = new InputManagerService(context);
// 注册服务
ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
/* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);

// 保存 wms 的回调
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
// 2. 启动
inputManager.start();


try {
// 3. 就绪
if (inputManagerF != null) {
inputManagerF.systemRunning();
}
} catch (Throwable e) {
reportWtf("Notifying InputManagerService running", e);
}

// ...
}

IMS 的启动流程分为三步

1.创建输入系统,建立上层与底层的映射关系。
2.启动输入系统,其实就是启动底层输入系统的几个模块。
3.输入系统就绪,上层会同步一些配置给底层输入系统。

下面分三个模块,分别讲解这三步。

2.1 创建输入系统
    // InputManagerService.java

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

// 配置为空
mStaticAssociations = loadStaticInputPortAssociations();

// 默认 false
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);

// 1. 底层进行初始化
// mPtr 指向底层创建的 NativeInputManager 对象
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

// 空
String doubleTouchGestureEnablePath = context.getResources().getString(
R.string.config_doubleTouchGestureEnableFile);
// null
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);

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

IMS 构造函数,主要就是调用 nativeInit() 来初始化底层输入系统。

    // com_android_server_input_InputManagerService.cpp

static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
// 从Java层的MessageQueue中获取底层映射的MessageQueue
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == nullptr) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}

// 创建 NativeInputManager
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);

// 返回指向 NativeInputManager 对象的指针
return reinterpret_cast<jlong>(im);
}

原来底层创建了 NativeInputManager 对象,然后返回给上层。

但是 NativeInputManager 并不是底层输入系统的服务,它只是一个连接上层输入系统和底层输入系统的桥梁而已。来看下它的创建过程

    // com_android_server_input_InputManagerService.cpp

NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();

// 1.保存上层的InputManagerService对象
mServiceObj = env->NewGlobalRef(serviceObj);

// 2. 初始化一些参数
{
AutoMutex _l(mLock);
// mLocked 的类型是 struct Locked,这里初始化了一些参数
// 这些参数会被上层改变
mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
mLocked.pointerCapture = false;
mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
}
mInteractive = true;

// 3.创建并注册服务 InputManager
mInputManager = new InputManager(this, this);
defaultServiceManager()->addService(String16("inputflinger"),
mInputManager, false);
}

NativeInputManager 构造过程如下

1.创建一个全局引用,并通过 mServiceObj 指向上层的 InputManagerService 对象。
2.初始化参数。这里要注意一个结构体变量 mLocked,它的一些参数都是由上层控制的。例如,mLocked.showTouches 是由开发者选项中 "Show taps" 决定的,它的功能是在屏幕上显示一个触摸点。
3.创建并注册服务 InputManager。

原来,InputManager 才是底层输入系统的服务,而 NativeInputManagerService 通过 mServiceObj 保存了上层 InputManagerService 引用,并且上层 InputManagerService 通过 mPtr 指向底层的 NativeInputManager。因此,我们可以判定 NativeInputManager 就是一个连接上层与底层的桥梁。

我们注意到创建 InputManager 使用了两个 this 参数,这里介绍下 NativeInputManager 和 InputManager 的结构图

67b93e42ddc11fbe83733b3762616535.webp

InputManager 构造函数需要的两个接口正好是由 NativeInputManager 实现的,然而,具体使用这两个接口的不是 InputManager,而是它的子模块。这些子模块都是在 InputManager 的构造函数中创建的

    // InputManager.cpp
InputManager::InputManager(
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
// 1. 创建InputDispatcher对象,使用 InputDispatcherPolicyInterface 接口
mDispatcher = createInputDispatcher(dispatcherPolicy);

// 2. 创建InputClassifier对象,使用 InputListenerInterface
mClassifier = new InputClassifier(mDispatcher);

// 3. 创建InputReader对象,使用 InputReaderPolicyInterface 和 InputListenerInterface
mReader = createInputReader(readerPolicy, mClassifier);
}

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

// InputReaderFactory.cpp
sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) {
return new InputReader(std::make_unique<EventHub>(), policy, listener);
}

InputManager 构造函数所使用的两个接口,分别由 InputDispatcher 和 InputReader 所使用。因此 InputManager 向上通信的能力是由子模块 InputDispatcher 和 InputReader 实现的。

InputManager 创建了三个模块,InputReader、InputClassifier、InputDispatcher。 InputReader 负责从 EventHub 中获取事件,然后把事件加工后,发送给 InputClassfier。InputClassifer 会把事件发送给 InputDispatcher,但是它会对触摸事件进行一个分类工作。最后 InputDispatcher 对进行事件分发。

那么现在我们可以大致推算下输入系统的关系图,如下

71766f48ab96b4914a342f1c0ad25654.webp

这个关系图很好的体现了设计模式的单一职责原则。

EventHub 其实只属于 InputReader,因此要想解剖整个输入系统,我们得逐一解剖 InputReader、InputClassifier、InputDispatcher。后面的一系列的文章将逐个来剖析。

2.2 启动输入系统
    // InputManagerService.java

public void start() {
Slog.i(TAG, "Starting input manager");
// 1.启动native层
nativeStart(mPtr);

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

// 2.监听数据库,当值发生改变时,通过 native 层
// 监听Settings.System.POINTER_SPEED,这个表示手指的速度
registerPointerSpeedSettingObserver();
// 监听Settings.System.SHOW_TOUCHES,这个表示是否在屏幕上显示触摸坐标
registerShowTouchesSettingObserver();
// 监听Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON
registerAccessibilityLargePointerSettingObserver();
// 监听Settings.Secure.LONG_PRESS_TIMEOUT,这个多少毫秒触发长按事件
registerLongPressTimeoutObserver();
// 监听用户切换
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);

// 3. 从数据库获取值,并传递给 native 层
updatePointerSpeedFromSettings();
updateShowTouchesFromSettings();
updateAccessibilityLargePointerFromSettings();
updateDeepPressStatusFromSettings("just booted");
}

输入系统的启动过程如下

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

第2步和第3步,本质上其实都是更新配置到底层,但是需要我们对 InputReader 的运行过程比较熟悉,因此这个配置更新过程,留到后面分析。

现在我们直接看下如何启动底层的输入系统

    // com_android_server_input_InputManagerService.cpp

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

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

通过 JNI 层的 NativeInputManager 这个桥梁来启动 InputManager。

前面用一幅图表明了 NativeInputManager 的桥梁作用,现在感受到了吗?

    status_t InputManager::start() {
// 启动 Dispatcher
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 的启动过程很简单,就是直接启动它的子模块 InputDispatcher 和 InputReader。

InputDispatcher 和 InputReader 的启动,都是通过 InputThread 创建一个线程来执行任务。

    //InputThread.cpp

InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
: mName(name), mThreadWake(wake) {
mThread = new InputThreadImpl(loop);
mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}

注意 InputThread 可不是一个线程,InputThreadImpl 才是一个线程,如下

    //InputThread.cpp

class InputThreadImpl : public Thread {
public:
explicit InputThreadImpl(std::function<void()> loop)
: Thread(/* canCallJava */ true), mThreadLoop(loop) {}

~InputThreadImpl() {}

private:
std::function<void()> mThreadLoop;

bool threadLoop() override {
mThreadLoop();
return true;
}
};

当线程启动后,会循环调用 threadLoop(),直到这个函数返回 false。从 InputThreadImpl 的定义可以看出,threadLoop() 会一直保持循环,并且每一次循环,会调用一次 mThreadLoop(),而函数 mThreadLoop 是由 InputReader 和 InputDispacher 在启动时传入

    // InputReader.cpp
status_t InputReader::start() {
if (mThread) {
return ALREADY_EXISTS;
}
// 线程启动后,循环调用 loopOnce()
mThread = std::make_unique<InputThread>(
"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
return OK;
}

// InputDispatcher.cpp
status_t InputDispatcher::start() {
if (mThread) {
return ALREADY_EXISTS;
}
// 线程启动后,循环调用 dispatchOnce()
mThread = std::make_unique<InputThread>(
"InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
return OK;
}

现在,我们可以明白,InputReader 启动时,会创建一个线程,然后循环调用 loopOnce() 函数,而 InputDispatcher 启动时,也会创建一个线程,然后循环调用 dispatchOnce()。

2.3 输入系统就绪
    // InputManagerService.java

public void systemRunning() {
mNotificationManager = (NotificationManager)mContext.getSystemService(
Context.NOTIFICATION_SERVICE);

synchronized (mLidSwitchLock) {
mSystemReady = true;

// Send the initial lid switch state to any callback registered before the system was
// ready.
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);
}
}

// 监听广播,通知底层加载键盘布局
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) {
updateKeyboardLayouts();
}
}, filter, null, mHandler);

// 监听广播,通知底层加载设备别名
filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
reloadDeviceAliases();
}
}, filter, null, mHandler);

// 直接通知一次底层加载键盘布局和加载设备别名
mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);

if (mWiredAccessoryCallbacks != null) {
mWiredAccessoryCallbacks.systemReady();
}
}

private void reloadKeyboardLayouts() {
nativeReloadKeyboardLayouts(mPtr);
}

private void reloadDeviceAliases() {
nativeReloadDeviceAliases(mPtr);
}

无论是通知底层加载键盘布局,还是加载设备别名,其实都是让底层更新配置。与前面一样,更新配置的过程,留到后面分析。

作者:大胃粥
链接:https://juejin.cn/post/7161376731096432653

友情推荐:

Android 开发干货集锦

至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

点击阅读原文,为大佬点赞!

浏览 32
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报