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
列表,用于存储所有注册的监听器。然后,我们提供registerInputEventListener
和unregisterInputEventListener
方法,用于注册和注销监听器。
文件路径: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事件时,我们就可以在InputEventReceiver
的onInputEvent
方法中获取到事件的详细信息。
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事件回调的实现和原理。了解了输入事件的定义和分类,以及输入事件的获取和分发的过程和组件。我还介绍了三种实现输入事件回调的方式,即注册监听器和事件监听广播 , 重写方法,并掌握了它们的原理和优缺点。
希望本文对你有所帮助,如果你有任何疑问或建议,欢迎在评论区留言。谢谢!