Android開發(fā)input問題解決分析
Android Input
Android Input指的是輸入事件,主要是觸摸滑動(dòng),當(dāng)然還包括類似藍(lán)牙外設(shè)的輸入。Input涉及到的主要模塊
- EventHub :對(duì)輸入事件進(jìn)行映射
- InputReader : 收集input事件
- InputDispatcher : 將事件分發(fā)到上層
- InputManager : framework中對(duì)input事件的接收和分發(fā)
- WMS : 管理窗口,收集和分發(fā)input事件
本篇主要以framework的視角來debug input問題,介紹input的資料已經(jīng)很多了,所以不講input傳遞流程和機(jī)制,只看如何去解決問題。
從framework的視角,首先我們要排查input driver的問題,比如從屏幕觸摸輸入的,那就是顯示屏的input驅(qū)動(dòng);如果是藍(lán)牙外設(shè)輸入的,那就需要找BT的驅(qū)動(dòng)層。
adb shell getEvent
然后再輸入,看鍵值是否正常,如果getEvent都沒有收到,就不屬于framework的范疇了。
確定驅(qū)動(dòng)沒有問題之后,就可以通過動(dòng)態(tài)或靜態(tài)開啟debug log。不同廠商的開關(guān)log的命令有些差異,打印log的內(nèi)容也不太一樣。
這里我們直接以本地debug為例,參考Android T版本的common code自己添加關(guān)鍵log,然后開始復(fù)現(xiàn)問題,檢查問題時(shí)間點(diǎn)的log。順便補(bǔ)充一下,可以通過如下命令使時(shí)間顯示到秒,這樣方便復(fù)現(xiàn)問題時(shí)對(duì)應(yīng)log時(shí)間
adb shell settings put secure clock_seconds 1
Step1.查看ViewRootImpl是否有收到input event
/frameworks/base/core/java/android/view/ViewRootImpl.java
@UnsupportedAppUsage void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); if (event instanceof MotionEvent) { MotionEvent me = (MotionEvent) event; if (me.getAction() == MotionEvent.ACTION_CANCEL) { EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Motion - Cancel", getTitle().toString()); } } else if (event instanceof KeyEvent) { KeyEvent ke = (KeyEvent) event; if (ke.isCanceled()) { EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Key - Cancel", getTitle().toString()); } } // Always enqueue the input event in order, regardless of its time stamp. // We do this because the application or the IME may inject key events // in response to touch events and we want to ensure that the injected keys // are processed in the order they were received and we cannot trust that // the time stamp of injected events are monotonic. QueuedInputEvent last = mPendingInputEventTail; if (last == null) { mPendingInputEventHead = q; mPendingInputEventTail = q; } else { last.mNext = q; mPendingInputEventTail = q; } mPendingInputEventCount += 1; Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName, mPendingInputEventCount); //添加log打印關(guān)鍵信息 Log.i(">_<!!","enqueueInputEvent: event = " + event + " ,this = " + this); if (processImmediately) { doProcessInputEvents(); } else { scheduleProcessInputEvents(); } }
這里只需要根據(jù)添加的log查看兩個(gè)參數(shù)即可,event會(huì)打印出來 KeyEvent的action和keyCode,我們需要看下這里的action和keyCode是否有紊亂的情況,如果輸入和get到的不對(duì)應(yīng),那還是需要driver來協(xié)調(diào)。后面打印出來的this就是此ViewRootImpl對(duì)象,具體內(nèi)容可以看它的toString方法。
我們只需要在最終的log中觀察這句是否打印出來,如果打印出來了,說明input事件已經(jīng)成功發(fā)送到應(yīng)用端了,跳過下面步驟,直接檢查Step5,如果沒打印這段log,再看Step2
Step2. 查看inputDispatcher是否有收到input event
/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry, DropReason* dropReason, nsecs_t* nextWakeupTime) { // Preprocessing. if (!entry->dispatchInProgress) { // 這個(gè)是AOSP的log機(jī)制,不用再另外添加log logOutboundKeyDetails("dispatchKey - ", *entry); } void InputDispatcher::logOutboundKeyDetails(const char* prefix, const KeyEntry& entry) { //if (DEBUG_OUTBOUND_EVENT_DETAILS) { if (true) { ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32 ", " "policyFlags=0x%x, action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, " "metaState=0x%x, repeatCount=%d, downTime=%" PRId64, prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId, entry.policyFlags, entry.action, entry.flags, entry.keyCode, entry.scanCode, entry.metaState, entry.repeatCount, entry.downTime); } }
這里AOSP的log已經(jīng)添加的很全面了,我們只需要手動(dòng)將打印條件置為true即可。這段log中同樣可以對(duì)應(yīng)上action和keyCode,不過c++代碼打印出來的是十六進(jìn)制,但是也和上面java code中打印出來的字符串是一一對(duì)應(yīng)的。如果我們最終可以搜索到這段log,說明inputDispatcher已經(jīng)收到input event了,那么直接快進(jìn)到Step4檢查inputDispatcher狀態(tài)是否正常。如果沒有查看到這句log,再看Step3
Step3. 查看inputreader線程里面是否有keycode
/frameworks/native/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
int32_t usageCode) { int32_t keyCode; int32_t keyMetaState; uint32_t policyFlags; if (getDeviceContext().mapKey(scanCode, usageCode, mMetaState, &keyCode, &keyMetaState, &policyFlags)) { keyCode = AKEYCODE_UNKNOWN; keyMetaState = mMetaState; policyFlags = 0; } if (mParameters.handlesKeyRepeat) { policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT; } NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource, getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime); getListener().notifyKey(&args); ALOGI("device: %s, keyCode=%d, scanCode=%d, eventTime = %lld, action=0x%x,duwnTime=%lld",getDeviceName().c_str(), keyCode, scanCode, args,eventTime, args.action. args.downTime); }
KeyboardInputMapper.cpp 是在Android R之后添加的工具,如果是比較舊的版本,需要在InputReader.cpp中添加log。此處可以確定input event被發(fā)送到了inputReader了,這里的值就是從getEvent讀取的,如果getEvent的值是對(duì)的,但這里沒有打印log,就需要打印cpp文件的callstack,看看是流程中哪一步出錯(cuò)。
Step4. 檢查inputDispatcher的狀態(tài)是否正常
可以通過adb命令來查看inputDispatcher的狀態(tài)
adb shell dumpsys input
/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::dumpDispatchStateLocked(std::string& dump) { dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled)); dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen)); dump += StringPrintf(INDENT "InputFilterEnabled: %s\n", toString(mInputFilterEnabled)); dump += StringPrintf(INDENT "FocusedDisplayId: %" PRId32 "\n", mFocusedDisplayId);
DispatcherEnabled 必須為1,并且DispatcherFrozen 必須為0,如果是inputDispatcher狀態(tài)有問題,需要在代碼中查看哪些地方有修改inputDispatcher的狀態(tài)mDispatchEnabled,mDispatchFrozen,找到將修改狀態(tài)的地方來分析問題。如果打印出來的FocusedDisplayId或FocusedApplications不符合預(yù)期,那就是display or WMS相關(guān)問題,與input流程沒有關(guān)系。
Step5. 查看最終input消費(fèi)event的是哪個(gè)頁面
/frameworks/base/core/java/android/view/View.java
public boolean dispatchKeyEvent(KeyEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 0); } Log.i(">_<!!","dispatchKeyEvent event:" + event + " to :" + v); // Give any attached key listener a first crack at the event. //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) { //表明input被消費(fèi)了 Log.i(">_<!!","Event:" + event+ " handle in: " + v + " ,ListenerInfo = " + li.toString()); return true; } if (event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)) { return true; } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
這里的log可以表明input event正在按照view的層級(jí)依次dispatch并最終被哪個(gè)view消費(fèi),如果這個(gè)view并不是所期望的view,那么就需要查看為什么消費(fèi)到這個(gè)view上面了,是layout區(qū)域有透明邊界?還是期望的view并不存在,可能性就很多,細(xì)節(jié)可以再深思下。如果這里的view是符合期望的,那么問題就回到應(yīng)用層了,看應(yīng)用層對(duì)此input事件的響應(yīng)是否有異常。
以上就是Android開發(fā)input問題解決分析的詳細(xì)內(nèi)容,更多關(guān)于Android input問題解決的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android實(shí)現(xiàn)聊天記錄上傳本地服務(wù)器(即時(shí)通訊)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)聊天記錄上傳本地服務(wù)器,即時(shí)通訊功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06自定義視圖view使用Canvas實(shí)現(xiàn)手寫板和涂鴉功能
這篇文章主要介紹了自定義視圖view使用Canvas實(shí)現(xiàn)手寫板和涂鴉功能,這里直接上代碼,里面有詳細(xì)講解和注釋,需要的朋友可以參考下2023-04-04Android自定義DataTimePicker實(shí)例代碼(日期選擇器)
本篇文章主要介紹了Android自定義DataTimePicker實(shí)例代碼(日期選擇器),非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-01-01androidx下的fragment的lazy懶加載問題詳解
這篇文章主要介紹了androidx下的fragment的lazy懶加載問題詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04android listview進(jìn)階實(shí)例分享
這篇文章主要介紹了android listview進(jìn)階實(shí)例分享,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01android sdk安裝及開發(fā)環(huán)境部署
本文給大家詳細(xì)講解了android sdk安裝方法以及android開發(fā)環(huán)境部署方法,非常的細(xì)致全面,有需要的小伙伴務(wù)必詳細(xì)研究下。2015-11-11Android手機(jī)聯(lián)系人帶字母索引的快速查找
這篇文章主要為大家詳細(xì)介紹了Android手機(jī)聯(lián)系人帶字母索引的快速查找實(shí)現(xiàn)方法,感興趣的小伙伴們可以參考一下2016-03-03