文章参考:http://gityuan.com/2016/12/24/input-ui/

概述

前面文章都是介绍了两个线程InputReader和InputDispatcher的工作过程。

在InputDispatcher的过程讲到 调用InputChannel通过socket与远程进程通信,本文便展开讲解这个socket是如何建立的。

对于InputReader和InputDispatcher都是运行在system_server进程; 用户点击的界面往往可能是某一个app,而每个app一般地都运行在自己的业务进程,这里就涉及到跨进程通信,app进程是如何与system进程建立通信。

要解答这些问题,从Activity最基本的创建过程开始说起。我们都知道一般地Activity对应一个应用窗口, 每一个窗口对应一个ViewRootImpl。窗口是如何添加到Activity的,从Activity.onCreate()为起点讲解。

众所周知,Activity的生命周期的回调方法都是运行在主线程,也称之为UI线程,所有UI相关的操作都需要运行在该线程。

Activity.onCreate

1
2
3
4
5
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_account_bind);
...
}

Activity启动是由system进程控制:

  1. handleLaunchActivity():会调用Activity.onCreate(), 该方法内再调用setContentView(),经过AMS与WMS的各种交互,层层调用后,进入step2
  2. handleResumeActivity():会调用Activity.makeVisible(),该方法继续调用便会执行到WindowManagerImpl.addView(), 该方法内部再调用WindowManagerGlobal.addView(),

ViewRootImpl

我们在建立应用和WMS的联系”中,我们介绍过ViewRootImpl的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
/**
* 代码位于:frameworks/base/core/java/android/view/ViewRootImpl.java
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;

// ........

// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
// 在添加到窗口管理器之前安排第一次布局,以确保我们在从系统接收任何其他事件之前进行布局。
requestLayout();
// Window的属性能接收输入并且还没有创建InputChannle,则会新创建一个InputChannel对象
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;

if (mView instanceof RootViewSurfaceTaker) {
PendingInsetsController pendingInsetsController =
((RootViewSurfaceTaker) mView).providePendingInsetsController();
if (pendingInsetsController != null) {
pendingInsetsController.replayAndAttach(mInsetsController);
}
}

try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
controlInsetsForCompatibility(mWindowAttributes);

Rect attachedFrame = new Rect();
final float[] sizeCompatScale = { 1f };
// mWindowSession的addToDisplay()方法传递到WindowManagerService中
// 传入InputChannel
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
mTempControls, attachedFrame, sizeCompatScale);
// ......
}
}
}

setView()方法中,如果Window的属性能接收输入并且还没有创建InputChannle,则会新创建一个InputChannel对象。这个对象会通过mWindowSession的addToDisplay()方法传递到WindowManagerService中。

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
/**
* 代码位于:frameworks/base/services/core/java/com/android/server/wm/Session.java
* addToDisplayAsUser 调用WindowManagerService的addWindow
* @param window client端得到
* @param attrs
* @param viewVisibility
* @param displayId
* @param requestedVisibilities
* @param outInputChannel
* @param outInsetsState
* @param outActiveControls
* @param outAttachedFrame
* @param outSizeCompatScale
* @return
*/
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
outAttachedFrame, outSizeCompatScale);
}

这个过程我们前面已经详细介绍过了,知道这最后会调用WMS的addWindow()方法,addWindow()方法中有下面的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, InputChannel outInputChannel) {
......
if (outInputChannel ! = null && (attrs.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)
{
String name = win.makeInputChannelName();
// 调用InputChannel的openInputChannelPair来返回一个InputChannel对象的数组
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
win.setInputChannel(inputChannels[0]);
// 调用InputChannel的openInputChannelPair来返回一个InputChannel对象的数组
inputChannels[1].transferTo(outInputChannel);
mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
}
......
}

在addWindow()方法中先调用InputChannel的openInputChannelPair来返回一个InputChannel对象的数组,然后将inputChannels[0]设置到win对象中。接着将inputChannels[1]转换成和客户端传递过来的outInputChannel对象。最后调用了InputManagerService中的registerInputChannel()方法(现在我们知道registerInputChannel()方法从哪里调用的了),调用该方法的参数是win的mInputChannel,实际上就是inputChannels[0]。这儿的代码有点不好理解,我们仔细看看这些函数的实现。

先看看openInputChannelPair()方法,代码如下:

1
2
3
4
public static InputChannel[] openInputChannelPair(String name) {
...... // 参数检查
return nativeOpenInputChannelPair(name);
}

openInputChannelPair()方法调用了nativeOpenInputChannelPair()方法,其对应的本地函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(
JNIEnv* env, jclass clazz, jstring nameObj) {
const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
String8 name(nameChars);
env->ReleaseStringUTFChars(nameObj, nameChars);
sp<InputChannel> serverChannel;
sp<InputChannel> clientChannel;
status_t result = InputChannel::openInputChannelPair(name, serverChannel,
clientChannel);
......
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(serverChannel));
if (env->ExceptionCheck()) {
return NULL;
}
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(clientChannel));
if (env->ExceptionCheck()) {
return NULL;
}
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
return channelPair;
}

android_view_InputChannel_nativeOpenInputChannelPair()函数中调用openInputChannelPair()函数创建了两个InputChannel对象,serverChannel和clientChannel。然后又以它们作为参数创建了两个NativeInputChannel对象,再以它们为参数调用android_view_InputChannel_createInputChannel()函数,最后把函数的返回值放到数组channelPair中。先看看openInputChannelPair()函数做了些什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
status_t InputChannel::openInputChannelPair(const String8& name,
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
......
return result;
}
..... // 设置socket属性
String8 serverChannelName = name;
serverChannelName.append(" (server)");
// 建立一个InputChannel对象,包含了sockets[0]
outServerChannel = new InputChannel(serverChannelName, sockets[0]);
String8 clientChannelName = name;
clientChannelName.append(" (client)");
// 建立一个InputChannel对象,包含了sockets[1]
outClientChannel = new InputChannel(clientChannelName, sockets[1]);
return OK;
}

openInputChannelPair()函数首先调用socketpair()函数创建了一对socket,我们知道这是用来相互通信的。接下来函数中用这两个socket句柄作为参数创建了两个InputChannel对象。

1
2
3
4
5
6
7
8
9
10
11
static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
NativeInputChannel* nativeInputChannel) {
// 创建Java层的InputChannel对象
jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
gInputChannelClassInfo.ctor);
if (inputChannelObj) {
android_view_InputChannel_setNativeInputChannel(env, inputChannelObj,
nativeInputChannel);
}
return inputChannelObj;
}

android_view_InputChannel_createInputChannel()函数中创建了Java层的InputChannel对象,并且调用了android_view_InputChannel_setNativeInputChannel()函数,代码如下:

1
2
3
4
5
6
static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env,
jobject inputChannelObj, NativeInputChannel* nativeInputChannel) {
// 将native层的NativeInputChannel对象的指针放到Jave层InputChannel对象的mPtr中
env->SetIntField(inputChannelObj, gInputChannelClassInfo.mPtr,
reinterpret_cast<jint>(nativeInputChannel));
}

android_view_InputChannel_setNativeInputChannel()函数把native层的NativeInputChannel对象的指针放到了Jave层的InputChannel对象的mPtr成员变量中。

android_view_InputChannel_setNativeInputChannel()函数把native层的NativeInputChannel对象的指针放到了Jave层的InputChannel对象的mPtr成员变量中。

看到这里我们就明白了,WindowMangerService中调用InputChannel中的openInputChannel Pair()方法将会创建两个Java的InputChannel对象,而它们对应的native层的NativeInputChannel对象中会通过两个socket关联在一起。

前面我们看到,WMS的addWindow()方法中会调用inputChannels[1]的transferTo()方法,而且以客户端传递的outInputChannel作为参数。我们看看transferTo()方法,代码如下:

1
2
3
4
public void transferTo(InputChannel outParameter) {
......
nativeTransferTo(outParameter);
}

transferTo()又调用了nativeTransferTo()方法,它对应的本地函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
jobject otherObj) {
if (android_view_InputChannel_getNativeInputChannel(env, otherObj) ! = NULL) {
...... // 错误处理
return;
}
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, obj);
android_view_InputChannel_setNativeInputChannel(env, otherObj,
nativeInputChannel);
android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
}

这个函数的功能就是把调用对象的mPtr中的值放到参数对象的mPtr中,并将调用对象的mPtr设置为NULL。

因此,addWindow()方法中transferTo()方法执行完后,outInputChannel的mPtr指针保存的就是native层创建的InputChannel对象的指针。

Session对象的addToDisplay()方法中参数outInputChannel是一个返回参数,也就是说客户端调用完addToDisplay()之后,作为参数传递的outInputChannel将会被赋值。我们看看它的readFromParcel()方法的实现。

1
2
3
4
public void readFromParcel(Parcel in) {
......
nativeReadFromParcel(in);
}

nativeReadFromParcel()方法对应的native层的函数如下:

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
static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
jobject parcelObj) {
if (android_view_InputChannel_getNativeInputChannel(env, obj) ! = NULL) {
......
return;
}
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel) {
bool isInitialized = parcel->readInt32();
if (isInitialized) {
String8 name = parcel->readString8();
int rawFd = parcel->readFileDescriptor(); // 读出socket句柄
int dupFd = dup(rawFd); // 复制一个
if (dupFd < 0) {
return;
}
// 以socket句柄为参数创建inputChannel对象
InputChannel* inputChannel = new InputChannel(name, dupFd);
NativeInputChannel* nativeInputChannel =
new NativeInputChannel(inputChannel);
android_view_InputChannel_setNativeInputChannel(env, obj,
nativeInputChannel);
}
}
}

nativeReadFromParcel()方法中将使用从WMS中传递回来的socket句柄创建InputChannel对象。这样InputManagerService中的InputChannel对象和应用进程中的InputChannel对象通过socket就连接在了一起,如图16.4所示。

接收Input消息

理解了InputChannel之后,让我们再回到InputManagerService中,前面己经介绍了,输入消息会传递到InputChannel的sendMessage()函数中,它的代码如下:

1
2
3
4
5
6
7
8
9
status_t InputChannel::sendMessage(const InputMessage* msg) {
size_t msgLength = msg->size();
ssize_t nWrite;
do {
nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);
......
return OK;
}

sendMessage()函数调用系统的send()函数来发送输入消息,这样输入消息通过socket就传递到用户进程中。

用户进程又在哪里接收socket传递过来的消息呢?我们再看看ViewRootImpl的setView()函数,函数中调用addToDisplay()得到InputChannel对象后,接下来是创建WindowInputEventReceiver对象,如下所示:

1
2
3
4
5
6
7
8
if (mInputChannel ! = null) {
if (mInputQueueCallback ! = null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}

注意,上面的代码中创建WindowInputEventReceiver对象时使用了InputChannel对象和当前线程的Looper对象作为参数。下面是它的构造方法:

1
2
3
4
5
6
7
8
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
...... // 检查参数
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}

InputEventReceiver()调用了nativeInit()方法,同时把从Looper对象中取到的MessageQueue对象作为参数传递到底层,nativeInit()方法对应的native层的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject inputChannelObj, jobject messageQueueObj) {
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
...... // 错误处理
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env,
messageQueueObj);
...... // 错误处理
sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
receiverWeak, inputChannel, messageQueue);
status_t status = receiver->initialize();
...... // 错误处理
receiver->incStrong(gInputEventReceiverClassInfo.clazz);
return reinterpret_cast<jint>(receiver.get());
}

nativeInit()函数首先取得了Jave层的InputChannel对象和MessageQueue在native层的对应对象的指针,然后使用它们作为参数创建了NativeInputEventReceiver对象,并调用它的initialize()函数。函数代码如下:

1
2
3
4
status_t NativeInputEventReceiver::initialize() {
setFdEvents(ALOOPER_EVENT_INPUT);
return OK;
}

归纳总结

  1. InputDispatcher中会先将事件放到InboundQueue也就是“iq”队列中,然后寻找具体处理input事件的目标应用窗口,并将事件放入对应的目标窗口OutboundQueue也就是“oq”队列中等待通过SocketPair双工信道发送到应用目标窗口中。
  2. 事件发送给具体的应用目标窗口后,会将事件移动到WaitQueue也就是“wq”中等待目标应用处理事件完成,并开启倒计时,如果目标应用窗口在5S内没有处理完成此次触控事件,就会向system_server报应用ANR异常事件