一、目的
最近接觸到了一個問題:耳機插入事件的由來,走讀了下IMS輸入系統服務的原始碼。同時,IMS輸入系統服務在Android的開發過程中,也經常出現,有必要了解下相關原理。
- 學習下IMS輸入系統的原始碼設計,瞭解該模組承擔的業務職責,熟悉Android結構
- 瞭解Android螢幕點選事件、物理按鍵事件的分發規則
二、環境
- 版本:Android 11
- 平臺:高通 QCM2290
三、相關概念
3.1 輸入裝置
常見的輸入裝置有滑鼠、鍵盤、觸控式螢幕等,使用者透過輸入裝置與系統進行互動。
3.2 UEVENT機制
"uevent" 是 Linux 系統中的一種事件通知機制,用於向使用者空間傳送有關核心和裝置狀態變化的通知。這種機制通常用於裝置驅動程式、熱插拔事件以及裝置狀態變化等場景,以便使用者空間應用程式能夠在這些事件發生時做出相應的響應。
3.3 JNI
JNI,全稱Java Native Interface,是Java程式語言的一種程式設計框架,用於實現Java程式碼與其他程式語言(如C、C++)進行互動的介面。JNI允許Java程式呼叫原生程式碼(native code),即由其他程式語言編寫的程式碼,並且允許原生程式碼呼叫Java程式碼。透過JNI,Java程式可以訪問底層系統功能、使用硬體裝置、呼叫第三方庫等。
3.4 EPOLL機制
監聽多個描述符的可讀/可寫狀態。等待返回時攜帶了可讀的描述符
3.5 INotify
Linux 核心所提供的一種檔案系統變化通知機制。可以監控檔案系統的變化,如檔案新建、刪除、讀寫等
四、詳細設計
透過螢幕的觸控事件,來分析IMS系統,相關如下
4.1 結構圖
4.2 程式碼結構
層級 | 模組 | 描述 | 原始碼 | 編譯產物 |
---|---|---|---|---|
Framework | InputManagerService | xxx | frameworks/base/services/core/java/ | out/target/product/qssi/system/framework/services.jar |
Native | NativeInputManager | xxx | frameworks/base/services/core/jni/ | out/target/product/qssi/system/lib64/libandroid_servers.so |
Native | Inputflinger | xxx | frameworks/native/services/inputflinger/ | out/target/product/qssi/system/lib64/libinputflinger.so |
Native | Inputreader | xxx | frameworks/native/services/inputflinger/reader | out/target/product/qssi/system/lib64/libinputreader.so |
Native | Inputdispatcher | xxx | frameworks/native/services/inputflinger/dispatcher/ | (靜態庫)out/soong/.intermediates/frameworks/native/services/inputflinger/dispatcher/libinputdispatcher/android_arm64_armv8-a_static/libinputdispatcher.a |
Native | NativeInputEventReceiver | xxx | frameworks/base/core/jni/ | out/target/product/qssi/system/lib64/libandroid_runtime |
Native | InputChannel | xxx | frameworks/native/libs/input/ | out/target/product/qssi/system/lib64/libinput.so |
4.3 InputManagerService模組
InputManagerService是Android框架層一個非核心服務,主要是提供一個IMS輸入系統啟動的入口,同時對應用層提供業務相關介面。
4.3.1 IMS服務入口
Android裝置開機後,會啟動system_server程式,InputManagerService服務(以下簡稱IMS)在該程式被喚起。
@frameworks\base\services\java\com\android\server\SystemServer.java
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
...
t.traceBegin("StartInputManagerService");
inputManager = new InputManagerService(context);//新建IMS例項
t.traceEnd();
...
t.traceBegin("StartInputManager");
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());//設定窗體事件監聽
inputManager.start();//啟動IMS服務
t.traceEnd();
...
}
4.3.2 IMS初始化
此處做一些IMS相關的初始化操作,會呼叫nativeInit方法,獲取一個NativeInputManager物件,類似於一個控制程式碼。
@frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
public InputManagerService(Context context) {
...
mStaticAssociations = loadStaticInputPortAssociations();
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
...
}
4.3.3 IMS啟動
InputManagerService透過start方法啟動,會呼叫nativeStart方法,該方法為Native方法
@frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
private static native void nativeStart(long ptr);
public void start() {
Slog.i(TAG, "Starting input manager");
nativeStart(mPtr);
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
...
}
4.3.4 IMS訊息監聽
該方法為Native的回撥方法,用於上報IMS事件,如耳機插入事件等。
@frameworks\base\services\core\java\com\android\server\input\InputManagerService.java
// Native callback.
private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
...
if ((switchMask & SW_LID_BIT) != 0) {
final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0);
mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
}
if ((switchMask & SW_CAMERA_LENS_COVER_BIT) != 0) {
final boolean lensCovered = ((switchValues & SW_CAMERA_LENS_COVER_BIT) != 0);
mWindowManagerCallbacks.notifyCameraLensCoverSwitchChanged(whenNanos, lensCovered);
}
if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
switchMask);
}
...
}
4.4 NativeInputManager模組
該模組為JNI模組,主要處理Java方法與c++方法對映關係,即IMS服務與InputFlinger模組的通訊橋樑。
4.4.1 nativeInit初始化
(1)新建一個NativeInputManager物件,並將該物件返回給java層
@\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
...
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}
(2)建立InputManager管理類,主要用於管理Input事件分發、事件讀取行為
@\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();
...
mInputManager = new InputManager(this, this);
defaultServiceManager()->addService(String16("inputflinger"),
mInputManager, false);
}
4.4.2 nativeStart啟動
獲取上一個階段建立NativeInputManager物件,並引用start啟動該模組
@\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_t result = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
4.5 Inputflinger模組
input事件的管理類,資料傳遞類,也是輸入系統native層核心的模組。
ps: 根據字典裡的定義,flinger是指出軌的人。在SurfaceFlinger的例子中,它把可視資料扔給surface AudioFlinger把音訊資料扔給適當的接收者。它們只是“可愛”的詞… ?
4.5.1 啟動事件管理服務
啟動兩個核心的阻塞執行緒,一個是事件分發執行緒,一個是事件讀取執行緒。
@frameworks\native\services\inputflinger\InputManager.cpp
status_t InputManager::start() {
status_t result = mDispatcher->start();//啟動事件分發服務
if (result) {
ALOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
result = mReader->start();//啟動事件讀取服務
if (result) {
ALOGE("Could not start InputReader due to error %d.", result);
mDispatcher->stop();
return result;
}
return OK;
}
4.6 Inputreader模組
事件讀取服務,讀取驅動上報事件
4.6.1 啟動InputReader執行緒
(1)建立一個InputThread執行緒
@frameworks\native\services\inputflinger\reader\InputReader.cpp
status_t InputReader::start() {
if (mThread) {
return ALREADY_EXISTS;
}
mThread = std::make_unique<InputThread>(
"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
return OK;
}
(2)InputThread執行緒的loop迴圈佇列(執行緒和loop的關係)
@frameworks\native\services\inputflinger\reader\InputReader.cpp
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
...
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);//step 1. 透過EventHub抽取事件列表
{ // acquire lock
...
if (count) {
processEventsLocked(mEventBuffer, count);// step 2. 對事件進行加工處理
}
...
} // release lock
...
mQueuedListener->flush();//step 3. 事件釋出
}
4.6.2 EventHub獲取事件佇列
EventHub:事件集線器,它將全部的輸入事件透過一個介面getEvents(),將從多個輸入裝置節點中讀取的事件交給InputReader,是輸入系統最底層的一個元件。
(1)EventHub的建構函式
它透過INotify與Epoll機制建立起了對裝置節點增刪事件以及可讀狀態的監聽。同時,EventHub建立了一個名為wakeFds的匿名管道,因為InputReader在執行getEvents()時會因無事件而導致其執行緒堵塞在epoll_wait()的呼叫裡,然而有時希望能夠立馬喚醒InputReader執行緒使其處理一些請求。
@frameworks\native\services\inputflinger\reader\EventHub.cpp
static const char* DEVICE_PATH = "/dev/input";
EventHub::EventHub(void)
: mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
mNextDeviceId(1),
mControllerNumbers(),
mOpeningDevices(nullptr),
mClosingDevices(nullptr),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false),
mNeedToScanDevices(true),
mPendingEventCount(0),
mPendingEventIndex(0),
mPendingINotify(false) {
ensureProcessCanBlockSuspend();
mEpollFd = epoll_create1(EPOLL_CLOEXEC);//建立一個epoll物件,用來監聽裝置節點是否有事件
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
mINotifyFd = inotify_init();//建立一個inotify物件,用來監聽裝置節點的增刪事件
mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
...
struct epoll_event eventItem = {};
eventItem.events = EPOLLIN | EPOLLWAKEUP;
eventItem.data.fd = mINotifyFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);//將mINotifyFd註冊進epoll物件中
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
int wakeFds[2];
result = pipe(wakeFds);//建立一個匿名管道,用於喚醒EventHub,避免無事件引起阻塞
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
...
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);//將管道讀取端加入epoll物件中
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
mEpollFd監聽如下幾個事件:裝置節點的增加、刪除、修改;匿名管道,避免無事件阻塞
(2)RawEvent結構體
mEventBuffer用於描述原始輸入事件,其型別為RawEvent,相關結構體如下:
@frameworks\native\services\inputflinger\reader\include\EventHub.h
/*
* A raw event as retrieved from the EventHub.
*/
struct RawEvent {
nsecs_t when;//事件時間戳
int32_t deviceId;//產生事件的裝置ID
int32_t type;//事件型別
int32_t code;//事件編碼
int32_t value;//事件值
};
(3)EventHub->getEvents事件,
getEvents()是事件處理的核心方法,其透過EPOLL機制和INOTIFY,從多個裝置節點讀取事件。
@frameworks\native\services\inputflinger\reader\EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
...
for (;;) {
...
if (mNeedToScanDevices) {//Step 1.掃描裝置
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
...
// Grab the next input event.
bool deviceChanged = false;
while (mPendingEventIndex < mPendingEventCount) { //Step 2.處理未被InputReader取走的輸入事件與裝置事件
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
...
// This must be an input event
if (eventItem.events & EPOLLIN) {
int32_t readSize =
read(device->fd, readBuffer, sizeof(struct input_event) * capacity);//Step 3.讀取底層上報事件
if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
...
} else {
int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
size_t count = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {//構建需要上報的事件
struct input_event& iev = readBuffer[i];
event->when = processEventTimestamp(iev);
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;
event += 1;//將event指標移動到下一個可用於填充事件的RawEvent物件
capacity -= 1;
}
...
}
}
...
}
...
mLock.unlock(); // release lock before poll
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);//Step 4.阻塞,等待事件各種型別訊息
mLock.lock(); // reacquire lock after poll
...
}
// All done, return the number of events we read.
return event - buffer;
}
Step 1. 掃描裝置,會獲取input/dev/下的所有裝置,並將各個裝置註冊到epoll執行緒池裡,監聽各個裝置的訊息狀態;
Step 2. 處理未被InputReader取走的輸入事件與裝置事件,一般情況下有事件上報時,epoll_wait會讀取到mPendingEventItems值,即mPendingEventCount值,即會進入該流程;
Step 3. 讀取底層上報事件,根據上報的fd裝置,讀取對應的裝置節點。即可以獲取到上報的事件內容。如下為螢幕點選對應的上報事件:
Step 4. 透過epoll機制,阻塞當前程式,等待裝置節點變更,事件上報。
4.6.3 Input事件加工
主要是將底層RawEvent事件,進一步加工,將Event事件注入到mArgsQueue佇列的過程。
(1)Input事件加工
@frameworks\native\services\inputflinger\reader\InputReader.cpp
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
for (const RawEvent* rawEvent = rawEvents; count;) {
int32_t type = rawEvent->type;
size_t batchSize = 1;
if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
...
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);//輸入事件
} else {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED://裝置增加
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED://裝置移除
removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN://裝置掃描結束
handleConfigurationChangedLocked(rawEvent->when);
break;
default:
ALOG_ASSERT(false); // can't happen
break;
}
}
count -= batchSize;
rawEvent += batchSize;
}
}
(2)Input事件推送
該流程業務程式碼比較冗長,做了層層封裝,如下為方法呼叫棧:
InputReader.processEventsLocked() -> InputReader.processEventsForDeviceLocked() -> InputDevice.process() -> MultiTouchInputMapper.process() -> TouchInputMapper.process()->TouchInputMapper.sync() -> TouchInputMapper.processRawTouches() -> TouchInputMapper.cookAndDispatch() -> TouchInputMapper.dispatchTouches() -> TouchInputMapper.dispatchMotion() -> QueuedInputListener -> notifyMotion()
最終可以看到事件最終會傳遞到mArgsQueue容器內。
@frameworks\native\services\inputflinger\InputListener.cpp
std::vector<NotifyArgs*> mArgsQueue;
void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
traceEvent(__func__, args->id);
mArgsQueue.push_back(new NotifyMotionArgs(*args));
}
4.6.4 事件釋出
(1)當事件加工完成後,會引用flush()方法,將事件釋出出去
@frameworks\native\services\inputflinger\InputListener.cpp
void QueuedInputListener::flush() {
size_t count = mArgsQueue.size();
for (size_t i = 0; i < count; i++) {
NotifyArgs* args = mArgsQueue[i];
args->notify(mInnerListener);//事件釋出
delete args;
}
mArgsQueue.clear();
}
(2)由上一節可知,螢幕點選事件對應的args為NotifyMotionArgs
@frameworks\native\services\inputflinger\InputListener.cpp
void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
listener->notifyMotion(this);
}
(3)大家可以自己去追溯下原始碼,該listener介面的實現類是InputDispatcher。至此,事件將進入下一階段——事件分發。
@frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
...
}
4.7 Inputdispatcher模組
事件分發服務,將底層讀到的事件,分發到上層
4.7.1 Input事件上報
至此,我們知道InputDispatch會啟動一個阻塞執行緒,等待底層事件上報;而透過InputReader的分析,我們知道底層事件響應,最終會通知InputDispatch模組的notifyMotion()方法
@frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
...
{ // acquire lock
mLock.lock();
...
// Just enqueue a new motion event.
MotionEntry* newEntry =
new MotionEntry(args->id, args->eventTime, args->deviceId, args->source,
args->displayId, policyFlags, args->action, args->actionButton,
args->flags, args->metaState, args->buttonState,
args->classification, args->edgeFlags, args->xPrecision,
args->yPrecision, args->xCursorPosition, args->yCursorPosition,
args->downTime, args->pointerCount, args->pointerProperties,
args->pointerCoords, 0, 0);
needWake = enqueueInboundEventLocked(newEntry);//構建新的MotionEvent事件
mLock.unlock();
} // release lock
if (needWake) {
mLooper->wake();//喚醒InputDispatch執行緒,進行分發
}
}
4.7.2 啟動InputDispatcher執行緒
(1)建立一個InputDispatcher執行緒
@frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
status_t InputDispatcher::start() {
if (mThread) {
return ALREADY_EXISTS;
}
mThread = std::make_unique<InputThread>(
"InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
return OK;
}
(2)InputThread執行緒的loop佇列
@frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);//事件分發
}
...
} // release lock
// Wait for callback or timeout or wake. (make sure we round up, not down)
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);//堵塞,等待喚醒
}
(3)事件分發過程
事件的分發過程也比較冗長,此處不具體分析過程,其業務堆疊如下,即事件分發最終會下發到publishMotionEvent。
InputDispatcher.dispatchOnceInnerLocked() -> InputDispatcher.dispatchMotionLocked() -> InputDispatcher.dispatchEventLocked() -> InputDispatcher.prepareDispatchCycleLocked() -> InputDispatcher.enqueueDispatchEntriesLocked() -> InputDispatcher.startDispatchCycleLocked() -> InputPublisher.publishMotionEvent()
@frameworks\native\libs\input\InputTransport.cpp
status_t InputPublisher::publishMotionEvent(
uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,
std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
int32_t edgeFlags, int32_t metaState, int32_t buttonState,
MotionClassification classification, float xScale, float yScale, float xOffset,
float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
float yCursorPosition, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) {
...
InputMessage msg;
msg.header.type = InputMessage::Type::MOTION;
msg.body.motion.seq = seq;
msg.body.motion.eventId = eventId;
...
return mChannel->sendMessage(&msg);
}
4.8 WindowManagerService模組
4.8.1 ViewRootImpl階段
InputDispatcher透過InputChannel將事件傳送到目標視窗的程式了。那麼目標視窗是如何接收傳遞事件呢?
(1)Activity建立視窗相關階段介紹
attach階段:
一個Activity 建立了一個PhoneWindow物件 ,PhoneWindow透過setWindowManager() 建立了WindowManagerImpl 。
即Activity 對應一個PhoneWindow,並得到了一個WindowManager(WindowManagerImpl,Window建立的)。
onCreate階段:
建立了DecorView ,並將 activity的佈局新增到DecorView中 。
onResume階段:
建立了ViewRootImpl,透過setView()最終由Session進入system_server程式。最終執行addWindow新增視窗到WMS。
(2)ViewRootImpl.setView()
@frameworks\base\core\java\android\view\ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
...
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();//建立inputChannel物件
}
try {
...
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);//透過session跨程式呼叫WMS的addWindow方法給inputChannel賦值
setFrame(mTmpFrame);
}
...
if (inputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());//建立mInputEventReceiver物件,用於App側接收Input事件
}
...
}
}
}
4.8.2 WindowManagerService.addWindow()
(1)openInputChannel():生成一對inputChannel,並返回一個物件給App端。
Session.addToDisplayAsUser() -> WindowManagerService.addWindow() -> EmbeddedWindow.openInputChannel()
@frameworks\base\services\core\java\com\android\server\wm\EmbeddedWindowController.java
InputChannel openInputChannel() {
final String name = getName();
final InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//InputChannel底層透過一對socket進行通訊
mInputChannel = inputChannels[0];
final InputChannel clientChannel = inputChannels[1];
mWmService.mInputManager.registerInputChannel(mInputChannel);//將一個inputChannel物件註冊到Input的Native端
...
return clientChannel;//返回一個inputChannel物件給App端
}
(2)openInputChannelPair():建立一對透過socket通訊的inputChannel物件。
InputChannel.openInputChannelPair() -> InputChannel.nativeOpenInputChannelPair() -> android_view_InputChannel.android_view_InputChannel_nativeOpenInputChannelPair() -> InputTransport.openInputChannelPair()
@frameworks\native\libs\input\InputTransport.cpp
status_t InputChannel::openInputChannelPair(const std::string& name,
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
status_t result = -errno;
ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
name.c_str(), errno);
outServerChannel.clear();
outClientChannel.clear();
return result;
}
int bufferSize = SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
sp<IBinder> token = new BBinder();
std::string serverChannelName = name + " (server)";
android::base::unique_fd serverFd(sockets[0]);
outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);//server端
std::string clientChannelName = name + " (client)";
android::base::unique_fd clientFd(sockets[1]);
outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);//client端
return OK;
}
4.8.3 WindowInputEventReceiver
app程式和system_server程式透過socket通訊,底層捕獲的事件最終透過inputChannel模組來實現,再由app端的WindowInputEventReceiver去接收,最後把事件分發到目標View上。
(1)WindowInputEventReceiver建構函式
註冊一個事件接收器,WindowInputEventReceiver的父類是InputEventReceiver
@frameworks\base\core\jni\android_view_InputEventReceiver.cpp
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
...
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);//初始化操作
mCloseGuard.open("dispose");
}
// Called from native code.
@SuppressWarnings("unused")
@UnsupportedAppUsage
private void dispatchInputEvent(int seq, InputEvent event) {//native層事件回撥方法
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);//事件分發到各個目標View上
}
(2)nativeInit
由上可知,在新增視窗時,WMS會針對於每個視窗設定一對InputChannel物件,分為client端和server端,其中server端在system_server程式,client端在app程式。我們需要去監聽client端,以期能夠捕獲server端的事件訊息。
@frameworks\base\core\jni\android_view_InputEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject inputChannelObj, jobject messageQueueObj) {
...
sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
receiverWeak, inputChannel, messageQueue);
status_t status = receiver->initialize();//初始化
...
receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
return reinterpret_cast<jlong>(receiver.get());
}
status_t NativeInputEventReceiver::initialize() {
setFdEvents(ALOOPER_EVENT_INPUT);
return OK;
}
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
int fd = mInputConsumer.getChannel()->getFd();//此fd為WMS建立的InputChannel的client端
if (events) {
mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);//註冊監聽
} else {
mMessageQueue->getLooper()->removeFd(fd);//移除監聽
}
}
}
(3)handleEvent
當server端寫入事件時,client端的looper就能被喚醒,會呼叫handleEvent函式(當fd可讀時,會呼叫LooperCallback的handleEvent,而NativeInputEventReceiver繼承自LooperCallback,所以這裡會呼叫NativeInputEventReceiver的handleEvent函式,執行緒和looper的關係此處不展開)
@frameworks\base\core\jni\android_view_InputEventReceiver.cpp
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
...
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);//處理事件
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? 1 : 0;
}
...
return 1;
}
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
...
for (;;) {
...
if (!skipCallbacks) {
...
if (inputEventObj) {
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);//事件訊息回撥java層
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching input event.");
skipCallbacks = true;
}
env->DeleteLocalRef(inputEventObj);
}
}
...
}
}
五、Input裝置節點介紹
5.1 常見觸控事件型別
事件型別 | 事件名稱 | 事件編碼 | 事件定義 |
---|---|---|---|
EV_SYN | 同步事件 | 0004 or 0005 | 代表一個事件開始(不必要) |
EV_SYN | 同步事件 | SYN_REPORT | 代表一個事件結束(必要的) |
EV_ABS | 絕對座標的事件 | ABS_MT_SLOT | 本質代表著不同的手指,他的value代表手指id |
EV_ABS | 絕對座標的事件 | ABS_MT_TRACKING_ID | 類協議特有的,每個slot會和一個ID相對應,一個非負數表示一次接觸,ffffffff表示一次接觸結束,即手指抬起。無論在接觸的型別相對應的slot發生改變,驅動都應該透過改變這個值來使這個slot失效,並且下一次觸控的ID值會是這次的值加1 |
EV_ABS | 絕對座標的事件 | ABS_MT_POSITION_X | 相對於螢幕中心的x座標 |
EV_ABS | 絕對座標的事件 | ABS_MT_POSITION_Y | 相對於螢幕中心的y座標 |
EV_ABS | 絕對座標的事件 | ABS_MT_TOUCH_MAJOR | 接觸部分的長軸長度,相當於橢圓的長軸 |
EV_ABS | 絕對座標的事件 | ABS_MT_TOUCH_MINOR | 接觸部分的短軸長度,相當於橢圓的短軸 |
EV_ABS | 絕對座標的事件 | ABS_MT_PRESSURE | 代表按下壓力,有的裝置不一定有 |
EV_KEY | 按鍵事件 | BTN_TOUCH | 觸碰按鍵,其值是DOWN或者UP |
EV_KEY | 按鍵事件 | BTN_TOOL_FINGER | 按鍵的是finger,其值是DOWN或者UP |
5.2 getevent
adb shell getevent -lt
5.3 sendevent
模擬按壓音量鍵+
//透過getevent指令,獲取音量按鍵+的事件碼
bengal:/ # getevent
add device 1: /dev/input/event4
name: "bengal-scubaidp-snd-card Button Jack"
add device 2: /dev/input/event3
name: "bengal-scubaidp-snd-card Headset Jack"
add device 3: /dev/input/event0
name: "qpnp_pon"
add device 4: /dev/input/event1
name: "gpio-keys"
add device 5: /dev/input/event2
name: "sitronix_ts_i2c"
/dev/input/event1: 0001 0073 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0001 0073 00000000
/dev/input/event1: 0000 0000 00000000
//透過sendevent模擬音量鍵+的事件
130|bengal:/ # sendevent /dev/input/event1 1 115 1
bengal:/ # sendevent /dev/input/event1 0 0 0
bengal:/ # sendevent /dev/input/event1 1 115 0
bengal:/ # sendevent /dev/input/event1 0 0 0
bengal:/ #
ps:getevent獲取到的事件碼為16進位制,sendevent輸入的值為10進位制,需要注意下!!!
六、參考資料
https://liuwangshu.blog.csdn.net/article/details/84883156
https://liuwangshu.blog.csdn.net/article/details/86771746
https://www.cnblogs.com/brucemengbm/p/7072395.html
事件分發介紹:
https://www.cnblogs.com/fanglongxiang/p/14091511.html
InputChannel介紹:
https://blog.csdn.net/ztisen/article/details/130188132
GetEvent指令介紹:
https://blog.csdn.net/Gary1_Liu/article/details/124675608