網站首頁 編程語言 正文
Android Input
Android Input指的是輸入事件,主要是觸摸滑動,當然還包括類似藍牙外設的輸入。Input涉及到的主要模塊
- EventHub :對輸入事件進行映射
- InputReader : 收集input事件
- InputDispatcher : 將事件分發到上層
- InputManager : framework中對input事件的接收和分發
- WMS : 管理窗口,收集和分發input事件
本篇主要以framework的視角來debug input問題,介紹input的資料已經很多了,所以不講input傳遞流程和機制,只看如何去解決問題。
從framework的視角,首先我們要排查input driver的問題,比如從屏幕觸摸輸入的,那就是顯示屏的input驅動;如果是藍牙外設輸入的,那就需要找BT的驅動層。
adb shell getEvent
然后再輸入,看鍵值是否正常,如果getEvent都沒有收到,就不屬于framework的范疇了。
確定驅動沒有問題之后,就可以通過動態或靜態開啟debug log。不同廠商的開關log的命令有些差異,打印log的內容也不太一樣。
這里我們直接以本地debug為例,參考Android T版本的common code自己添加關鍵log,然后開始復現問題,檢查問題時間點的log。順便補充一下,可以通過如下命令使時間顯示到秒,這樣方便復現問題時對應log時間
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打印關鍵信息
Log.i(">_<!!","enqueueInputEvent: event = " + event + " ,this = " + this);
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
這里只需要根據添加的log查看兩個參數即可,event會打印出來 KeyEvent的action和keyCode,我們需要看下這里的action和keyCode是否有紊亂的情況,如果輸入和get到的不對應,那還是需要driver來協調。后面打印出來的this就是此ViewRootImpl對象,具體內容可以看它的toString方法。
我們只需要在最終的log中觀察這句是否打印出來,如果打印出來了,說明input事件已經成功發送到應用端了,跳過下面步驟,直接檢查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) {
// 這個是AOSP的log機制,不用再另外添加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已經添加的很全面了,我們只需要手動將打印條件置為true即可。這段log中同樣可以對應上action和keyCode,不過c++代碼打印出來的是十六進制,但是也和上面java code中打印出來的字符串是一一對應的。如果我們最終可以搜索到這段log,說明inputDispatcher已經收到input event了,那么直接快進到Step4檢查inputDispatcher狀態是否正常。如果沒有查看到這句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被發送到了inputReader了,這里的值就是從getEvent讀取的,如果getEvent的值是對的,但這里沒有打印log,就需要打印cpp文件的callstack,看看是流程中哪一步出錯。
Step4. 檢查inputDispatcher的狀態是否正常
可以通過adb命令來查看inputDispatcher的狀態
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狀態有問題,需要在代碼中查看哪些地方有修改inputDispatcher的狀態mDispatchEnabled,mDispatchFrozen,找到將修改狀態的地方來分析問題。如果打印出來的FocusedDisplayId或FocusedApplications不符合預期,那就是display or WMS相關問題,與input流程沒有關系。
Step5. 查看最終input消費event的是哪個頁面
/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被消費了
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的層級依次dispatch并最終被哪個view消費,如果這個view并不是所期望的view,那么就需要查看為什么消費到這個view上面了,是layout區域有透明邊界?還是期望的view并不存在,可能性就很多,細節可以再深思下。如果這里的view是符合期望的,那么問題就回到應用層了,看應用層對此input事件的響應是否有異常。
原文鏈接:https://juejin.cn/post/7168870464301826055
相關推薦
- 2023-12-16 IDEA中設置遠程調試服務器上的程序
- 2022-08-13 element-ui 動態級聯選擇器Cascader
- 2022-04-09 解決使用docker權限不足需要加sudo問題,Got permission denied whil
- 2023-02-18 減少react組件不必要的重新渲染實現方法_React
- 2022-05-29 解決Docker容器下不能使用vim命令的問題_docker
- 2022-01-22 C 語言中一些重要關鍵字
- 2022-09-23 React深入了解原理_React
- 2022-10-15 Python?UnicodedecodeError編碼問題解決方法匯總_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支