Android按鍵事件傳遞流程(一)
做Android開發的少不了對觸控、按鍵事件進行處理,對於手機來說,主要是手勢移動、觸控按下等,而TV主要通過遙控器、按鍵操作,按鍵事件不同於觸控事件,必須先獲得焦點,然後才能移動、選擇。
Android輸入裝置支援滑鼠、鍵盤(按鍵)、觸控式螢幕(單點、多點)、軌跡球等,這些裝置所產生的輸入事件Input Event從底層驅動開始經過input子系統核心層到達Event Handler事件層,最終把事件copy_to_user到使用者空間,然後由使用者空間層獲取這些事件進行分發、傳遞。整個過程涉及到核心層、Framework層以及應用層,核心層傳遞過程不在本文研究範圍內,本文主要對按鍵事件在Framework層、應用層的傳遞過程進行分析(本文基於Android5.1.1版本),帶著問題出發:
1. Framework層如何獲得輸入事件
2. Framework層獲得按鍵事件後如何處理、儲存
3. Framework層如何分發、傳遞給應用層
4. Framework層服務端管道和應用程式客戶端管道如何建立、註冊
5. 應用層如何從Framework層接收按鍵事件
6. 應用層接收到按鍵事件後如何傳遞
7. 特殊按鍵如何處理
8. 總結
1. Framework層如何獲得輸入事件
要分析輸入系統,最好先從輸入事件系統服務InputManagerService入手,這是瞭解輸入系統的起點,所有其他相關類、執行緒都因此被建立或間接建立。
1.1 InputManagerService的初始化
Android系統啟動時,Android部分第一個使用者空間程序zygote(注:如果從Linux角度出發,第一個使用者空間程序是init)首先fork建立了SystemServer物件,隨後SystemServer建立了核心服務ActivityManagerService、PowerManagerService、PackageManagerService、InputManagerService等,相關程式碼在SystemServer.java的run方法中,與InputManagerService相關部分:
1 ) inputManager = new InputManagerService(context);
2) wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore);
3) ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
4) inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
5) inputManager.start();
1.1.1 InputManagerService的構造方法
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
建立了一個InputManagerHandler物件,引數是HandlerThread中建立的looper物件
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
用Looper中的訊息佇列作為引數,呼叫本地方法nativeInit,返回C++中的NativeInputManager物件地址賦給mPtr,mPtr在1.5節會用到。
LocalServices.addService(InputManagerInternal.class, new LocalService());
把InputManagerInternal.class和LocalService物件作為一對對映新增到ArrayMap<Class<?>, Object>中
1.1.2 nativeInit方法實現
static jlongnativeInit(JNIEnv* env, jclassclazz,
jobjectserviceObj, jobjectcontextObj, jobjectmessageQueueObj) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}
android_os_MessageQueue_getMessageQueue方法:
sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobjectmessageQueueObj) {
jlongptr = env->GetLongField(messageQueueObj, gMessageQueueClassInfo.mPtr);
return reinterpret_cast<NativeMessageQueue*>(ptr);
}
獲得了C++層NativeMessageQueue物件,NativeMessageQueue物件是在建立Looper物件時建立的,在本部落格Handler機制中已經分析過
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
建立了NativeInputManager物件,返回到java層的nativeInit,並將地址賦給mPtr;getLooper()獲得c++層的Looper物件,在Handler機制中,此時Looper物件也已初始化,同時建立了管道並註冊在epoll興趣列表中
NativeInputManager的建構函式:
NativeInputManager::NativeInputManager(jobjectcontextObj,
jobjectserviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();
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;
}
sp<EventHub> eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
把獲得的Looper物件傳給過來賦給mLooper,用來回調Looper物件的方法;建立c++層中全域性變數mContextObj, mServiceObj;建立EventHub物件並作為引數建立InputManager物件。
1.1.3 EventHub.cpp的建構函式
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_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
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_eventeventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
eventItem.data.u32 = EPOLL_ID_INOTIFY;
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);
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);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
eventItem.data.u32 = EPOLL_ID_WAKE;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
int major, minor;
getLinuxRelease(&major, &minor);
// EPOLLWAKEUP was introduced in kernel 3.5
mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}
初始化必要的變數,採用epoll機制系統呼叫介面epoll_create建立epoll物件,返回檔案描述符mEpollFd,代表epoll物件;採用inotify機制監聽檔案或目錄的移動、讀取、寫入或刪除等事件,inotify機制主要包含2個系統呼叫介面:inotify_init, inotify_add_watch
inotify_init:建立一個inotify物件,如果成功,返回一個檔案描述符,作為該物件;返回-1表示出錯
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE)
inotify_add_watch:把監控項新增到mINotifyFd物件的監控列表中,第二個引數DEVICE_PATH就是被監控物件,該物件一般是檔案或目錄,本文中被監控的DEVICE_PATH就是/dev/input目錄;第三個引數是一個位掩碼,表示被監控物件上發生的具體事件,可以由1個或多個掩碼位或門組成。IN_DELETE:當被監控目錄內刪除檔案或目錄時觸發該事件;IN_CREATE:當被監控目錄內建立檔案或目錄時觸發該事件。比如,插入、拔出滑鼠時,就會觸發該事件。
如果inotify_add_watch執行成功,返回一個非負監控描述符(假如為wd),代表被監控的項,如果將來不需要再監聽了,可以使用inotify_rm_watch(fd, wd)刪除所新增的監控項wd。
struct epoll_eventeventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
eventItem.data.u32 = EPOLL_ID_INOTIFY;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
把inotify物件mINotifyFd新增到epoll物件的興趣列表中,此處採用inotify與epoll機制結合起來檢查檔案
eventItem.data.u32 = EPOLL_ID_WAKE;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
建立了管道物件讀取端檔案描述符mWakeReadPipeFd並新增到epoll物件的興趣列表中
讀到此處,提出2個問題:
Q1 epoll機制一般有3個系統呼叫介面,而截止到此處,只提到epoll_create, epoll_ctl,還缺epoll_wait,猜測在程式碼某處肯定有epoll_wait,否則epoll機制無法工作
Q2 如果找到了epoll_wait,呼叫程序處於等待狀態,那麼肯定還有一處用來喚醒呼叫程序的程式碼,比如Looper的wake函式或者其他地方呼叫了write系統呼叫
記住這兩個問題,有助於在分析多程序控制時的程式碼走向
1.1.4 InputManager.cpp的構造器
mInputManager = new InputManager(eventHub, this, this);
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
第一個引數是剛建立的EventHub物件,第二、三個引數都是this,當前NativeInputManager物件,傳遞過去後分別是InputReaderPolicyInterface、InputDispatcherPolicyInterface型別,這是為何?因為NativeInputManager繼承了InputReaderPolicyInterface、InputDispatcherPolicyInterface類,實際型別還是NativeInputManager
用這兩個傳遞過去的物件型別作為引數分別建立了InputDispatcher和InputReader物件,再呼叫initialize方法分別建立了與InputDispatcher和InputReader對應的執行緒InputDispatcherThread和InputReaderThread物件
1.1.5 InputDispatcher的建構函式
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
mPolicy(policy),
mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
mNextUnblockedEvent(NULL),
mDispatchEnabled(false), mDispatchFrozen(false), mInputFilterEnabled(false),
mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
mLooper = new Looper(false);
mKeyRepeatState.lastKeyEntry = NULL;
policy->getDispatcherConfiguration(&mConfig);
}
把傳遞過來的NativeInputManager物件賦給mPolicy,該NativeInputManager物件又是InputDispatcherPolicyInterface介面型別,然後又初始化了很多變數,其中包括:
mLooper = new Looper(false);
建立並初始化了Looper物件,在Looper構造方法建立了管道並採用epoll機制把管道加入到興趣列表中。
注意,此處建立的Looper物件是在InputDispatcher中的,與主執行緒中的Looper沒有關係。
Q3 既然有了epoll_ctl,那肯定某處會呼叫epoll_wait
1.1.6 InputReader的建構函式
InputReader::InputReader(const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) :
mContext(this), mEventHub(eventHub), mPolicy(policy),
mGlobalMetaState(0), mGeneration(1),
mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
mQueuedListener = new QueuedInputListener(listener);
{ // acquire lock
AutoMutex_l(mLock);
refreshConfigurationLocked(0);
updateGlobalMetaStateLocked();
} // release lock
}
第一個引數是EventHub物件,第二個policy賦值給mPolicy,policy是InputReaderPolicyInterface介面型別,實際是NativeInputManager物件型別。第三個listener實際是InputDispatcher型別,因為InputDispatcher實現了InputDispatcherInterface,InputDispatcherInterface又實現了InputListenerInterface,因此policy也是InputListenerInterface物件型別
mQueuedListener = new QueuedInputListener(listener);
把InputListenerInterface物件型別listener作為引數建立QueuedInputListener物件,傳遞過去後賦給mInnerListener變數
1.1.7 InputManager.cpp的initialize函式
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
用InputReader物件作為引數建立InputReaderThread執行緒物件,用InputDispatcher作為引數建立InputDispatcherThread執行緒物件,InputReaderThread的建構函式:
InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
Thread(/*canCallJava*/ true), mReader(reader) {
}
把InputReader物件賦給mReader變數
InputDispatcherThread的建構函式:
InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
}
把InputDispatcher物件賦給mDispatcher變數
程序執行到此處,InputManagerService的初始化就完成,整個過程都是在建立、初始化各種物件。
Q4 既然建立了執行緒物件,那麼就應該在某處啟動執行緒,否則執行緒無法執行,在哪裡啟動執行緒
1.2 WindowManagerService的main方法
wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore);
public static WindowManagerServicemain(final Contextcontext,
final InputManagerServiceim,
final boolean haveInputMethods, final boolean showBootMsgs,
final boolean onlyCore) {
final WindowManagerService[] holder = new WindowManagerService[1];
DisplayThread.getHandler().runWithScissors(new Runnable() {
@Override
public void run() {
holder[0] = new WindowManagerService(context, im,
haveInputMethods, showBootMsgs, onlyCore);
}
}, 0);
return holder[0];
}
mInputManager = inputManager;
main方法建立了WindowManagerService物件,第二個引數是InputManagerService物件,傳遞到WindowManagerService的建構函式中賦給了mInputManager
1.3 ServiceManager的addService方法
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
把InputManagerService服務註冊到ServiceManager中
1.4 InputManagerService的setWindowManagerCallbacks方法
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
把InputMonitor物件傳遞到InputManagerService中去,方便回撥,比如7.2節通過該引數回撥InputMonitor中的interceptKeyBeforeQueueing方法
1.5 InputManagerService的start方法
start方法中有兩句:
nativeStart(mPtr);
Watchdog.getInstance().addMonitor(this);
nativeStart的本地實現:
static void nativeStart(JNIEnv* env, jclassclazz, jlongptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
status_tresult = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
ptr在1.1.1節已經提到過,是NativeInputManager物件地址,只不過是長整形,此處再轉化成NativeInputManager型指標
Watchdog是看門狗,一個單例類,addMonitor方法把InputManagerService物件新增到Watchdog,便於回撥
Q5 建立了看門狗,有什麼用?
nativeStart的傳遞過程: nativeStart —-> com_android_server_input_InputManagerService.cpp的nativeStart —-> InputManager.cpp的start()
1.5.1 InputManager.cpp的start
status_tInputManager::start() {
status_tresult = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputReader thread due to error %d.", result);
mDispatcherThread->requestExit();
return result;
}
return OK;
}
首先啟動分發器執行緒InputDispatcherThread,呼叫run方法後,開始執行threadLoop方法,如果threadLoop返回true,就再次執行threadLoop方法直到requestExit方法停止執行緒;再呼叫InputReaderThread的run方法啟動接收器執行緒。這回答了1.1.7節Q4問題,只要建立了執行緒,那就應該有啟動執行緒的地方。
threadLoop中呼叫了mDispatcher->dispatchOnce()