輸入事件系統架構
Android事件輸入系統主要負責鍵盤、觸屏、滑鼠等輸入裝置的事件輸入及向焦點視窗和焦點視圖的事件派發,插入,過濾,攔截等功能。Android支援的輸入裝置主要有:鍵盤、滑鼠、觸摸屏、軌迹球、遊戲搖杆/搖桿、繪圖闆等。
Android系統中輸入系統主要包括如下幾部分:View,InputDispatcher,InputReader,EventHub ,kernel幾部分,他們之間的關系如下:
與其他子產品的互動關系如下:
Input系統啟動流程
Input在SystemServer中的startOtherServices方法中啟動,在啟動過程中主要過程如下:
主要代碼如下:
startOtherServices(){
inputManager = new InputManagerService(context);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
......
}
建立InputMangerService對象
public InputManagerService(Context context) {
this.mContext = context;
//建立一個負責處理DisplayThread線程中的Message的Handler
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
//用mPtr儲存native層的InputManagerService
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
String doubleTouchGestureEnablePath = context.getResources().getString(
R.string.config_doubleTouchGestureEnableFile);
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
//初始化完成後将Service添加到LocalServices,通過Map以鍵值對的形式存儲
LocalServices.addService(InputManagerInternal.class, new LocalService());
}
初始化native層的InputManagerService
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();
//将Java層的Context和InputManagerService轉換為native層的Context和InputManagerService存儲在mContextObj和mServiceObj中
mContextObj = env->NewGlobalRef(contextObj);
mServiceObj = env->NewGlobalRef(serviceObj);
//初始化變量
{
AutoMutex _l(mLock);
mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
mLocked.pointerCapture = false;
}
mInteractive = true;
//建立EventHub
sp<EventHub> eventHub = new EventHub();
//建立InputManager
mInputManager = new InputManager(eventHub, this, this);
}
EventHub的建立(見2.1)
InputManager的建立
//建立一個InputDispatcher對象用于分發事件,一個InputReader對象用于讀事件并把事件交給InputDispatcher分發,然後調用initialize()初始化,其實也就是建立了InputReaderThread和InputDispatcherThread
InputManager::InputManager(
const sp<InputReaderInterface>& reader,
const sp<InputDispatcherInterface>& dispatcher) :
mReader(reader),
mDispatcher(dispatcher) {
initialize();
}
//InputDispatcher和InputReader的建立都相對簡單。InputDispatcher會建立自己線程的Looper,以及設定根據傳入的dispatchPolicy設定分發規則。InputReader則會将傳入的InputDispatcher封裝為監聽對象存起來,做一些資料初始化就結束了
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
InputManagerService的start()
public void start() {
Slog.i(TAG, "Starting input manager");
//調用nativeStart方法,其實就是調用InputManager的start()方法
nativeStart(mPtr);
// 将InputManagerService交給WatchDog監控
Watchdog.getInstance().addMonitor(this);
//注冊觸控點速度、顯示觸控的觀察者,并注冊廣播監控它們
registerPointerSpeedSettingObserver();
registerShowTouchesSettingObserver();
registerAccessibilityLargePointerSettingObserver();
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updatePointerSpeedFromSettings();
updateShowTouchesFromSettings();
updateAccessibilityLargePointerFromSettings();
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
//主動調用updateXXX方法更新(初始化)
updatePointerSpeedFromSettings();
updateShowTouchesFromSettings();
updateAccessibilityLargePointerFromSettings();
Input涉及主要線程
從上述結構圖中看到Input系統涉及到的主要線程為:
InputReader線程:通過EventHub從/dev/input節點擷取事件,轉換成EventEntry事件加入到InputDispatcher的mInboundQueue。當IMS.start啟動時啟動
InputDispatcher線程:從mInboundQueue隊列取出事件,轉換成DispatchEntry事件加入到connection的outboundQueue隊列。再然後開始處理分發事件,取出outbound隊列,放入waitQueue.
UI線程:(DisplayThread)建立socket pair,分别位于InputDispatcher線程和focused視窗所在程序的UI主線程,可互相通信。
a:UI主線程:通setFdEvents() 監聽socket用戶端,收到消息後回調NativeInputEventReceiver();
b:InputDispatcher線程: 通過IMS.registerInputChannel(),監聽socket服務端,收到消息後回調handleReceiveCallback
InputReader
InputReader同樣是system_server的子線程,負責輸入裝置的管理和輸入事件的處理,主要的作用如下:
- 從EventHub中獲得底層事件
- 處理輸入裝置添加、删除事件,管理裝置的InputMapper
- 處理輸入事件,由裝置對應的各個InputMapper處理之後傳遞InputDispatcher進行分發
從上述結構圖看到:
InputReaderThread線程是和EventHub關聯是通過InputReaderThread線程啟動後,會循環執行mReader->looperonce,loopOnce()中會調用mEventHub->getEvents讀取事件,讀取事件後讀到了事件就會調用processEventsLocked處理事件處理完成後調用getInputDevicesLocked擷取輸入裝置資訊調用mPolicy->notifyInputDevicesChanged函數利用InputManagerService的代理通過Handler發送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知輸入裝置發生了變化最後調用mQueuedListener->flush(),将事件隊列中的所有事件交給在InputReader中注冊過的InputDispatcher
EventHub
EventHub為輸入系統的最底層子產品,輸入事件的源頭,此類主要的作用為:
►監控輸入裝置添加、删除事件
►擷取新增裝置的資訊,如裝置的類别、支援哪些按鍵、鍵盤映射表等。
►擷取input_event,封裝成RawEvent傳遞給InputReader
►支援查詢輸入裝置目前的狀态。
●組合鍵狀态
●是否有LED燈或支援振動
new EventHub
EventHub的建立過程中做了以下事情:
建立mEpollFd用于監聽是否有資料(有無事件)可讀
建立mINotifyFd将它注冊到DEVICE_PATH(這裡路徑就是/dev/input)節點,并将它交給核心用于監聽該裝置節點的增删資料事件。那麼隻要有資料增删的事件到來,epoll_wait()就會傳回,使得EventHub能收到來自系統的通知,并擷取事件的詳細資訊
調用epoll_ctl函數将mEpollFd和mINotifyFd注冊到epoll中
定義int wakeFd[2]作為事件傳輸管道的讀寫兩端,并将讀端注冊到epoll中讓mEpollFd監聽
相關代碼如下:
EventHub::EventHub(void) :
mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
mOpeningDevices(0), mClosingDevices(0),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false), mNeedToScanDevices(true),
mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
//建立mEpollFd用于監聽是否有資料(有無事件)可讀,EPOLL_SIZE_HINT:8
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
//建立mINotifyFd将它注冊到DEVICE_PATH(這裡路徑就是/dev/input)節點,并将它交給核心用于監聽該裝置節點的增删資料事件。那麼隻要有資料增删的事件到來,epoll_wait()就會傳回,使得EventHub能收到來自系統的通知,并擷取事件的詳細資訊
mINotifyFd = inotify_init();
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s. errno=%d",
DEVICE_PATH, errno);
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));//epoll_event這個結構體清空
eventItem.events = EPOLLIN;//監測的事件類型為EPOLLIN
eventItem.data.u32 = EPOLL_ID_INOTIFY;
//調用epoll_ctl函數将mEpollFd和mINotifyFd注冊到epoll中,監測的fd為:mINotifyFd
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
//作為事件傳輸管道的讀寫兩端,并将讀端注冊到epoll中讓mEpollFd監聽
int wakeFds[2];
result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
mWakeReadPipeFd = wakeFds[0];//建立讀取事件的通道
mWakeWritePipeFd = wakeFds[1];//建立寫入事件的通道
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
eventItem.data.u32 = EPOLL_ID_WAKE;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
int major, minor;
getLinuxRelease(&major, &minor);
// EPOLLWAKEUP was introduced in kernel 3.5
mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}
那麼這裡抛出一個問題:為什麼要把管道的讀端注冊到epoll中?假如EventHub因為getEvents讀不到事件而阻塞在epoll_wait()裡,而我們沒有綁定讀端的話,我們要怎麼喚醒EventHub?如果綁定了管道的讀端,我們就可以通過向管道的寫端寫資料進而讓EventHub因為得到管道寫端的資料而被喚醒。
EventHub如何監聽裝置的插拔和讀取底層的事件,主要通過inotify和epoll