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的底層實作,這篇就到這了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/330359.html
標籤:其他
