Android系统获取event事件回调等几种实现和原理分析

简介: Android系统获取event事件回调等几种实现和原理分析

Android系统,它支持多种输入设备,如触摸屏、键盘、鼠标、遥控器等。Android系统如何处理这些输入设备的事件,并将它们传递给应用程序呢?本文将从源码的角度,Android系统获取event事件回调等几种实现和原理分析。

input系列文章:

Android Input系统(1) Input事件的产生与传递

一、输入事件的定义和分类

在Android系统中,输入事件是指用户通过输入设备与系统或应用程序交互产生的信号,如按键、触摸、滑动等。输入事件是一种抽象的概念,它可以被表示为不同的类,如KeyEvent、MotionEvent、InputEvent等。这些类都继承自InputEvent类,它是所有输入事件的基类,定义了一些通用的属性和方法。

输入事件可以根据不同的维度进行分类,如:

  • 按照来源分类,可以分为物理输入事件和虚拟输入事件。物理输入事件是指由真实的输入设备产生的事件,如按键、触摸屏等。虚拟输入事件是指由系统或应用程序模拟产生的事件,如导航栏、软键盘等。
  • 按照类型分类,可以分为按键事件和动作事件。按键事件是指由按下或松开某个按键产生的事件,如键盘、遥控器等。动作事件是指由触摸或移动某个位置产生的事件,如触摸屏、鼠标等。
  • 按照目标分类,可以分为系统级别的输入事件和应用级别的输入事件。系统级别的输入事件是指由系统处理或拦截的事件,如电源键、音量键、返回键等。应用级别的输入事件是指由应用程序处理或消费的事件,如按钮、文本框、滑动条等。

二、输入事件的获取和分发

Android系统获取和分发输入事件的过程涉及到多个组件和层次,如下图所示:

  • 输入设备:是指能够产生输入事件的硬件设备,如触摸屏、键盘、鼠标等。每个输入设备都有一个唯一的ID和名称,以及一组属性和功能。
  • EventHub:是位于Native层的一个组件,它负责打开和关闭输入设备,并从Linux内核读取原始的event数据,并将其封装成RawEvent结构体。
  • InputReader:是位于Native层的一个组件,它负责接收EventHub传递过来的RawEvent,并对其进行解析和转换,生成InputEvent对象,并将其发送给InputDispatcher。
  • InputDispatcher:是位于Native层的一个组件,它负责接收InputReader传递过来的InputEvent,并对其进行筛选和分发,根据不同的策略将其发送给不同的Window或应用程序。
  • InputManagerService:是位于Java层的一个服务,它负责管理InputDispatcher,并提供一些接口供其他组件调用,如注册或注销监听器、设置或获取过滤器等。
  • WindowManagerService:是位于Java层的一个服务,它负责管理Window和视图层次,并与InputDispatcher进行交互,提供一些回调方法供InputDispatcher调用,如拦截或分发输入事件等。
  • ViewRootImpl:是位于Java层的一个类,它负责连接Window和View,并从InputDispatcher接收输入事件,并将其传递给View。
  • View:是位于Java层的一个类,它是所有视图的基类,它负责处理或消费输入事件,并根据需要进行响应或反馈。

三、输入事件回调的实现和原理

在Android系统中,有两种方式可以实现输入事件回调,即注册监听器和重写方法。下面以按键事件为例,分析这两种方式的实现和原理。

方案1. 注册监听器

在Android系统中,我们有时需要获取到系统的event事件,例如键盘的按键事件,触摸屏的触摸事件等。这篇文章将详细介绍如何在Android系统中获取event事件,并实现回调。

1. 创建事件接收器接口

需要创建一个事件接收器接口,该接口定义了一个onInputEvent方法,用于接收事件的详细信息,包括键码、动作、进程ID和用户ID。

文件路径:frameworks/base/core/java/android/xxx/IInputEventReceiver.aidl

package android.xxx;
interface IInputEventReceiver {
    void onInputEvent(int keycode, int action, int pid, int uid);
}
2. 实现事件接收器

需要实现这个接口。在这个实现中,我们将事件的详细信息打印出来。

文件路径:frameworks/base/core/java/android/btf/InputEventReceiver.java

package android.xxx;
import android.util.Log;
public class InputEventReceiver extends IInputEventReceiver.Stub {
    @Override
    public void onInputEvent(int keycode, int action, int pid, int uid) {
        Log.d("ln28","keycode:"+keycode + ", action:"+action + ", pid:"+pid+", uid:"+uid);
    }
}
3. 在InputManagerService中注册事件监听器

需要在InputManagerService中注册事件监听器。我们首先创建一个inputEventListeners列表,用于存储所有注册的监听器。然后,我们提供registerInputEventListenerunregisterInputEventListener方法,用于注册和注销监听器。

文件路径:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

private List<IInputEventListener> inputEventListeners = new ArrayList<>();
@Override
public void registerInputEventListener(IInputEventListener listener) {
    inputEventListeners.add(listener);
}
@Override
public void unregisterInputEventListener(IInputEventListener listener) {
    inputEventListeners.remove(listener);
}

interceptKeyBeforeDispatching方法中,我们获取到事件的详细信息,然后遍历所有注册的监听器,调用其onInputEvent方法。

private long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
    // 获取键码、动作、PID和UID等信息
    int keyCode = event.getKeyCode();
    int action = event.getAction();
    int pid = Binder.getCallingPid();
    int uid = Binder.getCallingUid();
    // 遍历所有注册的监听器,调用其回调方法
    for (IInputEventListener listener : inputEventListeners) {
        try {
            listener.onInputEvent(keyCode, action, pid, uid);
        } catch (RemoteException e) {
            // 处理异常情况
            e.printStackTrace();
        }
    }
    return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
}
4. 在SystemBootAppService中注册事件接收器

需要在SystemBootAppService中注册事件接收器。我们首先创建一个InputEventReceiver实例,并将其注册到InputManagerService中。

文件路径:frameworks/base/services/core/java/com/android/server/SystemBootAppService.java(我自己加的 ,你们根据实际情况 搞到自己的系统服务或者调用地方)

//private IInputEventListener listener;
//private IInputManager inputManager;
//private IInputEventReceiver receiver;
receiver = new InputEventReceiver();
listener = new IInputEventListener.Stub() {
    @Override
    public void onInputEvent(int keycode, int action, int pid, int uid) throws RemoteException {
        receiver.onInputEvent(keycode, action, pid, uid);
    }
};
inputManager = IInputManager.Stub.asInterface(ServiceManager.getService(Context.INPUT_SERVICE));
if (inputManager != null) {
    try {
        inputManager.registerInputEventListener(listener);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
} else {
    Log.e("SystemBootAppService", "Failed to get INPUT_SERVICE");
}

当系统产生event事件时,我们就可以在InputEventReceiveronInputEvent方法中获取到事件的详细信息。

D/ln28: keycode:24, action:0, pid:1000, uid:1000
D/ln28: keycode:24, action:1, pid:1000, uid:1000
D/ln28: keycode:25, action:0, pid:1000, uid:1000
D/ln28: keycode:25, action:1, pid:1000, uid:1000

这种方式的原理是:

  • 当用户按下或松开某个按键时,输入设备会向Linux内核发送一个event数据,包含了按键的代码、动作、时间等信息。
  • EventHub会从Linux内核读取event数据,并将其封装成RawEvent结构体,并发送给InputReader。
  • InputReader会根据输入设备的类型和属性,将RawEvent转换成KeyEvent对象,并发送给InputDispatcher。
  • InputDispatcher会根据不同的策略,将KeyEvent分发给不同的Window或应用程序。同时,它也会遍历所有注册的IInputEventListener,调用其onInputEvent方法,将KeyEvent的信息传递给它们。
  • IInputEventListener的实现类会在onInputEvent方法中接收KeyEvent的信息,并根据需要进行处理或响应。例如,我们可以在这里打印日志、启动服务、发送广播等。

方案2. 事件监听广播

我之前写过一篇调试 Android Mstar增加IR 自定义遥控头码完整调试过程

这里有讲解 请看

5.3 调试系统层映射是否正确
5.4 拦截系统按键和发送特定按键广播
5.5 app增加注册广播接收framework发送的按键值

方案3. 重写方法

重写方法是一种简单的方式,它可以在Activity或View中实现输入事件回调,只需要重写一个方法并返回true即可。例如,我们可以在MainActivity中重写onKeyDown方法,用于接收按键事件,并根据按键的代码进行不同的操作。具体的代码如下:

// 重写onKeyDown方法
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    // 根据按键的代码进行不同的操作
    switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_UP:
            // 增加音量
            adjustVolume(AudioManager.ADJUST_RAISE);
            return true;
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            // 减少音量
            adjustVolume(AudioManager.ADJUST_LOWER);
            return true;
        case KeyEvent.KEYCODE_BACK:
            // 退出应用
            finish();
            return true;
        default:
            // 调用父类的方法
            return super.onKeyDown(keyCode, event);
    }
}

这种方式的原理是:

  • 当用户按下或松开某个按键时,输入设备会向Linux内核发送一个event数据,包含了按键的代码、动作、时间等信息。
  • EventHub会从Linux内核读取event数据,并将其封装成RawEvent结构体,并发送给InputReader。
  • InputReader会根据输入设备的类型和属性,将RawEvent转换成KeyEvent对象,并发送给InputDispatcher。
  • InputDispatcher会根据不同的策略,将KeyEvent分发给不同的Window或应用程序。如果KeyEvent是系统级别的,则由WindowManagerService处理或拦截,如电源键、音量键、返回键等。
  • WindowManagerService处理或拦截,如电源键、音量键、返回键等。如果KeyEvent是应用级别的,则由ViewRootImpl接收并传递给View。
  • ViewRootImpl会根据KeyEvent的目标Window和焦点View,将KeyEvent传递给相应的View。
  • View会根据KeyEvent的动作和代码,调用相应的方法进行处理或消费,如onKeyDown、onKeyUp等。如果View重写了这些方法,并返回true,则表示KeyEvent被消费,不再向上层传递。如果View没有重写这些方法,或者返回false,则表示KeyEvent未被消费,继续向上层传递,直到被消费或者到达最顶层。

四、细节注意

  • 方案1 : 适合系统源码任意位置使用 , 比如自定义Server 系统Server , 或者系统源码其他位置。
  • 方案2: 适合给客户app使用 , 让客户App监听注册个广播 系统收到事件就通过广播发出去 , 同时也兼容Server。
  • 方案3: 标准的搞法 适合App Activity用, Server的话重载不了方法 拿不到事件。

五、总结

本文从源码的角度,分析了Android系统获取event事件回调的实现和原理。了解了输入事件的定义和分类,以及输入事件的获取和分发的过程和组件。我还介绍了三种实现输入事件回调的方式,即注册监听器和事件监听广播 , 重写方法,并掌握了它们的原理和优缺点。

希望本文对你有所帮助,如果你有任何疑问或建议,欢迎在评论区留言。谢谢!

相关文章
|
10天前
|
Java Android开发
Android系统 修改无源码普通应用为默认Launcher和隐藏Settings中应用信息图标
Android系统 修改无源码普通应用为默认Launcher和隐藏Settings中应用信息图标
23 0
|
9天前
|
监控 物联网 数据处理
构建未来:基于Android的智能物联网家居系统
【4月更文挑战第24天】 随着物联网技术的飞速发展,智能家居系统作为其重要分支之一,正逐渐渗透到千家万户。本文将详细探讨如何利用Android平台的灵活性和开放性,设计并实现一个高效、可靠的智能物联网家居系统。我们将从系统的架构设计出发,深入分析关键技术点,包括设备通信协议、用户界面设计以及后台数据处理等方面,并展示通过Android设备进行实时控制和监控的实用性。文章不仅聚焦于技术细节,还将对未来发展趋势及潜在挑战进行前瞻性讨论。
11 0
|
10天前
|
Java Shell Android开发
Android构建系统:Android.mk(3)条件控制详解
Android构建系统:Android.mk(3)条件控制详解
15 1
|
10天前
|
存储 Android开发 开发者
Android构建系统:Android.mk(1)基础概念赋值变量引用详解
Android构建系统:Android.mk(1)基础概念赋值变量引用详解
18 0
|
10天前
|
Android开发
Android构建系统:Android.mk(2)函数详解
Android构建系统:Android.mk(2)函数详解
15 1
|
10天前
|
存储 Java API
Android系统 文件访问权限笔记
Android系统 文件访问权限笔记
44 1
|
10天前
|
移动开发 Java Unix
Android系统 自动加载自定义JAR文件
Android系统 自动加载自定义JAR文件
33 1
|
10天前
|
XML 存储 Android开发
Android系统 添加动态控制USB TP触摸方向、触摸唤醒
Android系统 添加动态控制USB TP触摸方向、触摸唤醒
24 0
|
10天前
|
XML Java Android开发
Android系统 添加动态控制屏幕方向、强制APP横竖屏方向
Android系统 添加动态控制屏幕方向、强制APP横竖屏方向
28 1
|
10天前
|
XML 存储 测试技术
Android系统 添加动态控制SystemUI状态栏、导航栏和下拉菜单
Android系统 添加动态控制SystemUI状态栏、导航栏和下拉菜单
23 1