? ? ? ? InputManagerService管理着屏幕点击以及硬件按钮事件的输入输出,InputManagerService的实现是在native代码中,想要对事件进行处理,那就一定要通过InputManagerService进行注册或是监听。如果A应用要想获取到输入事件,那要怎么和InputManagerService连接起来呢?答案是Socket,其中InputChannel就是对其进行封装,InputChannel的实现同样是native代码,实现的类是NativeInputChannel,类路径:frameworks/base/core/jni/android_view_InputChannel.cpp,这里就直接看下InputChannel的openInputChannelPair():
/**
* 创建一个新的输入通道对。 一个通道提供给输入调度程序,另一个通道提供给应用程序的输入列。
* @param name通道对的描述性(非唯一)名称。
* @return 一对输入通道。 第一个通道被指定为服务器通道,应该用于发布输入事件。 第二个通道被指
* 定为客户端通道,用于使用输入事件。
*/
public static InputChannel[] openInputChannelPair(String name) {
if (name == null) {
throw new IllegalArgumentException("name must not be null");
}
if (DEBUG) {
Slog.d(TAG, "Opening input channel pair '" + name + "'");
}
return nativeOpenInputChannelPair(name);
}
private static native InputChannel[] nativeOpenInputChannelPair(String name);
openInputChannelPair()会创建一对输入通道,一端用于服务器通道,也就是InputManagerService;一端用于客户端,也就是应用这一端。对InputChannel有个初步的了解后,接下来就看主角com.android.server.input.InputManagerService:
public class InputManagerService extends IInputManager.Stub
implements Watchdog.Monitor {
// Pointer to native input manager service object.
private final long mPtr;
//所有输入事件在分发前,会优先派发到这个回调中处理
private WindowManagerCallbacks mWindowManagerCallbacks;
//内部会初始化两个线程,一个用于读取底层的输入事件,一个用于将事件派发到应用层
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
//前面说InputChannel时会创建一对输入通过对,这里就是将其中的一个注册到底层,也就是所说的服务端
//InputChannel只会收到注册页面的输入事件
private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
int displayId);
//解注册页面注册的输入事件
private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
//这个方法是添加监听,InputChannel会收到所有的输入事件,
//其中isGestureMonitor表示收到的事件是否是手势,系统的全局手势就是通过这个方法注册的
private static native void nativeRegisterInputMonitor(long ptr, InputChannel inputChannel,
int displayId, boolean isGestureMonitor);
//注入模拟屏幕的点击事件,比如实现全局的返回按钮
private static native int nativeInjectInputEvent(long ptr, InputEvent event,
int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
int policyFlags);
public InputManagerService(Context context) {
... ...
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
... ...
}
//这个回调是在SystemServer中设置的,其实现是InputManagerCallback,最终会调用到PhoneWindowManager
public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
mWindowManagerCallbacks = callbacks;
}
/**
* Creates an input channel that will receive all input from the input dispatcher.
* @param inputChannelName The input channel name.
* @param displayId Target display id.
* @return The input channel.
*/
public InputChannel monitorInput(String inputChannelName, int displayId) {
if (inputChannelName == null) {
throw new IllegalArgumentException("inputChannelName must not be null.");
}
if (displayId < Display.DEFAULT_DISPLAY) {
throw new IllegalArgumentException("displayId must >= 0.");
}
InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
// Give the output channel a token just for identity purposes.
inputChannels[0].setToken(new Binder());
nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, false /*isGestureMonitor*/);
inputChannels[0].dispose(); // don't need to retain the Java object reference
return inputChannels[1];
}
/**
* Creates an input monitor that will receive pointer events for the purposes of system-wide
* gesture interpretation.
*
* @param inputChannelName The input channel name.
* @param displayId Target display id.
* @return The input channel.
*/
@Override // Binder call
public InputMonitor monitorGestureInput(String inputChannelName, int displayId) {
if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
"monitorInputRegion()")) {
throw new SecurityException("Requires MONITOR_INPUT permission");
}
Objects.requireNonNull(inputChannelName, "inputChannelName must not be null.");
if (displayId < Display.DEFAULT_DISPLAY) {
throw new IllegalArgumentException("displayId must >= 0.");
}
final long ident = Binder.clearCallingIdentity();
try {
InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
InputMonitorHost host = new InputMonitorHost(inputChannels[0]);
inputChannels[0].setToken(host.asBinder());
nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId,
true /*isGestureMonitor*/);
return new InputMonitor(inputChannelName, inputChannels[1], host);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
/**
* Registers an input channel so that it can be used as an input event target.
* @param inputChannel The input channel to register.
* @param inputWindowHandle The handle of the input window associated with the
* input channel, or null if none.
*/
public void registerInputChannel(InputChannel inputChannel, IBinder token) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null.");
}
if (token == null) {
token = new Binder();
}
inputChannel.setToken(token);
nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);
}
/**
* Unregisters an input channel.
* @param inputChannel The input channel to unregister.
*/
public void unregisterInputChannel(InputChannel inputChannel) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null.");
}
nativeUnregisterInputChannel(mPtr, inputChannel);
}
@Override // Binder call
public boolean injectInputEvent(InputEvent event, int mode) {
return injectInputEventInternal(event, mode);
}
private boolean injectInputEventInternal(InputEvent event, int mode) {
... ...
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
final int result;
try {
result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
} finally {
Binder.restoreCallingIdentity(ident);
}
... ...
}
// Native callback.
// 有输入事件时,这里会最先调用到
private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
}
// Native callback.
// 如果interceptKeyBeforeQueueing没有处理,在事件分发给应用端前会调用到
private long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
}
}
这里先把InputManagerService的底层实现看成一个沙盒,下一篇文章再聊,这里先来聊聊上面列出的这些方法,类中有一个类型是WindowManagerCallbacks的mWindowManagerCallbacks成员变量,其实现类是InputManagerCallback,但最终调用到的是PhoneWindowManager,事件在分发给应用前,会分别调用interceptKeyBeforeQueueing()和interceptKeyBeforeDispatching(),这也就是说PhoneWindowManager是最先处理输入事件的地方。
? ? ? ? 这里先说个小插曲,interceptKeyBeforeQueueing()这个方法为什么是这样命名的,最开始看到这个名字还是挺疑惑的,后面看了底层代码才明白,InputManagerService的底层实现开启了两个线程,一个用于读输入事件(读线程),一个用于将读取的到事件分发(分发线程),读线程如何将事件传递到分发线程呢,这里就会涉及到队列(Queue),所以现在看到这个interceptKeyBeforeQueueing()是不是就很明白了。
对于上面其他的方法,先来看下方法的命名,monitorXXX()、registerXXX()、unregisterXXX()、injectXXX(),可以分为三类:
1、带有monitorXXX()的方法,表示只要有输入事件就会接收到;
2、injectXXX()就是模拟按键发送事件了;
3、registerXXX()和unRegisterXXX()这是成对出现的,这对方法有什么作用呢?一个页面要想接收到输入事件,那就必须调用registerXXX()进行对接收事件的注册,页面销毁了就调用解注册;
下面就来看下android源码中对这些方法的使用:
1、frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java,这是对系统手势处理的类,来看下它内部的使用:
private void updateIsEnabled() {
... ...
// Register input event receiver
mInputMonitor = InputManager.getInstance().monitorGestureInput("edge-swipe", mDisplayId);
mInputEventReceiver = new InputChannelCompat.InputEventReceiver(mInputMonitor.getInputChannel(),
Looper.getMainLooper(),Choreographer.getInstance(), this::onInputEvent);
... ...
}
调用monitor监听后,当有输入事件输入,就会调用到这里的onInputEvent()方法。
2、对于injectInputEvent()方法,在InputManager中有@hide标识,也就是只能在系统中使用,那现在要怎么才能使用呢?在上一篇文章Android10 AppComponentFactory源码梳理有提到android.app.Instrumentation这个类,来看下它是怎么使用的:
//需要传入的是KeyEvent中Keycode的常量,比如:KeyEvent.KEYCODE_BACK就是执行返回按键的功能
public void sendKeyDownUpSync(int key) {
sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key));
sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key));
}
public void sendKeySync(KeyEvent event) {
validateNotAppThread();
long downTime = event.getDownTime();
long eventTime = event.getEventTime();
int source = event.getSource();
if (source == InputDevice.SOURCE_UNKNOWN) {
source = InputDevice.SOURCE_KEYBOARD;
}
if (eventTime == 0) {
eventTime = SystemClock.uptimeMillis();
}
if (downTime == 0) {
downTime = eventTime;
}
KeyEvent newEvent = new KeyEvent(event);
newEvent.setTime(downTime, eventTime);
newEvent.setSource(source);
newEvent.setFlags(event.getFlags() | KeyEvent.FLAG_FROM_SYSTEM);
InputManager.getInstance().injectInputEvent(newEvent,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
所以,要想实现按键功能就可以通过上面的方法。
3、下面重点来看下registerXX(),这个对于每个显示的页面都会调用到,只是在frameworks层调用了,对于应用层来说无感而已,应用层的所有的处理流程都是从ViewRootImpl开始的,当界面显示时,会调用ViewRootImpl.setView():
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
... ...
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
... ...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
... ...
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
... ...
}
先是new了一个InputChannel,但是并没有去创建通道对,也就是说这个InputChannel还没有初始化,这里的mWindowSession是通过WindowManagerService.openSession()返回,其实现是Session类,调用它的addToDisplay()最终调用到的是WindowManagerService.addWindow():
WindowManagerService
public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState) {
... ...
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
... ...
win.openInputChannel(outInputChannel);
... ...
}
WindowState
void openInputChannel(InputChannel outInputChannel) {
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
String name = getName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
mInputWindowHandle.token = mClient.asBinder();
if (outInputChannel != null) {
//初始化应用端的InputChannel,实际就是初始化mPtr,这个变量通过native代码可以转化成指针
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
mClientChannel = null;
} else {
// If the window died visible, we setup a dummy input channel, so that taps
// can still detected by input monitor channel, and we can relaunch the app.
// Create dummy event receiver that simply reports all events as handled.
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
}
mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
}
这里通过InputChannel创建了一对通道对inputChannels[0]和inputChannels[1],然后将inputChannels[1]和应用端的InputChannel关联起来,将inputChannels[0]和服务端(事件原始分发)关联起来,这样就将原始输入事件和应用界面关联起来了,再回到ViewRootImpl.setView(),之后还创建了一个WindowInputEventReceiver对象,这一看就是接受输入事件的了,WindowInputEventReceiver基础自InputEventReceiver,这里主要来看下InputEventReceiver:
public abstract class InputEventReceiver {
private long mReceiverPtr;
// We keep references to the input channel and message queue objects here so that
// they are not GC'd while the native peer of the receiver is using them.
private InputChannel mInputChannel;
private MessageQueue mMessageQueue;
private final SparseIntArray mSeqMap = new SparseIntArray();
private static native long nativeInit(WeakReference<InputEventReceiver> receiver,
InputChannel inputChannel, MessageQueue messageQueue);
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
... ...
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
}
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
@UnsupportedAppUsage
public void onInputEvent(InputEvent event) {
finishInputEvent(event, false);
}
public final void finishInputEvent(InputEvent event, boolean handled) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to finish an input event but the input event "
+ "receiver has already been disposed.");
} else {
int index = mSeqMap.indexOfKey(event.getSequenceNumber());
if (index < 0) {
Log.w(TAG, "Attempted to finish an input event that is not in progress.");
} else {
int seq = mSeqMap.valueAt(index);
mSeqMap.removeAt(index);
nativeFinishInputEvent(mReceiverPtr, seq, handled);
}
}
event.recycleIfNeededAfterDispatch();
}
}
主要的实现也是native代码,这里就不下看了,感兴趣的可以自行查看c++的实现类NativeInputEventReceiver,c++层代码处理完成后会调用这里的dispatchInputEvent()方法,转而调用onInputEvent(),在往回看下WindowInputEventReceiver的实现:
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
List<InputEvent> processedEvents;
try {
processedEvents =
mInputCompatProcessor.processInputEventForCompatibility(event);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (processedEvents != null) {
if (processedEvents.isEmpty()) {
// InputEvent consumed by mInputCompatProcessor
finishInputEvent(event, true);
} else {
for (int i = 0; i < processedEvents.size(); i++) {
enqueueInputEvent(
processedEvents.get(i), this,
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
}
}
} else {
//正常是会执行到这里
enqueueInputEvent(event, this, 0, true);
}
}
}
这样一个流程下来,就通过InputChannel把事件传递到了应用界面,接下去就是通过enqueueInputEvent()传递给view进行处理了,在事件处理完成后,会调用到InputEventReceiver.finishInputEvent(),通知服务端事件处理完成,这样一次事件的分发就算是完成了,这里就不在往下看了。
这里在总结下:
? ? ? ? InputManagerService可以理解为通往底层输入事件的一个大门,要想获得事件的处理,可以通过以下几种方式:
? ? ? ? 1、向InputManagerService中设置回调,在SystemServer中有设置,最终在PhoneWindowManager中实现事件处理;
? ? ? ? 2、通过InputManagerService发送模拟按键事件,比如返回键,可以通过Instrumentation;
? ? ? ? 3、通过InputManagerService注册或者添加监听,注册一般用于普通应用,每个启动的页面都有注册,添加监听一般用于系统应用,比如系统手势,就是在SystemUi中实现的;
下一篇文章再聊聊InputWindowManagerService的底层实现,这篇就到这了。
|