Android输入事件处理过程分析 & 锁屏方法
![此博文包含图片](https://i-blog.csdnimg.cn/blog_migrate/a4c26d1e5885305701be709a3d33442f.gif)
标签: it |
引子:WaveSecure是个安全软件,它拿到了ADC2的全球所有类别的第三名。猜测 其 锁屏 功能的实现原理,可能涉及到以下的内容。
参考词:远程锁屏 手机防盗
====================================================================
以下代码尝试 截获 HOME 键,但没有效果:
@Override
public void onCreate(Bundle savedInstanceState) {
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
}
但是以下代码是可以禁止HOME键对应用的影响(需API 5以上):
注:HOME键会被android framework处理,除非程序的window是TYPE_KEYGUARD类型。
android.permission.DISABLE_KEYGUARD 是说你是否有权限禁止keyguard,比如你现在keyguard打开,就是说你需要输入密码才能进入系统,那么来电话了怎么办,等你输入完密码,人家都等不及都挂了,因此Phone程序可以在有电话的时候禁止keyguard,直接弹出来接电话的界面。等接完电话再恢复keyguard。应用程序Phone的InCallScreen.java文件的OnCreate()中有如下代码:
另外可参考WindowManagerService.java和PhoneWindowManager.java。
===============================================================
补充:
在写程序时,需要捕获KEYCODE_HOME、KEYCODE_ENDCALL、KEYCODE_POWER这几个按键,但是这几个按键系统做了特殊处理,在进行dispatch之前做了一些操作,HOME除了Keygaurd之外,不分发给任何其它App;ENDCALL 和POWER也类似,所以需要我们 ‘在系统处理之前’ 进行处理。
可选的做法是自己定义一个FLAG,在自己的程序中添加此FLAG,然后在 frameworks/base/services/java/com/android/server/WindowManagerService.java 中获取当前窗口的FLAG属性;如果是我们自己设置的那个FLAG,则不进行特殊处理,直接分发按键消息到我们的APP当中,由APP自己处理。
这部分代码最好添加在
方法中,这个方法是KeyInputQueue中的一个虚函数,在处理按键事件之前进行“预处理”。WindowManagerService.java中有KeyInputQueue的实现类:
Ref: 对HOME键的处理参考frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java中的interceptKeyTi()方法。
===============================================================
(ref
框架总览(from
设备文件的打开
[文件frameworks/base/libs/ui/EventHub.cpp] 在系统启动后,android 会构建一个EvnetHub,并通过它进行所有输入事件的集中:
在函数EventHub::openPlatformInput()中,EventHub会通过Linux的系统调用inotify_add_watch()来监视此目录下的设备文件的变化,然后调用scan_dir(device_path)和open_device(const
{
}
打开设备的时候,如果 device->classes & CLASS_KEYBOARD 不等于 0, 则表明是它是个键盘设备。
输入设备的类型定义为(见frameworks/base/include/ui/EventHub.h):
enum {
};
对于生产厂商自定义的键盘,可通过上面的 ioctl 获得其设备名称,命令字 EVIOCGNAME 的定义在文件kernel/include/linux/input.h 中:
在内核键盘驱动文件 drivers/input/keyboard/pxa27x_keypad.c 中定义了设备名称:pxa27x-keypad
static struct platform_driver pxa27x_keypad_driver = {
};
前面函数open_device()中的ANDROID_ROOT 为环境变量,在android的命令模式下通过 printenv 可以知道它为: 'system'。所以 keylayoutFilename 为:/system/usr/keylayout/pxa27x-keypad.kl
===============================================================
键码转换: 扫描码向Android KeyCode的转换Android通过键码标签完成扫描码向Android Key Code的转换,即:
键盘布局定义文件(*.kl),如pxa27x-keypad.kl,定义了扫描码到键码标签的映射。(如果没有定义键盘布局定义文件,那么默认使用系统的 /system/usr/keylayout/qwerty.kl 文件。)键盘布局定义文件的格式如下:
key
key
key
key
key
key
key
key
key
key
key
#
key
key
key
key
key
key
key
key
key
格式说明:
前面函数open_device()中,device->layoutMap->load(keylayoutFilename)实际是调用文件 frameworks/base/libs/ui/KeyLayoutMap.cpp中定义的以下函数来加载键盘布局的定义,并构建扫描码与Android KeyCode的映射表:
键码标签到Android KeyCode的映射定义在文件frameworks/base/include/ui/KeycodeLabels.h中,由KeyLayoutMap.cpp中的函数token_to_value()完成实际的映射:
函数KeyLayoutMap::load()是这样调用token_to_value()的:
Android所支持的所有Key Code定义在文件frameworks/base/include/ui/KeycodeLabels.h中,同时文件frameworks/base/core/java/android/view/KeyEvent.java也给出了Java语言中的定义:
所以,要扩充Android支持的Key Code,需要修改以下文件:
在获得按键事件以后调用:
这个map()函数会根据 键盘映射关系表 KeyedVector<int32_t,Key> m_keys 把扫描码转换成andorid framework及上层应用使用的统一键码,即Android Key Code。Android应用接收到的都是Android Key Code。
输入事件的集中获取:EventHub
文件frameworks/base/libs/ui/EventHub.cpp的函数getEvent()通过对所有输入设备的轮询来获取输入事件:
static
{
}
====================================================================
输入事件 排队 线程
在frameworks/base/services/java/com/android/server/KeyInputQueue.java 里创建了一个线程,它循环地读取输入事件,然后把事件放入事件队列里(WindowManagerService当然会使用这个类)。
Java代码:
Thread
此线程在KeyInputQueue类的构造函数中被启动。其中函数 preprocessEvent()的功能为:
文件frameworks/base/services/java/com/android /server/WindowManagerService.java定义并实现一个KeyInputQueue的派生类KeyQ:
该类在构造时生成一个KeyQ的实例mQueue,实际上就是创建了一个线程 InputDeviceReader (见以上参考代码),专门用来从输入设备读取按键事件。
====================================================================
输入事件分发线程(Android2.2): 由WindowManagerService中的InputDispatcherThread分发
在frameworks/base/services/java/com/android /server/WindowManagerService.java(运行于system_server中)里创建了一个输入事件分发线程InputDispatcherThread,它从KeyQ(即mQueue)中读取Events,找到Window Manager中的Focus Window,通过Focus Window记录的mClient接口,将Events传递到Client端:
private final class InputDispatcherThread extends Thread {
// Time to wait when there is nothing to do: 9999 seconds.
static final int LONG_WAIT=9999*1000;
public InputDispatcherThread() {
super("InputDispatcher");
}
@Override
public void run() {
while (true) process();
}
private void process() {
android.os.Process.setThreadPriority(
android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
... ...
while (true) {
long curTime = SystemClock.uptimeMillis();
// Retrieve next event, waiting only as long as the next
// repeat timeout. If the configuration has changed, then
// don't wait at all -- we'll report the change as soon as
// we have processed all events.
QueuedEvent ev = mQueue.getEvent(
(int)( (!configChanged && curTime < nextKeyTime)
? (nextKeyTime-curTime)
: 0));
... ...
try {
if (ev != null) {
switch (ev.classType) {
case RawInputEvent.CLASS_KEYBOARD:
...
dispatchKey((KeyEvent)ev.event, 0, 0);
mQueue.recycleEvent(ev);
break;
case RawInputEvent.CLASS_TOUCHSCREEN:
//Slog.i(TAG, "Read next event " + ev);
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);
break;
case RawInputEvent.CLASS_TRACKBALL:
dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);
break;
case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
configChanged = true;
break;
default:
mQueue.recycleEvent(ev);
break;
}
} else if (configChanged) {
configChanged = false;
sendNewConfiguration();
} else if (lastKey != null) {
curTime = SystemClock.uptimeMillis();
... ...
dispatchKey(newEvent, 0, 0);
} else {
curTime = SystemClock.uptimeMillis();
lastKeyTime = curTime;
nextKeyTime = curTime + LONG_WAIT;
}
} catch (Exception e) {
... ...
}
}
}
}
1) 寻找焦点
输入事件分发线程InputDispatcherThread会调用以下方法寻找焦点并调用焦点窗口的dispatchKey()函数完成事件的分发:
其中waitForNextEventTarget()会调用以下函数获取当前窗口:此函数有约100行!
实际工作是getFocusedWindowLocked()做的:它直接返回WindowManagerService维护的一个私有变量mCurrentFocus。
2) 向焦点窗口分发事件
事件的分发是根据事件类型分别处理的,对于键盘事件就是函数dispatchKey()。那么键盘事件是怎样传递到当前窗口的呢?这要涉及到以下几个类:
ViewRoot是逻辑上的东西,用来负责View的事件处理和逻辑处理,并和WindowsManagerService建立联系。ViewRoot是Handler的子类,而Handler的基本功能就是处理回调、发送消息等。源码对ViewRoot的说明:
WindowManager是个接口类,其实际的实现就是frameworks/base/core/java/android/view/WindowManagerImpl.java,后者是Android应用与系统全局的WindowManager之间底层通讯的媒介,它维护viewRoot的数组,并实现了addView(),removeView(),closeAll()等维护用函数。
ViewRoot代表含有Looper的窗口(或View)与WindowManager进行通讯,是主View与WindowsManger通讯的桥梁,而通讯的核心是IWindowSession和IWindow。ViewRoot通过IWindowSession添加窗口到WindowManager,而IWindow是WindowManager分发消息给Client ViewRoot的渠道。
引用几个图片说明它们之间的关系(引自 http://blog.csdn.net/maxleng/article/details/5561401):
Activity使用getSystemService("window")获取对WindowManagerImpl的引用,即WindowManagerService的代理:
然后可以调用wm.addView()添加新的窗口(View)到WindowManagerImpl类的私有数组mViews[]中。在addView()的最后调用ViewRoot的setView()函数,以便把此新View和WindowManagerService联系起来:
WindwoManagerService就是调用已注册过的mWindow的dispatchKey()函数把键盘事件分发到当前窗口的。
引用另一张图显示事件分发涉及的Java类的关系 (引自 http://blog.csdn.net/maxleng/article/details/5561401):![Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法 Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法](https://i-blog.csdnimg.cn/blog_migrate/0c5ed0e63ab7c75068adbf75f0d35916.jpeg)
焦点窗口对于输入事件的处理
在ViewRoot (文件frameworks/base/core/java/android/view/ViewRoot.java)处理消息(handleMessage())时,会调用deliverKeyEvent((KeyEvent)msg.obj, true)。
如果存在输入法,deliverKeyEvent()会分发事件到输入法服务,否则会直接分发到View窗口层次结构中:
1)
2)deliverKeyEventToViewHie
不管事件发送到何处,最后都要调用*.finishedEvent()表明一次输入事件处理的结束。
输入事件分发线程(Android2.3.4)
(ref: http://blog.csdn.net/andyhuabing/article/details/7006688 )
A、WindowManagerService(在system_server进程中,简称WMS)与ViewRoot之间建立双向管道:
事件分发系统中的管道的主要作用是在有事件被存储到共享内存中时,system_server端通知ViewRoot去读取事件的通信机制。既然是 ViewRoot和system_server之间建立管道通信,那么ViewRoot和WindowManagerService(负责事件传递,运行 在system_server进程中)各需维护管道的一个文件描述符,其实ViewRoot和WindowManagerService不是各自维护一个 管道的文件描述符,而是两个,当然,这两个描述符不属于同一管道,实际上也就是ViewRoot和WindowManagerService之间实现了全 双工的管道通信。
ViewRoot和WindowManagerService之间的管道的文件描述符都是被存储在一个名为InputChannel的类(android_view_InputChannel.cpp)中,这个InputChannel类是管道通信的载体,而这二者之间通过ashmem_create_region()创建的匿名共享内存进行数据传递。
在ViewRoot和WMS(WindowManagerService)建立起连接之前首先会创建一个InputChannel对象。同样的WMS端也会创建一个InputChannel对象,不过WMS的创建过程是在ViewRoot调用add()方法时调用的。InputChannel的构造不做任何操作,所以在ViewRoot中创建InputChannel时尚未初始化,它的初始化过程是在调用WMS方法add()时进行的,看到上面代码中将mInputChannel作为参数传递给WMS,目的就是为了初始化。
WindowManagerService.java建立管道:
其中outInputChannel就是ViewRoot传递来的InputChannel对象。 上述代码主要的工作其实就是创建一对InputChannel,这一对InputChannel中实现了一组全双工管道。 在创建InputChannel对的同时,会申请共享内存,并向2个InputChannel对象中各自保存一个共享内存的文件描述符( openInputChannelPair()@InputTransport.cpp )。 InputChannel创建完成后,会将其中一个的native InputChannel 赋值给outInputChannel,也就是对ViewRoot端InputChannel对象的初始化,这样随着ViewRoot和WMS两端的 InputChannel对象的创建,事件传输系统的管道通信也就建立了起来。
B、InputChannel的注册过程:
需要清楚的一点是,一个管道通信只是对应一个Activity的事件处理,也就是当前系统中有多少个Activity就会有多少个全双工管道,那么系统需要一个管理者来管理以及调度每一个管道通信,因此在创建完InputChannel对象后,需要将其注册到某个管理者中。那么,ViewRoot和WMS端的InputChannel对象各自需要注册到哪里?
因为所处的位置不同,这两个InputChannel对象肯定需要被两个不同的管理者来管理:
ViewRoot端的InputChannel对象是这样向NativeInputQueue注册的:
其中的3个参数是:
需要注意的是,整个系统中只有一个NativeInputQueue对象,为了负责管理众多的Application的事件传递,android在NativeInputQueue类中定义了一个私有子类Connection,每个InputChannel对象在注册时都会创建一个自己的 Connection对象 (见frameworks/base/core/jni/android_view_InputQueue.cpp)。见下图:
WMS端并不采用Looper机制,而是启动了2个线程来轮询事件的发生与及时地传递事件:InputReaderThread和InputDispatcherThread。InputReaderThread进程负责轮询事件的发生,InputDispatcherThread负责dispatch事件。
WMS在初始化时会创建一个InputManager实例,当然,它也是系统唯一的一个实例。JAVA层的InputManager实例并没有实现太多的业 务,真正实现Input Manager业务的是Native的NativeInputManager实例,它在被创建时,建立起了整个WMS端事件传递系统的静态逻辑。NativeInputManager的核心其实就是InputReader和InputDispatcher两个线程。事件分发是在InputDispatcherThread中进行的,因此最终WMS端InputChannel对象会注册到InputDispatcher中。同样地,由于整个系统中InputDispatcher实例只有一个,而WMS端InputChannel对象是和ViewRoot一一对应的,因此InputDispatcher类中也定义了一个内部类 Connect来管理各自的InputChannel对象。不同于NativeInputQueue类中的内部Connect 类,InputDispatcher中的内部Connect类的主要工作是由InputPublisher对象来实现的,该对象负责将发生的事件信息写入到共享内存。
1)
2) 对looper进行轮询,这个轮询过程是检查NativeInputQueue是否处理完成上一个事件。如果NativeInputQueue处理完一个事件,它就会通过管道向InputDispatcher发送消息指示consume完成。只有NativeInputQueue consume完一个事件,InputDispatcher才会向共享内存写入另一个事件。
另外,并不是所有的InputReader发送来的事件都需要传递给应用,比如翻盖/滑盖事件,除此之外的按键、触屏、轨迹球(后两者统一按motion事件处理)等事件,也会有部分被丢弃,InputDispatcher总会根据一些规则来丢弃掉一部分事件。
另附上一个老外给出的输入事件处理过程图,以帮助理解(http://cjix.info/blog/misc/internal-input-event-handling-in-the-linux-kernel-and-the-android-userspace/):
![Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法 Android输入事件处理过程分析 <wbr>& <wbr>锁屏方法](https://i-blog.csdnimg.cn/blog_migrate/2c31ef27d06f8fa26aa4177eebc755b9.jpeg)
<end>