http://www.lxweimin.com/p/2bff4ecd86c9
本篇博客主要是過一下Android Input的框架,在熟悉Android input框架后,以及與App的事件傳遞流程后,再過一下事件的處理流程,以及ANR是怎樣發生的。如果在不了解input的框架情況下就去直接按鍵等事件就會覺得很卡。
一、Android Input的工作模型
1.1InputDispatcher
InputDispatcher單獨run在InputDispatcher線程中
1.1.1 InputDispatcher的模型
InputDispatcher的實現模型是Looper的機制,其底層根本還是屬于epoll機制. 只不過Input并沒有使用Looper相關的Message相關的功能,也就是說沒有MessageQueue了,僅是單純的使用Looper的addFd功能,以及它的epoll阻塞喚醒功能。
InputDispatcher單獨運行在一個線程當中,當線程啟動時,它會不停的調用threadLoop,
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
每一次threadLoop都會調用InputDispatcher的dispatchOnce函數
void InputDispatcher::dispatchOnce() {
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
mDispatcherIsAliveCondition.broadcast();
// Run a dispatch loop if there are no pending commands.
// The dispatch loop might enqueue commands to run afterwards.
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
} // 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);
}
上面dispatchOnce先會嘗試去獲得pending的Commands,然后處理這些pending的命令。當這些都處理完成后,會調用Looper的pollOnce,傳進去的參數是timeout. 正常情況下,如果當前沒有喚醒,或沒有fd的回調(這個后面會講,也就是App消費了input事件的回調), 那么InputDispatcher線程就一直block在Looper的epoll那里,直到被喚醒。具體可以參考Android Handler/Looper
1.1.2 Looper的喚醒
1.1.1已經大致的說了下InputDispatcher線程的工作模型,沒有事件時它會block在Looper的epoll處. 那它啥時候被喚醒呢?其實很簡單,查找哪些地方調用了 mLooper->wake();,還有一個地方是App消費了input事件的回調(后面講)
notifyConfigurationChanged()
notifyKey()
notifyMotion()
notifySwitch();
injectInputEvent()
setInputWindows()
setFocusedApplication()
setInputDispatchMode()
setInputFilterEnabled()
transferTouchFocus()
registerInputChannel()
unregisterInputChannel()
monitor()
上面這些函數都有調用到mLooper->wake的可能。
如notifyKey()/notifyMotion() 等, "間接"來自InputReader
線程的通知。
setInputWindows()/setFocusedApplication()等,"間接"來自android.display
線程的通知。
如果 App 消費了Input事件, Looper也會被喚醒,接著handleReceiveCallback被回調。
這里以notifyConfigurationChanged為例
void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
bool needWake;
{ // acquire lock 防止多線程同時訪問,這里加了一個mLock的互斥鎖。
AutoMutex _l(mLock);
ConfigurationChangedEntry* newEntry = new ConfigurationChangedEntry(args->eventTime);
needWake = enqueueInboundEventLocked(newEntry);
} // release lock
if (needWake) {
mLooper->wake();
}
}
notifyConfigurationChanged
其實挺簡單的,生成一個ConfigurationChangedEntry,然后通過enqueueInboundEventLocked函數加入到mInboundQueue隊列中。具體的可以看參考下enqueueInboudnEventLocked. 然后根據needWake決定是否喚醒Looper, 這個needWake,默認在mInboundQueue里沒有數據時為true, 當mInboundQueue里有數據時,此時Looper應該已經被喚醒了,且正在處理mInboundQueue里的命令,此時已經是wake的狀態,所以沒有必要再次wake一次。
Queue<EventEntry> mInboundQueue;
mInboundQueue申明為一個隊列,主要是保存InputReader中傳過來的EventEntry.
EventEntry主要有如下的幾種類型
mPendingEvent是EventEntry類型,它根據type類型轉為具體的EventEntry,如 MotionEntry, KeyEntry等等。
1.1.3 InputDispatcher處理Commands
從1.1的dispatchOnce()代碼中可以看出,InputDispatcher在當前沒有commands時會直接調用dispatchOnceInnerLocked一次,而dispatchOnceInnerLocked的目的就是去獲得Commands.
如果當前有Commands了(比如1.2已經ConfigurationEventEntry),就不會去獲得Commands,而是直接run已經有的Commands.
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
if (! mPendingEvent) {
if (mInboundQueue.isEmpty()) {
if (!mPendingEvent) {
return;
}
} else {
mPendingEvent = mInboundQueue.dequeueAtHead();
}
...
} //這個if塊意在獲得一個mPendingEvent,如果沒有Pending的event, 直接返回掉
//如果已經走到下面,mPendingEvent是不為空的,也就是有待處理的事件
switch (mPendingEvent->type) {
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
break;
}
case EventEntry::TYPE_DEVICE_RESET: {
case EventEntry::TYPE_KEY: {
case EventEntry::TYPE_MOTION: {
}
這里依然以mPendingEntry為ConfigurationChangedEntry為例,
bool InputDispatcher::dispatchConfigurationChangedLocked(
nsecs_t currentTime, ConfigurationChangedEntry* entry) {
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doNotifyConfigurationChangedInterruptible);
commandEntry->eventTime = entry->eventTime;
return true;
}
InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
CommandEntry* commandEntry = new CommandEntry(command);
mCommandQueue.enqueueAtTail(commandEntry);
return commandEntry;
}
生成一個Commands,它的函數指針指向doNotifyConfigurationChangedInterruptible.
dispatchOnce在runCommandsLockedInterruptible()里去處理所有的Commands,這時會調用doNotifyConfigurationChangedInterruptible,
void InputDispatcher::doNotifyConfigurationChangedInterruptible(
CommandEntry* commandEntry) {
mLock.unlock();
mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
mLock.lock();
}
最終調用mPolicy, 也就是NativeInputManager中的notifyConfigurationChanged, 將結果返回到java層去。
這種情況并沒有包含 Key/Motion這樣的事件情況,(后續會繼續介紹)
1.1.4 小結
- a). InputDispatcher使用Looper的epoll模型, 意味著在沒有命令處理時會block在epoll處
- b). 當IMS(java層) 或 InputReader有事件要dispatch時,它們會喚醒InputDispatcher
- c). InputDispatcher被喚醒后,會從mInboundQueue隊列中查找pending的event, 然后生成對應的Commands, 最后執行這些Commands.
1.2 InputReader
InputReader單獨運行在InputReaderThread中,它依然繼承于Thread類,也就是當InputReaderThread線程運行起來后它會一直調用threadLoop()函數。InputReader并沒有使用Looper機制,不過它使用到了EventHub里的 epoll 機制,和Looper的epoll機制一樣。
1.2.1 EventHub
EventHub在NativeInputManager里初始化,并沒有放到InputReader里初始化,其實完全可以放到InputReader里初始化的呢? why???
EventHub::EventHub(void) : ... {
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
//創建一個epoll文件字柄
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
mINotifyFd = inotify_init(); //創建一個inotify fd
//inotify 監聽 /dev/input 目錄
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
...
//將inotify fd加入到epoll中
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
int wakeFds[2];
//創建一個管道
result = pipe(wakeFds);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
...
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}
從EventHub的初始化可以看出,它通過inotify監聽 /dev/input里的文件的變化,另外創建了一個管道, 將read fd加入到epoll中去監聽,而write fd主要用來喚醒epoll.
從 EventHub::wake()函數可以看出來
void EventHub::wake() {
ssize_t nWrite;
do {
nWrite = write(mWakeWritePipeFd, "W", 1);
} while (nWrite == -1 && errno == EINTR);
}
通過調用wake函數,往 write fd中寫入一個字節,然后epoll監聽的read fd就有事件發生,epoll就被喚醒了,這和Looper的wake機制一模一樣。
1.2.2 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
}
InputReader的構造函數中初始化了一個QueuedInputListener, 它接收InputListenerInterface作為它的參數,從InputReader調用可知,這個InputListenerInterface其實就是InputDispatcher, QueueInputListener只是作為InputDispatcher的Wrapper.
1.2.2.1 讀取Java層中的配置
void InputReader::refreshConfigurationLocked(uint32_t changes) {
mPolicy->getReaderConfiguration(&mConfig);
mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
...
}
refreshConfigurationLocked的主要是通過getReaderConfiguration調用到Java層的配置信息,保存到mConfig里。具體調用到如下的接口
getVirtualKeyQuietTimeMillis
getExcludedDeviceNames
getHoverTapTimeout
getDoubleTapTimeout
getLongPressTimeout
getHoverTapSlop
這些函數具體實現就是去取一些framework-res.jar里的一些配置信息。讀到的信息最后都會設置到InputReader里不同的模塊中,比如"ecluded device name"會去設置 EventHub 里mExcludedDevices等等。
1.2.3 InputReader運作起來
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
InputReaderThread線程開啟后會不停的運行threadLoop函數, 而它會調用InputReader的loopOnce函數
void InputReader::loopOnce() {
... 太多細節就不多說了
//獲得事件, 沒有事件就block在EventHub中的epoll處
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
AutoMutex _l(mLock);
if (count) { //有事件了,著手處理事件
processEventsLocked(mEventBuffer, count);
}
...
} // release lock
// Send out a message that the describes the changed input devices.
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}
//將獲得的事件傳給InputDispatcher去做處理
mQueuedListener->flush();
}
EventHub的getEvents函數太長,這里就不貼出來了, 它主要就是獲得事件,這里的事件,并不單單指input事件,它還包括輸入設備的add/remove等相關的事件.
獲得輸入設備加入和刪除事件
輸入設備的加入和移除事件跟幾個變量非常相關.
- mNeedToReopenDevices
表示需要重新打開輸入設備, 它會先close當前已經打開的設置做一些清理工作,具體參見closeAllDevicesLocked() 函數,將沒有delete掉的設備用mClosingDevices來表示,最后會把 mNeedToScanDevices 置為true. - mClosingDevices
表示當前沒有被delete掉的設備,這getEvents里就將這些設備依次刪除掉, 并生成 DEVICE_REMOVED事件 - mNeedToScanDevices
該變量表示需要掃描輸入設備,并打開輸入設備,加入到mDevices中,用mOpeningDevices表示這些設備的Head - mOpeningDevices
表示剛剛打開的所有的設備,它是一個單鏈表的HEAD, getEvents會將它所保存的所有剛打開的設備創建一個DEVICE_ADDED事件 - mNeedToSendFinishedDeviceScan
表示finish 掃描輸入設備, 會生成一個FINISHED_DEVICE_SCAN事件
getEvents通過將產生的事件放到mEventBuffer所指向的一維數給中,然后通過最后一個事件的地址-mEventBuffer地址就可以得到當前有多少事件,很巧妙。
獲得輸入設備的事件
輸入設備加入后,如果沒有具體的事件產生的話,它就會進入epoll的阻塞狀態。
for (;;) {
//處理變化的事件
while (mPendingEventIndex < mPendingEventCount) {
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
//針對EventHub::wake
if (eventItem.data.u32 == EPOLL_ID_WAKE) {
if (eventItem.events & EPOLLIN) {
awoken = true;
...
}
}
ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
Device* device = mDevices.valueAt(deviceIndex);
if (eventItem.events & EPOLLIN) {
int32_t readSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);
...
} 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]; //獲得具體的輸入事件
//將輸入事件保存到mEventBuf中
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;
event += 1; //指向下一個事件
capacity -= 1;
}
//當有事件后就直接退出, awoken表示通過調用EventHub::wake函數喚醒epoll,也直接退出
if (event != buffer || awoken) {
break;
}
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
if (pollResult < 0) {
} else {
// Some events occurred.
mPendingEventCount = size_t(pollResult);
}
}
當有事件產生后,epoll_wait就會返回,將有改變的個數放到mPendingEventCount中, 再下一輪的for循環中, 就在while循環中處理變化的事件.
while循環其實挺簡單,主要是通過從改變的輸入設備中讀取輸入事件,然后保存到mEventBuf中,然后從getEvents返回。
處理事件
void InputReader::loopOnce() {
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
if (count) {
processEventsLocked(mEventBuffer, count); // 處理事件
}
mQueuedListener->flush();
}
InputReader在有事件發生后,getEvents就會返回,如果返回的count > 0時,就著手處理這些事件
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) {
int32_t deviceId = rawEvent->deviceId;
while (batchSize < count) {
if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
|| rawEvent[batchSize].deviceId != deviceId) {
break;
}
batchSize += 1;
}
//處理輸入事件
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:
//configuration 改變
handleConfigurationChangedLocked(rawEvent->when);
break;
}
}
count -= batchSize;
rawEvent += batchSize;
}
}
processEventsLocked根據返回的那些事件依次處理,包括對輸入設備的增加和移出,以及輸入事件的處理。
這里依然以handleConfigurationChangedLocked為例
void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
NotifyConfigurationChangedArgs args(when);
mQueuedListener->notifyConfigurationChanged(&args);
}
Vector<NotifyArgs*> mArgsQueue;
void QueuedInputListener::notifyConfigurationChanged(
const NotifyConfigurationChangedArgs* args) {
mArgsQueue.push(new NotifyConfigurationChangedArgs(*args));
}
handleConfigurationChangedLocked生成一個NotifyConfigurationChangedArgs然后通過QueuedListener,將NotifyConfigurationChangedArgs加入到mArgsQueue這個vector中
當InputReader::loopOnce()在處理完事件后會調用 mQueuedListener->flush();
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();
}
void NotifyConfigurationChangedArgs::notify(const sp<InputListenerInterface>& listener) const {
listener->notifyConfigurationChanged(this);
}
flush函數對mArgsQueue里所有的NotifyArgs,調用notify, 這里mInnerListener也就是InputDispatcher, 如NotifyConfigurationChangedArgs為例,調用InputDispatcher的notifyConfigurationChanged將事件傳入到了InputDispatcher中了。
至此InputReader的工作模型就介紹完了。
1.3 Input java層與jni層相互調用
已經知道Input工作在三個線程中,一個java線程,兩個jni線程(InputReader, InputDispatcher)
- Java通過jni獲得相關信息
Java層通過各種 nativeXXX去獲得jni中的相關信息,具體可以查詢InputManagerService.java中的nativeXXX開頭的函數, 如
nativeGetKeyCodeState()
nativeSetFocusedApplication()
它們對應的jni實現如下
static jint nativeGetSwitchState(JNIEnv* /* env */, jclass /* clazz */,
jlong ptr, jint deviceId, jint sourceMask, jint sw) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
return (jint) im->getInputManager()->getReader()->getSwitchState(
deviceId, uint32_t(sourceMask), sw);
}
static void nativeSetFocusedApplication(JNIEnv* env, jclass /* clazz */,
jlong ptr, jobject applicationHandleObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
im->setFocusedApplication(env, applicationHandleObj);
}
void NativeInputManager::setFocusedApplication(JNIEnv* env, jobject applicationHandleObj) {
sp<InputApplicationHandle> applicationHandle =
android_server_InputApplicationHandle_getHandle(env, applicationHandleObj);
mInputManager->getDispatcher()->setFocusedApplication(applicationHandle);
}
如圖,它們都是通過NativeInputManager去獲得InputDispatcher或InputReader去做相應的處理,注意,這些都是在java線程中調用的,為了線程安全,在相應的實現中都有鎖。
- Jni回調java接口
同樣InputDispatcher和InputReader線程都有可能調用到java層的接口,具體就不多說了。
二、Android Input與App
第一節已經基本說了下Android Input的代碼結構,input的作用就是獲得輸入設備產生的事件,并且分發出來,那分發到哪里去了呢? 當然是分發到了Focused的App里了。
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel(); //生成一個InputChannel
}
...
try {
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
App在addView時,會在ViewRootImpl中生成InputChannel, InputChannel實現了Parcelable, 所以它可以通過Binder傳輸。具體是通過addDisplay()將當前window加入到WMS中管理,同時也會有相應的input的處理.
2.1 SystemServer端中WMS的addWindow
public int addWindow(Session session, IWindow client, ... InputChannel outInputChannel) {
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
}
addWindow會通過WindowState去openInputChannel()
void openInputChannel(InputChannel outInputChannel) {
String name = getName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
mInputWindowHandle.inputChannel = inputChannels[0];
if (outInputChannel != null) {
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
mClientChannel = null;
} else {
}
mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}
openInputChannel做了三件事,
一是通過openInputChannelPair也就是nativeOpenInputChannelPair去打開一組socket用于跨進程通信. 具體可以參考
android_view_InputChannel_nativeOpenInputChannelPair -> InputChannel::openInputChannelPair()
-
創建一對socket pair
transfer給outInputChannel
if (outInputChannel != null) {
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
mClientChannel = null;
- 注冊InputChannel和InputWindowHandle給Input
mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
在registerInputChannel后,InputDispatcher就開始監聽App在Server端的InputChannel了。
2.2 App端獲得InputChannel
SystemServer端生成的InputChannel是在SystemServer進程中,App進程不能直接訪問其地址,那App是怎么獲得InputChannel的呢? 當然是通過Binder了.
App的ViewRootImpl在調用addToDisplay返回后,ViewRootImpl里的InputChannel就指向了正確的InputChannel, 它是Client端,即Client端的fd與SystemServer進程中Server端的fd組成 socket pair, 它們就可以雙向通信了。 那App端的InputChannel是如何正確的Client的InputChannel呢?
在 IWindowSession類中
public int addToDisplay(... android.view.InputChannel outInputChannel) {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
...
mRemote.transact(Stub.TRANSACTION_addToDisplay, _data, _reply, 0);
...
if ((0 != _reply.readInt())) {
outInputChannel.readFromParcel(_reply);
}
}
Binder Proxy端(App)端中 ViewRootImpl中的InputChannel是從Parcel里讀出來的。
public boolean onTransact(...) {
case TRANSACTION_addToDisplay: {
...
android.view.InputChannel _arg8;
_arg8 = new android.view.InputChannel();
int _result = this.addToDisplay(...);
reply.writeNoException();
reply.writeInt(_result);
...
if ((_arg8 != null)) {
reply.writeInt(1);
_arg8.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
Binder Server端(SystemServer)在 onTransact里生成一個局部的InputChannel,在addDisplay處理完后,就將InputChannel序列化到Parcel中傳遞到App端.
static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj,
jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel) {
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, obj);
if (nativeInputChannel) {
sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
parcel->writeInt32(1);
parcel->writeString8(inputChannel->getName());
parcel->writeDupFileDescriptor(inputChannel->getFd());
} else {
parcel->writeInt32(0);
}
}
}
序列化的過程其實就三個,寫name, 然后writeDupFileDescriptor, dup文件句柄。
反序列化過程
static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
jobject parcelObj) {
if (android_view_InputChannel_getNativeInputChannel(env, obj) != NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"This object already has a native input channel.");
return;
}
Parcel* parcel = parcelForJavaObject(env, parcelObj);
if (parcel) {
bool isInitialized = parcel->readInt32();
if (isInitialized) {
String8 name = parcel->readString8();
int rawFd = parcel->readFileDescriptor();
int dupFd = dup(rawFd);
if (dupFd < 0) {
return;
}
InputChannel* inputChannel = new InputChannel(name, dupFd);
NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel);
android_view_InputChannel_setNativeInputChannel(env, obj, nativeInputChannel);
}
}
}
反序化就是在 App端 native層生成一個InputChannel,然后dup 文件句柄,設置等等
2.3 App端注冊InputChannel到Looper
通過 2.2 小節,App端已經獲得了InputChannel,以及正確的socket fd. 那要怎么利用起來呢?
ViewRootImpl在addDisplay后,會生成一個WindowInputEventReceiver
if (mInputChannel != null) {
...
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
Looper.myLooper()是App進程Main線程的Looper.
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}
WindowInputEventReceiver會調用父類InputEventReceiver構造函數,然后通過nativeInit函數將InputChannel的fd加入到Looper的epoll中去。
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject inputChannelObj, jobject messageQueueObj) {
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, 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();
if (events) {
mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
} else {
mMessageQueue->getLooper()->removeFd(fd);
}
}
}
也就是說在 App進程的Main線程的Looper中監聽InputChannel的Client端。當有事件發生時,Looper就會回調 NativeInputEventReceiver::handleEvent()
2.4 小節
App通過Binder獲得InputChannel的client端,然后將fd加入到App進程的Main線程中監聽。
三、input事件的傳遞流程
3.1 Input事件傳遞Overview
在了解了input框架和App端與Input的關系后,input的按鍵等相關事件的傳遞過程就相當簡單了。
圖中綠色方塊表示調用java的方法.
InputReader在將事件加入到mInboundQueue之前會嘗試interceptKey, 如果按鍵被截獲成功,那么在InputDispatcher的紅色塊會被drop掉
以及filterInputEvent. 如果filter成功,那在InputReader線程中就直接返回,不會再將Event傳遞到InputDispatcher中.
另外
一個InputDevice可支持多種Mapper, 取決于mClasses的值, 具體是在
InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, uint32_t classes) {
InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
controllerNumber, identifier, classes);
...
// Vibrator-like devices.
if (classes & INPUT_DEVICE_CLASS_VIBRATOR) {
device->addMapper(new VibratorInputMapper(device));
}
// Keyboard-like devices.
uint32_t keyboardSource = 0;
int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
keyboardSource |= AINPUT_SOURCE_KEYBOARD;
}
if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
}
if (classes & INPUT_DEVICE_CLASS_DPAD) {
keyboardSource |= AINPUT_SOURCE_DPAD;
}
if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
keyboardSource |= AINPUT_SOURCE_GAMEPAD;
}
if (keyboardSource != 0) {
device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
}
...
3.2 Native中Focused的App與Window
InputApplicationHandle表示的是一個Focused的Application
InputWindowHandle表示是當前系統中所有的Window, 當然這里是指可以接收Input事件的窗口, 它可以是多個,只不過只一個當前獲得焦點的窗口。
3.1 設置Focused 的 InputApplicationHandle
當一個App resumed后,AMS就會調用setResumedActivityUncheckLocked去更新AMS的一些狀態, 接著通知WMS去setFocusedApp
3.2 設置Focused 的 InputWindowHandle
void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray) {
Vector<sp<InputWindowHandle> > windowHandles; //保存所有從JAVA層傳入的InputWindowHandle
if (windowHandleObjArray) {
jsize length = env->GetArrayLength(windowHandleObjArray);
for (jsize i = 0; i < length; i++) {
jobject windowHandleObj = env->GetObjectArrayElement(windowHandleObjArray, i);
//獲得一個InputWindowHandle
sp<InputWindowHandle> windowHandle =
android_server_InputWindowHandle_getHandle(env, windowHandleObj);
if (windowHandle != NULL) {
//保存到windowHandles里
windowHandles.push(windowHandle);
}
}
}
mInputManager->getDispatcher()->setInputWindows(windowHandles);
void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
{ // acquire lock
AutoMutex _l(mLock);
//獲得舊的所有的window的InputWindowHandle
Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles;
mWindowHandles = inputWindowHandles; //保存所有InputWindowHandle
sp<InputWindowHandle> newFocusedWindowHandle;
bool foundHoveredWindow = false;
for (size_t i = 0; i < mWindowHandles.size(); i++) {
const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
//updateInfo這里是去獲得java層對應的InputWindowHandle的值, 并保存到InputWindowInfo里
//只有當InputWindowHandle里有InputChannel時,這個Window才可能接收 input事件
if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) {
mWindowHandles.removeAt(i--);
continue;
}
//只有當InputWindowHandle hasFocus值為true時,此時將會改變focused InputWindowHandle
if (windowHandle->getInfo()->hasFocus) {
newFocusedWindowHandle = windowHandle;
}
}
if (mFocusedWindowHandle != newFocusedWindowHandle) {
if (mFocusedWindowHandle != NULL) {
// Focused InputWindowHandle改變了,此時會cancel掉上一個Focused的Window的事件
sp<InputChannel> focusedInputChannel = mFocusedWindowHandle->getInputChannel();
if (focusedInputChannel != NULL) {
CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
"focus left window");
synthesizeCancelationEventsForInputChannelLocked(
focusedInputChannel, options);
}
}
//指向最新的Focused InputWindowHandle
mFocusedWindowHandle = newFocusedWindowHandle;
}
//release沒在mWindowsHandle里的舊的InputWindowHandle的信息
for (size_t i = 0; i < oldWindowHandles.size(); i++) {
const sp<InputWindowHandle>& oldWindowHandle = oldWindowHandles.itemAt(i);
if (!hasWindowHandleLocked(oldWindowHandle)) {
oldWindowHandle->releaseInfo();
}
}
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}
從代碼中可以看出,將java層中所有的InputWindowHandle都會加入到InputDispatcher里來保存,然后遍歷所有的InputWindowHandle,根據其是否獲得了焦點來將它設置為mFocusedWindowHandle
特別注意的是,InputWindowHandle里的InputWindowInfo的值都是通過獲得Java層對應的InputWindowHandle的值。具體可以參見 NativeInputWindowHandle::updateInfo()
3.3 找到Focused的App與Window
由3.1, 3.2小節的知識,找到Focused的App與Window就非常簡單了,為什么需要找到這兩個呢?因為當前有輸入事件,輸入事件需要傳遞給當前獲得焦點的App的窗口.
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
int32_t injectionResult;
String8 reason;
//當前Focused的App是否正在add window, 意思是還沒有Focused的window
if (mFocusedWindowHandle == NULL) {
if (mFocusedApplicationHandle != NULL) {
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
mFocusedApplicationHandle, NULL, nextWakeupTime,
"Waiting because no window has focus but there is a "
"focused application that may eventually add a window "
"when it finishes starting up.");
goto Unresponsive;
}
//當前也沒有Focused的 App
injectionResult = INPUT_EVENT_INJECTION_FAILED;
goto Failed;
}
// Check permissions. //檢查是否具體INJECT_EVENT的權限
if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {
goto Failed;
}
// Check whether the window is ready for more input.
//進一步檢查是否需要 drop
reason = checkWindowReadyForMoreInputLocked(currentTime,
mFocusedWindowHandle, entry, "focused");
if (!reason.isEmpty()) { //如果 reason不為空,就drop掉
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
goto Unresponsive;
}
// Success! Output targets.
injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
//找到正確的InputChannel,加入到InputTargets中
addWindowTargetLocked(mFocusedWindowHandle,
InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),inputTargets);
// Done.
Failed:
Unresponsive:
...
return injectionResult;
}
經過findFocusedWindowTargetsLocked后就找到了正確的InputChannel, 然后通過socket通信就將事件傳輸到App端了。
3.4 ANR是怎么發生的?
可以看出ANR發生的源頭是 handleTargetsNotReadyLocked. 從字面上來看應該是InputTargets還沒有Ready,它主要是在3.3中的findFocusedWindowTargetsLocked中調用
情況一:mFocusedApplicationHandle != null, mFocusedWindowHandle== null
情況二:mFocusedApplicationHandle 與 mFocusedWindowHandle都不空的情況
- 當前Focused的window PAUSED了
- Focused的window的 Connection都沒有,也就是還沒有注冊
- Focused的window的 Connection 不正常
- Focused的window的 Connection里塞滿了輸入事件, 還在等著App去finish掉事件
- 針對 KeyEvent情況,必須上一個事件完成了才行
- 針對Touch事件的情況
3.4.1 正常事件的流程
以 Key事件為例, Key事件按下是ACTION_DOWN, 抬起是ACTION_UP, 現在來看下這兩個事件的正常流程如下.
-
先將事件加入到outBoundQueue,然后publishKeyEvent到App Main線程
image.png -
然后立馬將A事件從outboundQueue中剝離,加入到waitQueue中
image.png -
App線程處理完按鍵事件了
App線程會調用nativeFinishInputEvent,進一步調用 sendFinishedSignal 向 SystemServer發送哪個按鍵事件已經被finish, 最后從waitQueue中移出掉事件
image.png
3.4.2 ANR發生
1. 假設 App 在處理 A事件(ACTION_DOWN), 沒有返回。
2. 這時來了一個B事件(ACTION_UP).
這時findFocusedWindowTargetsLocked在checkWindowReadyForMoreInputLocked時發現waitQueue里不為空,這時就要調用handleTargetsNotReadyLocked了
reason = checkWindowReadyForMoreInputLocked(currentTime,
mFocusedWindowHandle, entry, "focused");
if (!reason.isEmpty()) {
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime, reason.string());
goto Unresponsive;
}
int32_t InputDispatcher::handleTargetsNotReadyLocked(...) {
if (applicationHandle == NULL && windowHandle == NULL) {
//一般不會進入該分支,這個情況一般是系統剛啟動或systemserver重啟的情況
} else {
// mInputTargetWaitCause 默認情況下是INPUT_TARGET_WAIT_CAUSE_NONE
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
nsecs_t timeout;
//獲得超時時間,默認是5s
if (windowHandle != NULL) {
timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else if (applicationHandle != NULL) {
timeout = applicationHandle->getDispatchingTimeout(
DEFAULT_INPUT_DISPATCHING_TIMEOUT);
} else {
timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
}
//設置cause為 APPLICATION_NOT_READY狀態
mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
mInputTargetWaitStartTime = currentTime;
//超時時間是dispatch這個事件的時間+5
mInputTargetWaitTimeoutTime = currentTime + timeout;
mInputTargetWaitTimeoutExpired = false;
//重新設置正確的mInputTargetWaitApplicationHandle
mInputTargetWaitApplicationHandle.clear();
if (windowHandle != NULL) {
mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
}
if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
mInputTargetWaitApplicationHandle = applicationHandle;
}
}
}
// 并不會進入,這里顯示mInputTargetWaitTimeoutTime = current + 5s
if (currentTime >= mInputTargetWaitTimeoutTime) {
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
...
} else {
return INPUT_EVENT_INJECTION_PENDING;
}
}
這時候B事件就會設置正確的mInputTargetWaitCause, mInputTargetWaitApplicationHandle, mInputTargetWaitTimeoutTime.
如果隔了5s后,App Main線程還是沒有返回,這時再來了一個C 事件,此時,在handleTargetsNotReadyLocked里就要發生ANR了
int32_t InputDispatcher::handleTargetsNotReadyLocked(...) {
if (applicationHandle == NULL && windowHandle == NULL) {
//一般不會進入該分支,這個情況一般是系統剛啟動或systemserver重啟的情況
} else {
// 此時的mInputTargetWaitCause 是INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
//不會進入該分支
if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
...
}
}
// 由于是隔了5s 左右,此時currentTime 就 大于 mInputTargetWaitTimeoutTime, 這時就要產生ANR了
if (currentTime >= mInputTargetWaitTimeoutTime) {
onANRLocked(currentTime, applicationHandle, windowHandle,
entry->eventTime, mInputTargetWaitStartTime, reason);
...
} else {
return INPUT_EVENT_INJECTION_PENDING;
}
}
3. 假設A事件(ACTION_DOWN)在5s內被consumed了, 那2中就不會發生ANR,那2中的B事件是何時在dispatch出去的呢?
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
...
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
...
if (done) {
if (dropReason != DROP_REASON_NOT_DROPPED) {
dropInboundEventLocked(mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
releasePendingEventLocked();
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
}
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
...
Vector<InputTarget> inputTargets;
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false;
}
如果findFocusedWindowTargetsLocked返回為INPUT_EVENT_INJECTION_PENDING, 那dispatchKeyLocked就直接返回false, 在本例中,此處返回 false.
所以dispatchOnceInnerLocked并不會處理 if (done), 這就意思著不會調用releasePendingEventLocked,進而mPendingEvent也不會被置為NULL, 這樣下一輪dispatchOnceInnerLocked中就會發現mPendingEvent不為NULL, 就繼續dispatch上一次沒有被dispatch出去的Event, 好巧妙。
四、小結
- Input工作于三個線程, android.display, InputReader, InputDispatcher線程
- Input與App的通信是通過socket.
- InputReader使用EventHub里的epoll機制, InputDispatcher使用Looper中的epoll機制
- App在consume掉input事件后,會通過nativeFinishInputEvent去通知Input移出到waitQueue里等待的事件,防止ANR.
- ANR的發生需要三個事件,第一個事件,讓App線程處理,且 App線程不返回, 第二個事件開始計時,默認5s, 第三個事件在5s結束后來到,此時產生ANR