0%

Android应用主线程接收input event

Android主线程与epoll 中介绍到Android应用的主线程通过监听InputManager注册到 epoll 的文件描述符从而去监听、接收输入事件的,但是我还是有很多疑惑,InputManager是什么时候注册?怎样解注册?

Input系统的主要逻辑

在解答这些问题前,先来复习下Android Input系统的整体逻辑,system server进程存在两个和 Input Event 相关的线程,一个是 InputReader线程 ,另一个是 InputDispatcher线程InputReader线程 负责监听 /dev/input 下的设备节点,并封装成触摸屏幕对应的 MotionEvent 或者按键对应的 KeyEvent ,然后把它们放进 inBoundQueue 中,并唤醒 InputDispatcher线程InputDispatcher线程 读取 inBoundQueue 中的InputEvent,如果是 MotionEvent 则寻找Touched类型的窗口,如果是 KeyEvent 则寻找Focus类型的窗口去处理事件, InputDispatcher线程 为每一个窗口维护两个队列,一个是 outboundQueue ,另一个是 waitQueue ,寻找到窗口后将Input Event放进窗口对应的 outboundQueue ,等待发送给应用进程端的窗口 ,每一个窗口都对应一个 socket 连接,socket的server端被 InputDispatcher线程 监听,client 端被应用进程的 UI线程 监听,当socket连接建立成功,就会将 outboundQueue 里的Input Event出列并通过 socket 发送给应用端的窗口,同时将该Input Event保存到 waitQueue ,应用进程的 UI线程 这时被唤醒开始接收并处理事件,处理完成后再通过 socket 通知 InputDispatcher线程 ,这时它被唤醒并将记录在 waitQueue 的该事件移除,如果在5s内没有能移除这个事件,由于每次向该窗口发送Input Event时会检查一遍 waitQueue ,这时会发现有超时的事件,就会触发ANR通知。

建立连接

Input系统的主要逻辑中每一个步骤都对应着复杂的细节逻辑,不用去深入关心system server的实现细节,但回到应用进程端,应用窗口要和服务端建立双向的input event通道,这个流程必然是应用端主动发起的,要不然一个 InputDispatcher线程 主动维护和一堆状态未知的窗口连接得多累,那UI线程它是在什么时候通过什么方式发起连接的呢?又是在什么时候结束的呢?

我们知道在启动一个Activity的时候,真正添加Window让系统去渲染这些View是在Activity的 onResume 方法之后紧接着开始的,建立input event的 socket 连接也是从这里开始的,代码的主要调用顺序如下:

1
2
3
4
5
6
7
8
//应用进程的UI线程
->frameworks/base/core/java/android/app/ActivityThread.java handleResumeActivity()
-> frameworks/base/core/java/android/view/WindowManagerImpl.java addView()
-> frameworks/base/core/java/android/view/WindowManagerGlobal.java addView()
-> frameworks/base/core/java/android/view/ViewRootImpl.java setView()
//system server进程的Binder线程
-> IPC frameworks/base/services/core/java/com/android/server/wm/Session.java addToDisplayAsUser()

关键在代码在 frameworks/base/core/java/android/view/ViewRootImpl.java setView() 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
.......
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();//新建Input Event的管道对象
}
......
try {
......
//IPC调用到system server进程,创建socket pair,inputChannel会被赋值,它就代表socket pair的客户端,
//用于监听、接收InputDispatch线程中socket pair服务端发送过来的input event
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
.......
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}

......
if (inputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
//创建WindowInputEventReceiver,会被主线的epoll回调
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
......
}

......
//处理input event的InputStage责任链
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);

mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
}
}

setView() 会调用到 mWindowSessionaddToDisplayAsUser ,这是一个跨进程调用,调用到system server进程binder线程创建socket pair,传入的inputChannel实参会被赋值,它就代表socket pair的客户端,用于监听、接收InputDispatch线程中socket pair服务端发送过来的input event。

监听、接收input event

setView() 中创建完socket 后,会新建一个 WindowInputEventReceiver 顾名思义用来接收input event的,新建 WindowInputEventReceiver 的流程如下:

1
2
3
4
5
6
-> frameworks/base/core/java/android/view/ViewRootImpl.java WindowInputEventReceiver(InputChannel, Looper)
-> frameworks/base/core/java/android/view/InputEventReceiver.java InputEventReceiver(InputChannel, Looper)
-> frameworks/base/core/jni/android_view_InputEventReceiver.cpp nativeInit(receiverWeak,inputChannelObj, messageQueueObj)
-> frameworks/base/core/jni/android_view_InputEventReceiver.cpp initialize()
-> frameworks/base/core/jni/android_view_InputEventReceiver.cpp setFdEvents(int events)
->

其中 setFdEvents 是点睛之笔:

1
2
3
4
5
6
7
8
9
10
11
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
int fd = mInputConsumer.getChannel()->getFd();
if (events) {//将input channel的文件描述符添加到epoll的监听列表中,并且回调方法就是这个NativeInputEventReceiver
mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
} else {
mMessageQueue->getLooper()->removeFd(fd);
}
}
}

这样,在Activity perform resume 时,传输input event的socket就已经建立好了,并且在应用进程端,window持有的的客户端socket fd被添加到UI线程的的epoll监听列表中,一旦有输入事件就会唤醒主线程执行 response.request.callback->handleEvent() ,这时这个callback正是这个 NativeInputEventReceiver ,它会按照在 frameworks/base/core/java/android/view/ViewRootImpl.java setView() 末尾设置的 InputStage 责任链依次询问 xxxxInputStage 是否处理输入事件。

结束连接

我们知道,一个Activity detach window是在 Activity执行destroy时,那么解注册主线程epoll监听的这个socket fd也应该是在这里,调用顺序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
-> frameworks/base/core/java/android/app/ActivityThread.java handleDestroyActivity()
-> frameworks/base/core/java/android/view/WindowManagerImpl.java removeViewImmediate()
-> frameworks/base/core/java/android/view/WindowManagerGlobal.java removeView()
-> frameworks/base/core/java/android/view/WindowManagerGlobal.java removeViewLocked()
-> frameworks/base/core/java/android/view/ViewRootImpl.java die()
-> frameworks/base/core/java/android/view/ViewRootImpl.java doDie()
-> frameworks/base/core/java/android/view/ViewRootImpl.java dispatchDetachedFromWindow()
-> frameworks/base/core/java/android/view/ViewRootImpl$WindowInputEventReceiver.java dispose()
-> frameworks/base/core/java/android/view/InputEventReceiver.java dispose()
-> frameworks/base/core/jni/android_view_InputEventReceiver.cpp nativeDispose(jlong receiverPtr)
-> frameworks/base/core/jni/android_view_InputEventReceiver.cpp dispose()
-> frameworks/base/core/jni/android_view_InputEventReceiver.cpp setFdEvents(0)