参考资料
Android Pointer location功能研究
https://blog.csdn.net/zhangqi6627/article/details/108467242
Introduction Android Input of Pointer Location
https://cwgoover.github.io/2016/05/31/android-setting-pointer-location/
InputManagerService分析一:IMS的启动与事件传递
https://blog.csdn.net/lilian0118/article/details/28617185
十分钟了解Android触摸事件原理(InputManagerService)
https://www.jianshu.com/p/f05d6b05ba17
在开发者选项中,打开 Pointer Location 调试功能,可以显示PointerLocationView 来展示点击信息,这表明,系统的点击事件也发给了PointerLocationView,即可以发给不同的界面(应用),input事件的分发流程是怎样的呢?
Input事件是通过socket进行传送的,下图是传送示意图,通过数据传送的这个简图,可以让我们忽略掉繁琐的细枝末节,从整理入手去理解input数据分发。
socket
IMS <---------------> WMS
|----------------------> APP
socket
Pointer LocationView中的事件获取是WMS中获取到后再分发的,
APP中的事件流程是ims直接通过socket传送给app的,app把socket的fd传给wms,wms又传给IMS,之后IMS就可以和app进行直接通信了。
IMS发送input信息到WMS这部分从Pointer LocationView说起,
打开Pointer Location后,走到
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
private void enablePointerLocation() {
if (mPointerLocationView != null) {
return;
}
mPointerLocationView = new PointerLocationView(mContext);
mPointerLocationView.setPrintCoords(false);
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
lp.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
if (ActivityManager.isHighEndGfx()) {
lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
lp.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
}
lp.format = PixelFormat.TRANSLUCENT;
lp.setTitle("PointerLocation - display " + getDisplayId());
lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
final WindowManager wm = mContext.getSystemService(WindowManager.class);
wm.addView(mPointerLocationView, lp);
mDisplayContent.registerPointerEventListener(mPointerLocationView);
}
关键地方是
wm.addView(mPointerLocationView, lp);
mDisplayContent.registerPointerEventListener(mPointerLocationView);
添加了一个view,并且registerPointerEventListener进行了注册,wms的PointerEventDispatcher分发的时候就会把input数据传入。
private WindowManagerService(Context context, PowerManagerService pm,
DisplayManagerService displayManager, InputManagerService inputManager,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
......
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mPointerEventDispatcher = new PointerEventDispatcher(mInputManager.monitorInput(TAG));
.....
}
通过monitorInput可以看到,PointerEventDispatcher(mInputManager.monitorInput(TAG));
这一个操作大有文章,把wms和InputManagerService通过InputChannel关联起来了,他们可以进行socketpair通信了。
InputManagerService的
public InputChannel monitorInput(String inputChannelName) {
InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
nativeRegisterInputChannel(mPtr, inputChannels[0], null, true);
inputChannels[0].dispose(); // don't need to retain the Java object reference
return inputChannels[1];
}
WMS中的InputChannel收到socket信息后,会调用到PointerEventDispatcher的dispatchInputEvent方法去分发,这个过程记为1,后续再查看。
PointerEventDispatcher的定义
30public class PointerEventDispatcher extends InputEventReceiver {
31 ArrayList<PointerEventListener> mListeners = new ArrayList<PointerEventListener>();
32 PointerEventListener[] mListenersArray = new PointerEventListener[0];
33
34 public PointerEventDispatcher(InputChannel inputChannel) {
35 super(inputChannel, UiThread.getHandler().getLooper());
36 }
37
38 @Override
39 public void onInputEvent(InputEvent event, int displayId) {
40 try {
41 if (event instanceof MotionEvent
42 && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
43 final MotionEvent motionEvent = (MotionEvent) event;
44 PointerEventListener[] listeners;
45 synchronized (mListeners) {
46 if (mListenersArray == null) {
47 mListenersArray = new PointerEventListener[mListeners.size()];
48 mListeners.toArray(mListenersArray);
49 }
50 listeners = mListenersArray;
51 }
52 for (int i = 0; i < listeners.length; ++i) {
53 listeners[i].onPointerEvent(motionEvent, displayId);
54 }
55 }
56 } finally {
57 finishInputEvent(event, false);
58 }
59 }
60
61 /**
62 * Add the specified listener to the list.
63 * @param listener The listener to add.
64 */
65 public void registerInputEventListener(PointerEventListener listener) {
66 synchronized (mListeners) {
67 if (mListeners.contains(listener)) {
68 throw new IllegalStateException("registerInputEventListener: trying to register" +
69 listener + " twice.");
70 }
71 mListeners.add(listener);
72 mListenersArray = null;
73 }
74 }
184 private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
185 mSeqMap.put(event.getSequenceNumber(), seq);
186 onInputEvent(event, displayId);
187 }
而普通app的input交互处理是在创建窗口的时候创建了InputChannel对象,ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
…
mInputChannel = new InputChannel();
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
<!--创建InputChannel容器-->
mInputChannel = new InputChannel();
}
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
<!--添加窗口,并请求开辟Socket Input通信通道-->
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}...
<!--监听,开启Input信道-->
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
在IWindowSession.aidl定义中 InputChannel是out类型,也就是说需要服务端进行填充,那么接着看服务端WMS如何填充的呢?
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
...
if (outInputChannel != null && (attrs.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
String name = win.makeInputChannelName();
<!--关键点1创建通信信道 -->
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
<!--本地用-->
win.setInputChannel(inputChannels[0]);
<!--APP端用-->
inputChannels[1].transferTo(outInputChannel);
<!--注册信道与窗口-->
mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
}
通过Wms中转,送到IMS中