Android开发input问题解决分析

目录
  • Android Input
    • Step1.查看ViewRootImpl是否有收到input event
    • Step2. 查看inputDispatcher是否有收到input event
    • Step3. 查看inputreader线程里面是否有keycode
    • Step4. 检查inputDispatcher的状态是否正常
    • Step5. 查看最终input消费event的是哪个页面

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事件的响应是否有异常。

以上就是Android开发input问题解决分析的详细内容,更多关于Android input问题解决的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android WebView支持input file启用相机/选取照片功能

    webview要调起input-file拍照或者选取文件功能,可以在webview.setWebChromeClient方法中重写指定的方法,来拦截webview的input事件,并做我们相应的操作. Android代码 webView.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { if (newProgress

  • Android开发InputManagerService创建与启动流程

    目录 前言 启动流程 创建输入系统 启动输入系统 输入系统就绪 结束 前言 之前写过几篇关于输入系统的文章,但是还没有写完,后来由于工作的变动,这个事情就一直耽搁了.而现在,在工作中,遇到输入系统相关的事情也越来越多,其中有一个非常有意思的需求,因此是时候继续分析 InputManagerService. InputManagerService 系统文章,基于 Android 12 进行分析. 本文将以 IMS 简称 InputManagerService. 启动流程 InputManagerS

  • Android开发EditText禁止输入监听及InputFilter字符过滤

    目录 监听事件 InputFilter 监听事件 setOnEditorActionListener:软键盘回车监听事件 testEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { Log.e("TAG", "on

  • Android开发Input系统触摸事件分发

    目录 引言 1. InputDispatcher 收到触摸事件 1.1 截断策略查询 2. InputDispatcher 分发触摸事件 2.1 寻找触摸的窗口 2.1.1 根据坐标找到触摸窗口 2.1.2 保存窗口 结束 引言 Input系统: InputReader 处理触摸事件 分析了 InputReader 对触摸事件的处理流程,最终的结果是把触摸事件包装成 NotifyMotionArgs,然后分发给下一环.根据 Input系统: InputManagerService的创建与启动 可

  • Android开发手册TextInputLayout样式使用示例

    目录 前言 布局代码 属性介绍 前言 前面小空带同学们学了EditText控件,又用其实践做了个验证码功能,以为这就完了吗? 然而并没有. Android在5.0以后引入了Materia Design库的设计,现在又有了Jetpack UI库的设计.帮助开发者更高效的实现炫酷的UI界面,降低开发门槛. Jetpack我们后面再说,承接之前的EditText,先说说Materia Design里的TextInputLayout. 使用方式是将TextInputEditText或EditText套到

  • Android audio音频流数据异常问题解决分析

    目录 一.背景 二.Android Audio 音频系统 1. 音频链路 2. 音频链路关键节点: 3. 音频库的选择 三.案例分析 1. 声音忽大忽小问题 具体分析 2. 应用卡顿问题 具体分析 四.总结 一.背景 在 Android 系统的开发过程当中,音频异常问题通常有如下几类,无声,调节不了声音,爆音,声音卡顿,声音效果异常(忽大忽小,低音缺失等)等. 尤其声音效果这部分问题通常从日志上信息量较少,相对难定位根因.想要分析此类问题,便需要对声音传输链路有一定的了解,能够在链路中对各节点的

  • android开发socket编程之udp发送实例分析

    本文实例讲述了android开发socket编程之udp发送实现方法.分享给大家供大家参考.具体分析如下: 需要实现的功能:采用udp下的socket编程,当按下确认键,模拟器发送文本框数据,pc机上的网络调试助手接收 一.环境: win7 + eclipse + sdk 二.代码: package test.soket; //import com.test_button.R; import java.io.DataOutputStream; import java.io.IOException

  • Android开发中include控件用法分析

    本文实例讲述了Android开发中include控件用法.分享给大家供大家参考,具体如下: 我们知道,基于Android系统的应用程序的开发,界面设计是非常重要的,它关系着用户体验的好坏.一个好的界面设计,不是用一个xml布局就可以搞定的.当一个activity中的控件非常多的时候,所有的布局文件都放在一个xml文件中,很容易想象那是多么糟糕的事情!笔者通过自身的经历,用include控件来解决这个问题,下面是一个小例子,仅仅实现的是布局,没有响应代码的设计. user.xml文件内容如下: <

  • Android 开发手机(三星)拍照应用照片旋转问题解决办法

    Android 开发手机(三星)拍照应用照片旋转问题解决办法 最近解决了一个令我头疼好久的问题,就是三星手机拍照图片旋转的问题,项目中有上传图片的功能,那么涉及到拍照,从相册中选择图片,别的手机都ok没有问题,唯独三星的手机拍照之后,你会很清楚的看到会把照片旋转一下,然后你根据路径找到的图片就是已经被旋转的了,解决办法终于被我找到了.我们可以根据图片的路径读取照片exif(Exchangeable Image File 可交换图像文件)信息中的旋转角度 根据调试,可以清楚的发现三星手机拍照的图片

  • Android开发笔记之图片缓存、手势及OOM分析

    把图片缓存.手势及OOM三个主题放在一起,是因为在Android应用开发过程中,这三个问题经常是联系在一起的.首先,预览大图需要支持手势缩放,旋转,平移等操作:其次,图片在本地需要进行缓存,避免频繁访问网络:最后,图片(Bitmap)是Android中占用内存的大户,涉及高清大图等处理时,内存占用非常大,稍不谨慎,系统就会报OOM错误. 庆幸的是,这三个主题在Android开发中属于比较普遍的问题,有很多针对于此的通用的开源解决方案.因此,本文主要说明笔者在开发过程中用到的一些第三方开源库.主要

  • Android开发中PopupWindow用法实例分析

    本文实例分析了Android开发中PopupWindow用法.分享给大家供大家参考,具体如下: private TextView tv_appmanager_title; private ListView lv_app_manager; private LinearLayout ll_appmanager_loading; private AppManagerProvider provider; private List<AppManagerInfo> infos ; private AppM

  • Android开发中Widget的生命周期实例分析

    本文实例分析了Android开发中Widget的生命周期.分享给大家供大家参考,具体如下: Widget是android中桌面小控件,创建时必须继承AppWidgetProvider,AppWidgetProvider其实就是继承了BroadcastReceiver的Receiver的一种,widget有以下几个生命周期方法: 1.onEnabled方法:此方法在Widget第一次被创建的时候调用,并且只调用一次,此方法中常放入初始化数据,服务的操作. 2.onReceive方法:通Broadc

  • Android开发中Intent传递对象的方法分析

    本文实例分析了Android开发中Intent传递对象的方法.分享给大家供大家参考,具体如下: 方法一: 利用方法:public Intent putExtra (String name, Parcelable value)传递一个Parceable的参数,此方法的参数被序列化到内存. 利用方法:public Intent putExtra (String name, Serializable value)传递一个实现了序列化接口类的对象,此方法的实参被序列化到磁盘. 方法二: 把数据存放到应用

  • Android 开发中线程的分析

    Android 开发中线程的分析 今天早上把公司给的任务做完了之后,突然就有点无聊,于是,把以前学的那些东西翻了翻,博客看了看,就看到一个关于线程的博客,有了很大的争议,我也差点误解了(感觉高大上~~~).整体代码差不多是这样: package sw.angel.thread; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.util.Log; pub

随机推荐