后台有位小伙伴分享了一个头条的面试题:按下手机的 Home 键,有哪些动作和事件发生?

今天我们就来分析一下,本文源码基于 Android - 28

事件的分类

安卓系统中的事件,主要有以下几种:

  • 按键事件(KeyEvent) 由物理按键产生的事件,如:Home, Back, Volume Up, Volume Down, Camera 等。今天主要分析的就是这类事件。

  • 触摸事件(TouchEvent) 在屏幕上点击拖动,以及由它们组合的各种事件。

  • 鼠标事件(MouseEvent) 鼠标操作产生的事件

  • 轨迹球事件 (TrackBallEvent) 知道轨迹球的,怕不是要暴露年龄

安卓针对上面这些事件共性,提取了一个统一的抽象类 InputEvent 。InputEvent 提供了几个常用的抽象方法,比如 getDevice() 获得当前事件的“硬件源”,getEventTime() 获取事件发生的时间。

InputEvent 有两个子类:

  • KeyEvent 用于描述按键事件

  • MotionEvent 用来描述 Movement 类型的事件(通过 mouse, pen, finger, trackball 产生)。

而我们要监听这些事件一般也是通过对 View 设置相应的监听实现:

setOnKeyListener(OnKeyListener)
setOnTouchListener(OnTouchListener)
...

或者也可以直接复写相关的方法:

boolean onKeyDown(int keyCode, KeyEvent event)
boolean onTouchEvent(MotionEvent event)
....

事件处理的准备工作

事件处理设计的整体思路是驱动层会有一个消息队列来存放事件,会有一个 Reader 来不停的读取事件,一个 Dispatcher 来分发消息队列中的事件。Dispatcher 分发的事件最后会通过 jni 上报到 InputManagerService,然后通过接口最后传递给PhoneWindow,PhoneWindow 再根据不同的事件类型来做不同的处理。

我们先看一下 Reader、Dispatcher 是怎么来的。

SystemServer 在 startOtherServices() 方法中启动 InputMangerService:

InputManagerService inputManager = new InputManagerService(context);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();

接下来我们看一下  InputMangerService 的构造方法:

public InputManagerService(Context context) {this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper()); mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());...
}

主要是调用 nativeInit 方法传入一个消息队列,nativeInit 方法是为了去初始化一些 native 对象。最终是为了 new 一个 native 层的 InputManager 。调用链如下(想要了解详情的同学可以在相应的源码类里查看):

   new InputManagerService() // InputManagerService.java
-> nativeInit(..., queue) // com_android_server_input_InputManagerService.cpp
-> new NativeInputManager(..., looper) // com_android_server_input_InputManagerService.cpp
-> new InputManager(eventHub, ...) //InputManager.cpp

重点就在这个 InputManager 里:

InputManager::InputManager(...) {mDispatcher = new InputDispatcher(dispatcherPolicy);mReader = new InputReader(eventHub, readerPolicy, mDispatcher);initialize();
}void InputManager::initialize() {mReaderThread = new InputReaderThread(mReader);mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

我们可以看到,在 InputManager 里准备好了 mReader、mDispatcher,以及相关的两个线程,那么接下来当然就是把线程跑起来,好去读事件和分发事件。

启动事件读取和分发线程的调用链如下:

   SystemServer.startOtherServices()
-> inputManagerService.start()
-> nativeStart() // com_android_server_input_InputManagerService.cpp
-> InputManager.start() // InputManager.cpp

最后的这个 start 方法很简单,就是把两个线程跑起来:

status_t InputManager::start() {status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
}

至此,事件处理工作所需要的对象和线程都已经处理好了。前面那些代码看完记不住就算了,记住这句话:「SystemServer 启动 IMS 时,会创建一个 native 的 InputManager 对象,这个 InputManager 会通过 mReader 不断读事件,再通过 mDispatcher 不断分发事件」

接下来我们看下,事件是怎么读取和分发的。

事件的读取

InputReaderThread 等待按键消息到来,该 Thread 在 threadLoop 中无尽的调用 InputReader 的 loopOnce 方法:

bool InputReaderThread::threadLoop() {mReader->loopOnce();return true;
}

在 loopOnce 方法中会通过 EventHub 来获取事件,并放入 buffer 中:

void InputReader::loopOnce() {// EventHub 从驱动读取事件size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);...if (count) {// 获取的事件是 RawEvent,需要处理成 KeyEvent、MotionEvent 等各种类型等processEventsLocked(mEventBuffer, count);}// 将队列中事件刷给监听器,监听器实际上就是 InputDispatcher 事件分发器。mQueuedListener->flush();
}

InputDispatcher 收到事件后调用,如果是 KeyEvent 会调用 notifyKey,(如果是 MotionEvent 则会调用 notifyMotion) :

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {KeyEvent event;event.initialize(args->deviceId, args->source, args->action,flags, keyCode, args->scanCode, metaState, 0,args->downTime, args->eventTime);// 通过 NatvieInputManager 在 Event 入队前做一些处理mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);...KeyEntry* newEntry = new KeyEntry(args->eventTime, args->source,args->action, flags, keyCode,...)// 事件放入队尾needWake = enqueueInboundEventLocked(newEntry);
}

以上,便是 InputReader 获取到设备事件通知 InputDispatcher 并存放到事件队列中的流程。

事件的分发

下面将介绍 InputDispatcher 如何从事件队列中读取事件并分发出去。

首先在 InputDispatcherThread 的 threadLoop 中无尽的调用 dispatchOnce 方法:

bool InputDispatcherThread::threadLoop() {mDispatcher->dispatchOnce();return true;
}

该方法两个功能:

  • 调用 dispatchOnceInnerLocked 分发事件;

  • 调用 runCommandsLockedInterruptible 来处理 CommandQueue 中的命令,出队并处理,直到队列为空。

void InputDispatcher::dispatchOnce() {if (!haveCommandsLocked()) {dispatchOnceInnerLocked(&nextWakeupTime);}if (runCommandsLockedInterruptible()) {nextWakeupTime = LONG_LONG_MIN;}...
}

在 dispatchOnceInnerLocked 中会处理多种类型的事件,这里关注按键类型的(其他如触摸,设备重置等事件流程稍有区别)。一通调用后到 PhoneWindowManager , 终于回到 java 了:

  InputDispatcher::dispatchOnceInnerLocked
-> dispatchKeyLocked
-> doInterceptKeyBeforeDispatchingLockedInterruptible
-> NativeInputManager.interceptKeyBeforeDispatching
-> PhoneWindowManager.interceptKeyBeforeDispatching
...// 以下 NativeInputManager.doInterceptKeyBeforeDispatchingLockedInterruptible 的部分代码
nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,&event, entry->policyFlags);
if (delay < 0) {// Home 事件将被拦截entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
}
// 未被拦截的继续处理分发,本篇暂不分析

PhoneWindowManager

话不多说,继续看代码,离胜利不远了!源码的注释写得很清楚,我这边就不翻译了。真的不是因为懒,是想让你们提高点英语阅读水平 。(-> <-)

public long interceptKeyBeforeDispatching(KeyEvent event, ...) {// First we always handle the home key here, so applications// can never break it, although if keyguard is on, we do let// it handle it, because that gives us the correct 5 second// timeout.if (keyCode == KeyEvent.KEYCODE_HOME) {// If we have released the home key, and didn't do anything else// while it was pressed, then it is time to go home!if (!down) {cancelPreloadRecentApps();mHomePressed = false;... // Delay handling home if a double-tap is possible.if (mDoubleTapOnHomeBehavior != DOUBLE_TAP_HOME_NOTHING) {mHomeDoubleTapPending = true;mHandler.postDelayed(mHomeDoubleTapTimeoutRunnable,ViewConfiguration.getDoubleTapTimeout());return -1;}handleShortPressOnHome(); //短按//-1 调用处的事件结果就会赋值 INTERCEPT_KEY_RESULT_SKIPreturn -1;}...// Remember that home is pressed and handle special actions.if (repeatCount == 0) {mHomePressed = true;if (mHomeDoubleTapPending) {handleDoubleTapOnHome();//双击} else if (mDoubleTapOnHomeBehavior == DOUBLE_TAP_HOME_RECENT_SYSTEM_UI) {preloadRecentApps();//最近 app}} else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {if (!keyguardOn) {handleLongPressOnHome(event.getDeviceId());//长按}}return -1;
}
}

Home 的相关事件都在这处理啦。接下来我们就看一下 handleShortPressOnHome 短按 Home 进入 Luncher 是怎么实现的吧:

private void handleShortPressOnHome() {...// Go home! 坚持下,看完我们就 Go Home!launchHomeFromHotKey();
}

Go Home!

void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {if (respectKeyguard) {// 处理一些锁屏的情况,可能直接 return }// no keyguard stuff to worry about, just launch home!if (mRecentsVisible) {// 延时后台的打开 Activity 的操作,避免打扰用户的操作// 虽然方法名 stop 实际实现是延时 5sActivityManager.getService().stopAppSwitches();// Hide Recents and notify it to launch HomehideRecentApps(false, true);} else {// Otherwise, just launch HomestartDockOrHome(true /*fromHomeKey*/, awakenFromDreams);}
}

最后看一下跳转到 Home 的一些细节

 void startDockOrHome(boolean fromHomeKey, boolean awakenFromDreams) {ActivityManager.getService().stopAppSwitches();// 关闭系统弹窗,如输入法sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);Intent dock = createHomeDockIntent();if (dock != null) { // 开启应用抽屉startActivityAsUser(dock, UserHandle.CURRENT);return;}intent = mHomeIntent //省略部分逻辑// 开启 Home 页面startActivityAsUser(intent, UserHandle.CURRENT);}

怎么回答

「面试官」:按下手机的 Home 键,有哪些动作和事件发生

????️:按下 Home键后,底层驱动会获取这个事件, AMS 通过 Reader 读取驱动捕获的事件,再通过 Dispatcher 对事件进行分发。Dispatcher 分发事件前,PhoneWindowManager 会对 Home 和其它系统事件进行拦截处理,其中短按 Home 键的处理有:关闭相应的系统弹窗,延迟其它待打开的 Activity,最后使用 Intent 打开 Home 或者 Dock 页面。

推荐阅读
最近聊了一些高P,我慌了
再见,Eclipse
到底什么是ThreadLocal
编程·思维·职场
欢迎扫码关注

按下 Home 键后发生了什么事?相关推荐

  1. windows8怎么关机_按下电源键后发生了什么?电脑是如何关机的?

    在Windows启动后,最自然的关机方式是什么呢?当然是按下电源键了.有没有好奇,当我们按下电源键,会发生什么呢?为什么Windows可以选择关机或者睡眠?背后的机理又是什么呢? 历史 如果你曾经使用 ...

  2. ubuntu 下更新pip后发生 ImportError: cannot import name ‘main‘的问题解决

    ubuntu 下更新pip后发生 ImportError: cannot import name 'main'的问题解决 参考文章: (1)ubuntu 下更新pip后发生 ImportError: ...

  3. int 9中断例程-;在屏幕中间依次显示'a'~'z',并可以让人看清。 在显示的过程中,按下Esc键后,改变显示的颜色...

    ;在屏幕中间依次显示'a'~'z',并可以让人看清. ;在显示的过程中,按下Esc键后,改变显示的颜色 assume cs:code stack segment db 128 dup(0) stack ...

  4. ubuntu 下更新pip后发生 ImportError: cannot import name 'main'的问题解决

    ubuntu 下更新pip后发生 ImportError: cannot import name 'main'的问题解决 今天刚使用ubuntu 由于安装的是pip 8的版本,我感觉pip版本有些低就 ...

  5. textbox控件输入内容后按下Enter键后执行button1的click方法

    对于Asp.Net.在TextBox1中输入内容后,按下enter键后,执行Button1的click方法,在page_load事件增加如下代码即可实现: TextBox1.Attributes.Ad ...

  6. 键盘按下某键 停止运行java_Java:按下“Q”键后终止while循环

    我有下面这个java程序,它在没有while循环的情况下工作正常,但我想运行执行,直到用户从键盘按下Q键.Java:按下"Q"键后终止while循环 那么,什么样的条件应该放在wh ...

  7. stm32按下复位键后程序停止运行,重新上电又可以运行

    文章目录 问题描述 一.问题排查 二.问题原因 1.boot引脚没有地或者VCC 总结 问题描述 今天遇到了一个比较有意思的问题,大致就是在做蓝牙串口通信时,发现自己焊接的板子出现了按下reset键时 ...

  8. 按下开机键后,电脑都干了些什么?

    ① 第一步,开机直接访问BIOS ROM的0xFFFF0. 开机以后,CS寄存器置为0xFFFF,IP寄存器置为0x0000.这样一来,CPU就会要求访问地址为0xFFFF0的这个地方.这个地址实际上 ...

  9. Day10 -- JavaScript实现按下 Shift 键后进行多选操作的功能

    实现效果 需求分析 通过shift键实现连续多选功能 按下shift的同时点击A复选框,然后在点击B复选框,A,B之间的复选框都被勾选上 或者是先点击A复选框,再按下shift键点击B复选框,A,B之 ...

最新文章

  1. android简单分享----文字加图片
  2. 云米路演PPT曝光:发行区间9-11美元 依赖小米品牌
  3. 李彦宏:5年后语音和图片搜索会超文字搜索
  4. matlab veristand,amesim Veristand matlab
  5. 2015年9大优秀项目管理工具集锦
  6. Tensorflow 获取model中的变量列表,用于模型加载等
  7. 工作笔记——海康威视网络摄像头接入华为云VIS服务
  8. java random 种子数_JAVA:Random的种子含义
  9. SQL SERVER 取得某月第一天
  10. ceph (luminous 版) primary affinity 管理
  11. 关于VScode中如何修改默认的中文注释格式(包括去除斜体和修改颜色)
  12. Multi-University Training Contest L - Wavel Sequence
  13. 利用腾讯漏洞,QQ群日拉万人精准流量的方法
  14. 产品经理小技术:图片素材随手找,原型设计快又好
  15. IOS - rangeOfString、NSNotFound
  16. 如何更改音频格式?分享这几个简单的转换方法给你
  17. 百度编辑器ueditor添加视频方法
  18. 【CVPR2021】【语义编辑】SeFa(Closed-Form Factorization of Latent Semantics in GANs)论文分析
  19. 计算从1加到1000的结果
  20. Pr 编译影片时出错。软件渲染错误

热门文章

  1. 【华人学者风采】李海洲 新加坡国立大学
  2. 客户端软件GUI开发技术漫谈:原生与跨平台解决方案分析
  3. Python自动化测试常用库整理
  4. 提示计算机未安装flash,win10系统提示未安装Flash的解决方法
  5. C语言求解一元二次方程组的代码
  6. 神级总结:报价英文函电的常用金句
  7. 看集装箱号码识别技术如何解决港口拥堵
  8. java jmf获取图像_java利用jmf实现拍照功能
  9. 什么是ring0-ring3
  10. 计算机分子模拟的意义包括,计算机分子模拟