上小节知道了关于Dispatcher线程的基本框架,该小节讲解Dispatcher线程情景分析。
一般来说按键分为3类:
1.global key:当按下某个按键会启动特定的APP,我们称为global key,通过修改frameworks/base/core/res/res/xml/Global_keys.xml,
假设他是AKEYCODE_TV
2.system key:比如音量(AKEYCODE_VOLUME_DOWN),电源等
3.user key:a(AKCODE_KEY),b,c,d等按键。
框架回顾
之前提到过,Reader线程会把驱动上报的scancode根据.kl文件转换成keycode,现在我们看看上述小括号中的对应的具体按键,AKEYCODE_TV,AKEYCODE_TV,AKCODE_KEY在android系统中是如何处理的。贴出上一节的框图:
Reader线程会把输入事件放入mInBoundQueue队列,在放入之前会稍作处理,Dispatcher线程会从这个队列取出事件,也稍作处理之后放入到一个mOutBoundQueue的队列中,然后从队列中取出事件,发送给目标APP。
下面我们根据图表的五个步骤分析源代码
首先我们看看Reader线程,打开源码中的InputReader.cpp,上一节提到过:
KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,int32_t usageCode) {
/*构建args*/
NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
getListener()->notifyKey(&args);//通知Dispatcher线程
首先从驱动获取scancode。然后转化为android的keyCode,构造成args,最终发送给Dispatcher线程。
源码分析
GlobalKeys
现在我们打开android输入系统中的第一个c++文件,SDK\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp,看到该文件属于jni我们可以知道,这是一个本地C函数注册文件,竟然如此,我们在文件中搜索nativeInit()函数:
nativeInit()
NativeInputManager* im = new NativeInputManager(contextObj,serviceObj,messageQueue->getLooper());
NativeInputManager::NativeInputManager()
mInputManager = new InputManager(eventHub, this, this);
InputManager::InputManager()
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
通过对源码的追踪,我们最后可以看到最后创建了mReader,但是我们要注意InputReader(eventHub, readerPolicy, mDispatcher)函数的第三个参数是InputDispatcher mDispatcher,所以getListener()函数获得的是InputDispatcher的一个实例化对象,当执行
getListener()->notifyKey(&args);//通知Dispatcher线程
实际上是执行InputDispatcher中的notifyKey(&args)函数,这个地方我们要留意一下。
通过前面的分析我们可以知道,最开始时,3种按键执行的代码是一样的,没有出现分支,都是通过Reader线程读取驱动scancode,转换为android的keyCode,然后发送给Dispatcher线程,下面我们看看InputDispatcher.cpp中的notifyKey:
InputDispatcher::notifyKey(const NotifyKeyArgs* args)
KeyEvent event;
/*使用传递进来的参数args构建了一个KeyEvent*/
event.initialize(args->deviceId, args->source, args->action,flags, keyCode, args->scanCode, metaState, 0,args->downTime, args->eventTime);
/*添加到队列之前的简单处理*/
mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
/*根据policyFlags等等构造needWake*/
newEntry = new KeyEntry(args->eventTime,args->deviceId, args->source, policyFlags,args->action, flags, keyCode, args->scanCode,metaState, repeatCount, args>downTime);
/*处理完成之后添加到mInbound队列之中*/
needWake = enqueueInboundEventLocked(newEntry);
if (needWake) { //如果有必要,唤醒Dispatcher线程
mLooper->wake();
}
查看源代码的注释,可以知道interceptKeyBeforeQueueing(&event, /byref/ policyFlags)函数的第二个参数是一个引用,也就是说在该函数内部对policyFlags的修改,在外部也是有效的,进行一定处理之后,policyFlags会传递给enqueueInboundEventLocked(newEntry)函数。
我们到回去查看一下mPolicy->interceptKeyBeforeQueueing(&event, /byref/ policyFlags),位于
com_android_server_input_InputManagerService.cpp文件。
mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
if (keyEventObj) {
/*调用java中的interceptKeyBeforeQueueing同名方法,该方法处于PhoneWindowManager.java中,根据返回结果设置policyFlags*/
wmActions = env->CallIntMethod(mServiceObj,gServiceClassInfo.interceptKeyBeforeQueueing,keyEventObj, policyFlags);
/*根据wmActions设置policyFlags*/
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
if (wmActions & WM_ACTION_PASS_TO_USER) { //如果该数据需要传递给用户
policyFlags |= POLICY_FLAG_PASS_TO_USER; //设置标志位
上面提到会调用java中的一个同名方法,其处于PhoneWindowManager.java中,我们查看该文件,找到 public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags):
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags)
/*表示是否和用户处于交互状态,一般为交互状态,屏幕熄灭时为非交互状态*/
final boolean interactive = (policyFlags & FLAG_INTERACTIVE) != 0;
if (interactive || (isInjected && !isWakeKey)) {
/*设置标志位*/
result = ACTION_PASS_TO_USER;
/*如果为Global按键*/
if (isValidGlobalKey(keyCode)&& mGlobalKeyManager.shouldHandleGlobalKey(keyCode, event)) {
return result;//直接返回ACTION_PASS_TO_USER
他是怎么分辨按键是否为GlobalKey的呢?我们查看shouldHandleGlobalKey方法,位于GlobalKeyManager.java文件:
boolean shouldHandleGlobalKey(int keyCode, KeyEvent event) {
/*如果keyCode属于mKeyMappin中,那么他就是GlobalKey*/
return mKeyMapping.get(keyCode) != null;
}
那么mKeyMapping是怎么设置的呢?实际就是解析一个xml文件(Global_keys.xml),该处就不进行详细分析了。根据前面的分析,判断是否为Global_keys之后会执行enqueueInboundEventLocked(newEntry)函数。
这样Dispatcher线程对Global_keys的分析就讲解完成了。下面我们对普通按键进行分析,
普通按键
假设我们按下的按键为a,PhoneWindowManager.java中,我们查看该文件,找到 public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags):
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
return result;
就是这么简单,他没有走 interceptKeyBeforeQueueing方法中的所有分支。难点在与对SystemKey的分析
SystemKey
SystemKey会进行分类处理,假设我们按下的音量键。
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
// Handle special keys.
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
interceptScreenshotChord();//如果处于按下状态,他会判断以下是否需要截屏,然后做出相应处理
if (telecomManager.isRinging()) {
telecomManager.silenceRinger();//如果按下时,有来电,就会把来电静音。
result &= ~ACTION_PASS_TO_USER;//同时设置标志位,该按键不传递给用户
return result;
简单总结SystemKey如下:当按下时如果能处理就直接,本能处理,设置相应标志位,然后返回。但是不管如何都要放入到mInbound队列之中。
这是一个简单示例,大家可以根据源码分析其他系统按键。