Android编程输入事件流程详解

本文实例讲述了Android编程输入事件流程。分享给大家供大家参考,具体如下:

EventHub对输入设备进行了封装。输入设备驱动程序对用户空间应用程序提供一些设备文件,这些设备文件放在/dev/input里面。

EventHub扫描/dev/input下所有设备文件,并打开它们。

bool EventHub::openPlatformInput(void)
{
...
  mFDCount = 1;
  mFDs = (pollfd *)calloc(1, sizeof(mFDs[0]));
  mDevices = (device_t **)calloc(1, sizeof(mDevices[0]));
  mFDs[0].events = POLLIN;
  mDevices[0] = NULL;
  res = scan_dir(device_path);
...
  return true;
}

EventHub对外提供了一个函数用于从输入设备文件中读取数据。

bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
    int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
    int32_t* outValue, nsecs_t* outWhen)
    {
     ...
      while(1) {
    // First, report any devices that had last been added/removed.
    if (mClosingDevices != NULL) {
      device_t* device = mClosingDevices;
      LOGV("Reporting device closed: id=0x%x, name=%s\n",
         device->id, device->path.string());
      mClosingDevices = device->next;
      *outDeviceId = device->id;
      if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
      *outType = DEVICE_REMOVED;
      delete device;
      return true;
    }
    if (mOpeningDevices != NULL) {
      device_t* device = mOpeningDevices;
      LOGV("Reporting device opened: id=0x%x, name=%s\n",
         device->id, device->path.string());
      mOpeningDevices = device->next;
      *outDeviceId = device->id;
      if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
      *outType = DEVICE_ADDED;
      return true;
    }
    release_wake_lock(WAKE_LOCK_ID);
    pollres = poll(mFDs, mFDCount, -1);
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
    if (pollres <= 0) {
      if (errno != EINTR) {
        LOGW("select failed (errno=%d)\n", errno);
        usleep(100000);
      }
      continue;
    }
    for(i = 1; i < mFDCount; i++) {
      if(mFDs[i].revents) {
        LOGV("revents for %d = 0x%08x", i, mFDs[i].revents);
        if(mFDs[i].revents & POLLIN) {
          res = read(mFDs[i].fd, &iev, sizeof(iev));
          if (res == sizeof(iev)) {
            LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d",
               mDevices[i]->path.string(),
               (int) iev.time.tv_sec, (int) iev.time.tv_usec,
               iev.type, iev.code, iev.value);
            *outDeviceId = mDevices[i]->id;
            if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
            *outType = iev.type;
            *outScancode = iev.code;
            if (iev.type == EV_KEY) {
              err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
              LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d\n",
                iev.code, *outKeycode, *outFlags, err);
              if (err != 0) {
                *outKeycode = 0;
                *outFlags = 0;
              }
            } else {
              *outKeycode = iev.code;
            }
            *outValue = iev.value;
            *outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);
            return true;
          } else {
            if (res<0) {
              LOGW("could not get event (errno=%d)", errno);
            } else {
              LOGE("could not get event (wrong size: %d)", res);
            }
            continue;
          }
        }
      }
    }
 ...
}

对于按键事件,调用mDevices[i]->layoutMap->map进行映射。映射实际是由 KeyLayoutMap::map完成的,KeyLayoutMap类里读取配置文件qwerty.kl,由配置文件qwerty.kl决定键值的映射关系。你可以通过修改./development/emulator/keymaps/qwerty.kl来改变键值的映射关系。

JNI函数

在frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp文件中,向JAVA提供了函数android_server_KeyInputQueue_readEvent,用于读取输入设备事件。

static jboolean
android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz, jobject event)
{
  gLock.lock();
  sp hub = gHub;
  if (hub == NULL) {
    hub = new EventHub;
    gHub = hub;
  }
  gLock.unlock();
  int32_t deviceId;
  int32_t type;
  int32_t scancode, keycode;
  uint32_t flags;
  int32_t value;
  nsecs_t when;
  bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode, &flags, &value, &when);
  env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);
  env->SetIntField(event, gInputOffsets.mType, (jint)type);
  env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);
  env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);
  env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);
  env->SetIntField(event, gInputOffsets.mValue, value);
  env->SetLongField(event, gInputOffsets.mWhen, (jlong)(nanoseconds_to_milliseconds(when)));
  return res;
}

readEvent调用hub->getEvent读了取事件,然后转换成JAVA的结构。

事件中转线程

在frameworks/base/services/java/com/android/server/KeyInputQueue.java里创建了一个线程,它循环的读取事件,然后把事件放入事件队列里。

Thread mThread = new Thread("InputDeviceReader") {
  public void run() {
      android.os.Process.setThreadPriority(
          android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
        try {
        RawInputEvent ev = new RawInputEvent();
        while (true) {
          InputDevice di;
        readEvent(ev);
        send = preprocessEvent(di, ev);
          addLocked(di, curTime, ev.flags, ..., me);
        }
    }
  };
}

输入事件分发线程

在frameworks/base/services/java/com/android/server/WindowManagerService.java里创建了一个输入事件分发线程,它负责把事件分发到相应的窗口上去。

mQueue.getEvent
dispatchKey/dispatchPointer/dispatchTrackball

更多关于Android相关内容感兴趣的读者可查看本站专题:《Android开发入门与进阶教程》、《Android调试技巧与常见问题解决方法汇总》、《Android多媒体操作技巧汇总(音频,视频,录音等)》、《Android基本组件用法总结》、《Android视图View技巧总结》、《Android布局layout技巧总结》及《Android控件用法总结》

希望本文所述对大家Android程序设计有所帮助。

(0)

相关推荐

  • 分析Android中应用的启动流程

    前言 在我们开始之前,希望您能最好已经满足以下条件: 1.有一份编译后的Android源码(亲自动手实践才会有更深入的理解) 2.对Binder机制有一定的了解 本文启动流程分析基于Android 5.1的源码.为什么是5.1的源码呢?因为手边编译完的代码只有这个版本-另外,用什么版本的源码并不重要,大体的流程并无本质上的区别,仅仅是实现细节的调整,找一个你熟悉的版本就好. 1.启动时序图 作为一个轻微强迫症的人,整理的时序图,相信大家按图索骥,一定能搞明白整个启动流程: 说明:为了让大家更清楚

  • Android Bluetooth蓝牙技术使用流程详解

    在上篇文章给大家介绍了Android Bluetooth蓝牙技术初体验相关内容,感兴趣的朋友可以点击了解详情. 一:蓝牙设备之间的通信主要包括了四个步骤 设置蓝牙设备 寻找局域网内可能或者匹配的设备 连接设备 设备之间的数据传输 二:具体编程实现 1. 启动蓝牙功能 首先通过调用静态方法getDefaultAdapter()获取蓝牙适配器BluetoothAdapter,如果返回为空,则无法继续执行了.例如: BluetoothAdapter mBluetoothAdapter = Blueto

  • Android Mms之:接收信息流程(图文详解)

    信息的接收工作是由底层来完成的,当有一个 新的信息时底层完成接收后会以Intent的方式来通知上层应用,信息的相关内容也包含在Intent当中,Android所支持的信息Intent都定义在android.provider.Telephony.Intents里面.短信的接收 短信接收,对于上层应用程序来讲就是要处理广播事件SMS_RECEIVED_ACTION,它是由Frameworks发出告诉上层有新的SMS已收到.在Mms中,是由PrivilegedSmsReceiver来处理,它收到SMS

  • Android中打电话的数据流程分析

    1.所有流程的起点是从拨号后按下拨号键开始,此步的代码在/android sourcecode/packages/Contacts/src/com/android/contacts/目录的TwelveKeyDialer.java文件中,相关代码如下: 复制代码 代码如下: dialButtonPressed() { ......... final String number = mDigits.getText().toString(); startActivity(newDialNumberIn

  • Android APP启动方式、启动流程及启动优化分析

    本文章向大家介绍Android app应用启动的一些相关知识,包括app启动方式.app启动流程和app启动优化等知识!  app应用启动方式 1.冷启动 当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动.冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量.布局.绘制),最后显示在界面上. 2.热启动 当启动应用时,后台已有该应用的进程(例:按

  • Android系统关机的全流程解析

    在PowerManager的API文档中,给出了一个关机/重启接口: public void reboot (String reason) 对于这个接口的描述很简单,就是几句话. 接口的作用就是重启设备,而且,就算重启成功了也没有返回值. 需要包含REBOOT权限,也就是android.permission.REBOOT 唯一参数reason代表需要的特定重启模式,比如recovery,当然也可以为null. 一.上层空间 1.frameworks/base/core/java/android/

  • Android教程之开机流程全面解析

    本文详细讲述了Android的开机流程.分享给大家供大家参考,具体如下: 开机过程中无线模块的初始化过程;如果sim卡锁开启,或者pin被锁住的时候,会要求输入pin或者puk,但是这个解锁动作必须在系统初始化完成以后才能进行.(图形系统都还没有初始化怎么输入密码阿?)当系统初始化完成以后会调用 wm.systemReady()来通知大家.这时候该做什么就做什么. 开机过程中无线模块的初始化过程: rild 调用参考实现 Reference-ril.c (hardware\ril\referen

  • 从源码分析Android的Glide库的图片加载流程及特点

    0.基础知识 Glide中有一部分单词,我不知道用什么中文可以确切的表达出含义,用英文单词可能在行文中更加合适,还有一些词在Glide中有特别的含义,我理解的可能也不深入,这里先记录一下. (1)View: 一般情况下,指Android中的View及其子类控件(包括自定义的),尤其指ImageView.这些控件可在上面绘制Drawable (2)Target: Glide中重要的概念,目标.它即可以指封装了一个View的Target(ViewTarget),也可以不包含View(SimpleTa

  • Android Mms之:短信发送流程(图文详解)

    信息的发送,对于Mms应用程序来讲主要就是在信息数据库中创建并维护一条信息记录,真正的发送过程交由底层(Frameworks层)函数来处理. 总体的来讲,当信息创建完成后,对于信息通常有三个去处,一个是放弃这个信息,也就是用户不想要此信息,一旦选择,信息将不会被保存:第二个去处就是保存为草稿:最后一个去处就是发送此信息. 当点击了发送后,UI层暂不会有变化,UI层要监听负责发送的各个类的回调信息和数据库的变化信息来更新UI.信息发送的第一站是WorkingMessage,它会先处理一下信息的相关

  • Android 2.3 拨号上网流程从源码角度进行分析

    通常,如果我们想使用SIM卡拨号上网功能,我们要在设置中进行简单的配置,步骤如下: 设置 ->无线和网络 ->移动网络 ->(已启用数据/数据漫游/接入点名称/仅使用2G网络/网络运营商) 我们必须选中其中的"已启用数据"选项,然后配置接入点名称后就可以上网了,当然有的设置中已经根据你的SIM卡类型默认设置了接入点,这时候你只选择"已启用数据"项后就可以完成上网功能设置. 这些设置步骤究竟做了哪些事情呢?我们现在就从源码的角度进行分析. 1. 首先

随机推荐