Android开发input问题解决分析

作者:咖啡你冲不冲 时间:2021-11-10 08:58:46 

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

标签:Android,input,问题解决
0
投稿

猜你喜欢

  • MAC上IntelliJ IDEA的svn无法保存密码解决方案

    2022-11-10 13:54:12
  • Android TabLayout 实现底部Tab的示例代码

    2023-09-16 12:21:17
  • Android 九宫格的实现方法

    2022-08-19 01:07:09
  • Android 启动页白屏解决方案

    2023-11-06 02:54:02
  • Android如何快速集成腾讯Bugly

    2021-11-26 00:52:26
  • Android闹钟机制实现定时任务功能

    2021-08-06 07:36:03
  • 关于Eureka的概念作用以及用法详解

    2023-08-23 15:17:29
  • java如何将一个float型数的整数部分和小数分别输出显示

    2022-08-17 16:50:26
  • C# DateTime.Compare()方法案例详解

    2023-08-12 23:41:36
  • java并发包JUC诞生及详细内容

    2022-04-06 02:57:00
  • C#波形图控件制作示例程序

    2022-02-24 21:50:40
  • java实现socket从服务器连续获取消息的示例

    2021-10-22 02:50:55
  • C#中decimal保留2位有效小数的实现方法

    2023-01-30 07:35:13
  • C#仿Windows XP自带的扫雷游戏

    2023-07-30 07:40:48
  • SpringBoot+SpringCache实现两级缓存(Redis+Caffeine)

    2023-10-02 05:20:58
  • Java Fluent Mybatis 项目工程化与常规操作详解流程篇 上

    2022-08-28 16:47:28
  • 动态配置Spring Boot日志级别的全步骤

    2023-01-29 01:57:19
  • Android Studio工程导入及坑的解决

    2022-01-12 20:48:31
  • C# memcached缓存使用实例代码

    2022-01-15 02:17:11
  • Android 实现签到足迹功能

    2023-06-21 15:16:24
  • asp之家 软件编程 m.aspxhome.com