06.输入系统:第10课第9节_输入系统_Dispatcher线程情景分析_Reader线程传递事件

上小节知道了关于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队列之中。

这是一个简单示例,大家可以根据源码分析其他系统按键。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江南才尽,年少无知!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值