Android输入系统之 的创建与启动
今天趁着在公司摸鱼的时间,来更新一篇文章。
上一篇文章 InputManagerService的创建与启动 分析了 IMS 的创建与启动,这其中就伴随着 InputReader 的创建与启动,本文就着重分析这两点内容。
本文所涉及的文件路径如下
frameworks/native/services/inputflinger/InputManager.cpp frameworks/native/services/inputflinger/reader/EventHub.cpp frameworks/native/services/inputflinger/reader/InputReader.cpp frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp frameworks/native/services/inputflinger/InputThread.cpp
从 InputManagerService的创建与启动 可知,创建 InputReader 的代码如下
InputManager::InputManager(
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
// ...
mReader = createInputReader(readerPolicy, mClassifier);
}
sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) {
// InputReader从EventHub中读取数据
// policy 实现类是 NativeInputManager
// listener的实现类其实是 InputClassifier
return new InputReader(std::make_unique<EventHub>(), policy, listener);
}
创建 InputReader 需要三个参数。
第一个参数的类型是 EventHub。正如名字所示,它是输入事件的中心,InputReader 会从 EventHub 中读取事件。这个事件分两类,一个是输入设备的输入事件,另一个是 EventHub 合成事件,用于表明设备的挂载与卸载。
第二个参数的类型为 InputReaderPolicyInterface,由 InputManagerService的创建与启动 可知,它的实现类是 JNI 层的 NativeInputManager。
第三个参数的类型为 InputListenerInterface, 由 InputManagerService的创建与启动 可知,它的实现类是 InputClassifier。InputReader 会的加工后的事件发送给 InputClassifier,而 InputClassifier 会针对触摸事件进行分类,再发送到 InputDispatcher。
为止防止大家有所健忘,我把上一篇文章中,关于事件的流程图,再展示下
graph TD
EventHub --> InputReader
InputReader --> NativeInputManager
InputReader --> InputClassifer
InputClassifer --> InputDispatcher
InputDispatcher --> NativeInputManager
NativeInputManager --> InputManagerService
创建EventHub
创建 InputReader 首先需要一个 EventHub 对象,因此我们首先得看下 EventHub 的创建过程
EventHub::EventHub(void)
: mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
mNextDeviceId(1),
mControllerNumbers(),
mOpeningDevices(nullptr),
mClosingDevices(nullptr),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false),
mNeedToScanDevices(true),
mPendingEventCount(0),
mPendingEventIndex(0),
mPendingINotify(false) {
ensureProcessCanBlockSuspend();
// 创建epoll
mEpollFd = epoll_create1(EPOLL_CLOEXEC);
// 初始化inotify
mINotifyFd = inotify_init();
// 1. 使用inotify监听/dev/input目录下文件的创建与删除事件
mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
// ...
struct epoll_event eventItem = {};
eventItem.events = EPOLLIN | EPOLLWAKEUP;
eventItem.data.fd = mINotifyFd;
// 2. epoll监听inotify可读事件
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
// 3. 创建两个管道
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;
// 4. epoll监听mWakeReadPipeFd可读事件
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
第一步和第二步,初始化 inotify 监听 /dev/input/ 目录下的文件的创建和删除事件,然后使用 epoll 管理这个 inotify。
为何要使用 inotify 监听 /dev/input/ 目录呢,因为当输入设备挂载和卸载时,内核会相应地在这个目录下创建和删除设备文件,因此监听这个目录可获知当前有哪些输入设备,然后才能监听这些设备的输入事件。
第三步和第四步,创建了两个管道,其中一个管道也被 epoll 管理起来,这个管道是用来唤醒 InputReader 线程。例如当配置发生改变时,这个管道会被用来唤醒 InputReader 线程来处理配置的改变。
另一个管道用于做什么呢?
现在 epoll 已经管理了两个文件描述符,mINotifyFd 和 mWakeReadPipeFd。但是现在并没有启动 epoll 来监听它们的可读事件,这是因为 InputReader 还没有准备好,让我们继续往下看。
本文不想浪费篇幅去介绍 Linux inotify 和 epoll 机制,这两个机制并不复杂,请大家自己去了解。
创建 InputReader
EventHub 已经创建完毕,现在来看下创建 InputReader 的过程
InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener)
: mContext(this), // ContextImpl mContext 是一个关于 InputReader 的环境
mEventHub(eventHub),
mPolicy(policy), // 由 NativeInputManager实现
mGlobalMetaState(0),
mGeneration(1),
mNextInputDeviceId(END_RESERVED_ID),
mDisableVirtualKeysTimeout(LLONG_MIN),
mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
// 1. 创建QueuedInputListener对象
// 事件的转换都是通过 InputListenerInterface 接口
// QueuedInputListener 是一个继承并实现了 InputListenerInterface 接口的代理类
// QueuedInputListener 把事件加入队列,并推迟发送事件直到调用它的flush()函数
mQueuedListener = new QueuedInputListener(listener);
{ // acquire lock
AutoMutex _l(mLock);
// 2. 更新配置,保存到mConfig中
refreshConfigurationLocked(0);
// 根据已经映射的设备,更新 mGlobalMetaState 的值
// 由于目前还没有映射设备,所以mGlobalMetaState值为0
updateGlobalMetaStateLocked();
} // release lock
}
InputReader 构造函数看似平平无奇,实际上有许多值得注意的地方。
首先注意 mContext 变量,它的类型是 ContextImpl,这是一个表示 InputReader 的环境,由于 ContextImpl 是 InputReader 的友元类,因此透过 ContextImpl 可以访问 InputReader 的私有数据。
那么这个 mContext 变量被谁所用呢? InputReader 会为物理输入设备建立一个映射类 InputDevice,这个 InputDevice 就会保存这个 mContext 变量,InputDevice 会通过 mContext,从 InputReader 中获取全局的设备状态以及参数。
InputReader 构造函数使用了一个 InputClassifier 接口对象,由 InputManagerService的创建与启动 可知,InputListenerInterfac 接口的实现类是 InputClassifier。 实际上,InputListenerInterface 接口是专为传递事件设计的。因此只要你看到哪个类实现 ( 在c++中叫继承 ) 了 InputListenerInterface 这个接口,那么它肯定是传递事件中的一环。
mQueuedListener 变量的类型是 QueuedInputListener ,恰好这个类也实现了 InputListenerInterface 接口,那么它肯定也传递事件。然而 QueuedInputListener 只是一个代理类,InputReader 会把事件存储到 QueuedInputListener 的队列中,然后直到 QueuedInputListener::flush() 函数被调用,QueuedInputListener 才把队列中的事件发送出去。发送给谁呢,就是 InputClassifier。
那么现在我们来总结下,事件通过 InputListenerInterface 接口传递的关系图
graph TD
InputReader --> |InputListenerInterface|QueuedInputListener
QueuedInputListener --> |InputListenerInterface|InputClassifier
InputClassifier --> |InputListenerInterface|InputDispatcher
最后,我们来一件挺烦琐的小事,InputReader 读取配置,它调用的是如下代码
// 注意,参数 changes 值为0
void InputReader::refreshConfigurationLocked(uint32_t changes) {
// 从NativeInputManager中获取配置,保存到mConfig中
mPolicy->getReaderConfiguration(&mConfig);
// EventHub保存排除的输入设备
mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
if (changes) {
// ...
}
}
mPolicy 的实现类是 JNI 层 NativeInputManager,由 InputManagerService的创建与启动 可知, NativeInputManager 只是一个桥梁作用,那么它肯定是向上层的 InputManagerService 获取配置,是不是这样呢,来验证下。
void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
ATRACE_CALL();
JNIEnv* env = jniEnv();
// 0
jint virtualKeyQuietTime = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getVirtualKeyQuietTimeMillis);
if (!checkAndClearExceptionFromCallback(env, "getVirtualKeyQuietTimeMillis")) {
outConfig->virtualKeyQuietTime = milliseconds_to_nanoseconds(virtualKeyQuietTime);
}
outConfig->excludedDeviceNames.clear();
// 如下两个文件定义了排除的设备
// /system/etc/excluded-input-devices.xml
// /vendor/etc/excluded-input-devices.xml
jobjectArray excludedDeviceNames = jobjectArray(env->CallStaticObjectMethod(
gServiceClassInfo.clazz, gServiceClassInfo.getExcludedDeviceNames));
if (!checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && excludedDeviceNames) {
jsize length = env->GetArrayLength(excludedDeviceNames);
for (jsize i = 0; i < length; i++) {
std::string deviceName = getStringElementFromJavaArray(env, excludedDeviceNames, i);
outConfig->excludedDeviceNames.push_back(deviceName);
}
env->DeleteLocalRef(excludedDeviceNames);
}
// Associations between input ports and display ports
// The java method packs the information in the following manner:
// Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
// Received data: ['inputPort1', '1', 'inputPort2', '2']
// So we unpack accordingly here.
// 输入端口和显示端口绑定的关系,一种是静态绑定,来自于/vendor/etc/input-port-associations.xml
// 而另一种是来自于运行时的动态绑定,并且动态绑定可以覆盖静态绑定。
outConfig->portAssociations.clear();
jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj,
gServiceClassInfo.getInputPortAssociations));
if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
jsize length = env->GetArrayLength(portAssociations);
for (jsize i = 0; i < length / 2; i++) {
std::string inputPort = getStringElementFromJavaArray(env, portAssociations, 2 * i);
std::string displayPortStr =
getStringElementFromJavaArray(env, portAssociations, 2 * i + 1);
uint8_t displayPort;
// Should already have been validated earlier, but do it here for safety.
bool success = ParseUint(displayPortStr, &displayPort);
if (!success) {
ALOGE("Could not parse entry in port configuration file, received: %s",
displayPortStr.c_str());
continue;
}
outConfig->portAssociations.insert({inputPort, displayPort});
}
env->DeleteLocalRef(portAssociations);
}
// 下面这些都与悬浮点击有关系,如果触摸屏支持悬浮点击,可以研究下这些参数
jint hoverTapTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getHoverTapTimeout);
if (!checkAndClearExceptionFromCallback(env, "getHoverTapTimeout")) {
jint doubleTapTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getDoubleTapTimeout);
if (!checkAndClearExceptionFromCallback(env, "getDoubleTapTimeout")) {
jint longPressTimeout = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getLongPressTimeout);
if (!checkAndClearExceptionFromCallback(env, "getLongPressTimeout")) {
outConfig->pointerGestureTapInterval = milliseconds_to_nanoseconds(hoverTapTimeout);
// We must ensure that the tap-drag interval is significantly shorter than
// the long-press timeout because the tap is held down for the entire duration
// of the double-tap timeout.
jint tapDragInterval = max(min(longPressTimeout - 100,
doubleTapTimeout), hoverTapTimeout);
outConfig->pointerGestureTapDragInterval =
milliseconds_to_nanoseconds(tapDragInterval);
}
}
}
// 悬浮移动距离
jint hoverTapSlop = env->CallIntMethod(mServiceObj,
gServiceClassInfo.getHoverTapSlop);
if (!checkAndClearExceptionFromCallback(env, "getHoverTapSlop")) {
outConfig->pointerGestureTapSlop = hoverTapSlop;
}
// 如下mLocked的相关参数是在 NativeInputManager 的构造函数中初始化的
// 但是这些参数都是可以通过 InputManagerService 改变的
{ // acquire lock
AutoMutex _l(mLock);
outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
* POINTER_SPEED_EXPONENT);
outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
outConfig->showTouches = mLocked.showTouches;
outConfig->pointerCapture = mLocked.pointerCapture;
outConfig->setDisplayViewports(mLocked.viewports);
outConfig->defaultPointerDisplayId = mLocked.pointerDisplayId;
outConfig->disabledDevices = mLocked.disabledInputDevices;
} // release lock
}
从这里可以看出,InputReader 获取配置的方式,是通过 JNI 层的 NativeInputManager 向 Java 层的 InputManagerService 获取的。
但是这些配置并不是不变的,当Java层改变这些配置后,会通过 JNI 层的 NativeInputManager 通知 InputReader ( 注意,不是InputReader线程 ),然后通过 EventHub::wake() 函数通过管道唤醒 InputReader 线程来处理配置改变。这个过程可以在阅读完本文后,自行分析。
启动 InputReader
现在 InputReader 已经创建完毕,让我们继续看下它的启动过程。
由 InputManagerService的创建与启动 可知,启动 InputReader 的代码如下
status_t InputReader::start() {
if (mThread) {
return ALREADY_EXISTS;
}
// 创建线程并启动
mThread = std::make_unique<InputThread>(
"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
return OK;
}
InputThread 封装了 c++ 的 Thread 类
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;
}
};
当 InputThread 对象创建的时候,会启动一个线程
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);
}
线程会循环调用 loopOnce() 函数,也就是 InputThread 构造函数的第二个参数,它的实现函数是 InputReader::loopOnce() 函数。
我们注意到 InputThread 构造函数还有第三个参数,它是在 InputThread 析构函数调用的。
InputThread::~InputThread() {
mThread->requestExit();
// mThreadWake 就是构造函数中的第三个参数
if (mThreadWake) {
mThreadWake();
}
mThread->requestExitAndWait();
}
那么什么时候会调用 InputThread 的析构函数呢,我觉得应该是 system_server 进程挂掉的时候,此时会调用 EventHub::wake() 来唤醒 InputReader 线程,从而退出 InputReader 线程。而这个唤醒的方式,就是使用刚才在 EventHub 中创建的一个管道。
现在来分析下 InputReader::loopOnce() 函数,这里就是 InputReader 线程所做的事
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
// 1. 处理配置改变
{ // acquire lock
AutoMutex _l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
uint32_t changes = mConfigurationChangesToRefresh;
if (changes) {
mConfigurationChangesToRefresh = 0;
timeoutMillis = 0;
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) { // mNextTimeout 也属于配置
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock
// 2. 读取事件
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
// 3. 处理事件
{ // acquire lock
AutoMutex _l(mLock);
mReaderIsAliveCondition.broadcast();
// 如果读到事件,就处理
if (count) {
processEventsLocked(mEventBuffer, count);
}
// 处理超时情况
if (mNextTimeout != LLONG_MAX) {
// ...
}
// mGeneration 表明输入设备改变
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
// 对inputDevices填充inputDeviceInfo,而这个InputDeviceInfo是从InputDevice中获取
getInputDevicesLocked(inputDevices);
}
} // release lock
// 4. 通知设备改变
if (inputDevicesChanged) {
// mPolicy实现类为NativeInputManager
mPolicy->notifyInputDevicesChanged(inputDevices);
}
// 5. 事件发送给 InputClassifier。
mQueuedListener->flush();
}
我第一次看到这段代码时,头皮发麻,InputReader 做了这么多事情,我该怎么分析呢?不要紧,让我来梳理下思路。
首先看第一步,这一步是处理配置改变。前面我们谈论过这个话题,当配置发生改变时,一般都通过 Java 层的 InputManagerService 发送信息给 JNI 层的 NativeInputManager ,然后再通知 InputReader (注意不是InputReader线程),InputReader 会通过 EventHub::wake() 函数来唤醒 InputReader 线程来处理配置改变。这就是第一步做的事件。鉴于篇幅原因,这个过程就不分析了。
第二步,从 EventHub 获取数据。这个获取数据的过程其实分三种情况。
-
第一种情况,发生在系统首次启动,并且没有输入事件发生,例如手指没有在触摸屏上滑动。EventHub 会扫描输入设备,并建立与输入设备相应的数据结构,然后创建多个 EventHub 自己合成的事件,最后把这些事件返回给 InputReader 线程。为何要扫描设备,前面已经说过,是为了监听输入设备事件。
-
第二种情况,发生在系统启动完毕,然后有输入事件,例如手指在触摸屏上滑动。EventHub 会把 /dev/input/ 目录下的设备文件中的原始数据,包装成一个事件,发送给 InputReader 线程处理。
-
第三种情况,系统在运行的过程中,发生设备的挂载和卸载,EventHub 也会像第一种情况一样,合成自己的事件,并发送给 InputReader 线程处理。其实第一种情况和第三种情况下,InputReader 线程对事件的处理是类似的。因此后面的文章并不会分析这种情况。
第三步,获取完事件后,就处理这些事件。
第四步,通知监听者,设备发生改变。谁是监听者呢,就是上层的 InputManagerService。
第五步,把InpuReader加工好的事件发送给 InputClassifier。
事件发送关系图
经过本文的分析,我们可以得出一张事件发送关系图,以及各个组件如何通信的关系图
graph TD
EventHub --> |EventHub::getEvent|InputReader
InputReader --> |InputReaderPolicyInterface|NativeInputManager
InputReader --> |InputListenerInterface|InputClassifer
InputClassifer --> |InputListenerInterface|InputDispatcher
InputDispatcher --> |InputDispatcherPolicyInterface|NativeInputManager
NativeInputManager --> |mServiceObj|InputManagerService
结束
简简单单的 InputReader 的创建与启动就分析完了,而本文仅仅是描述了一个轮廓,但是我就问你复杂不复杂?复杂吧,不过没关系,只要我们理清思路,我们就一步一步来。那么下篇文章,我们来分析系统启动时,EventHub 是如何扫描设备并发送合成事件,以及 InputReader 线程是如何处理这些合成事件。