Android结合源码分析Power按键处理流程
这是之前团队进行技术交流时,我选择的一个主题,那段时间解决power锁屏按键的bug,搞得头大,所以借此机会结合Android8.0源码去分析Power键的处理流程,也将此分享出来,希望对大家有所帮助,本文为博主原创文章,有不对的地方,欢迎大家指正!
Android系统中,一般的按键都可以在应用中处理,但是,对于系统级别的按键上层应用是无法收到消息的,也就是说,你的APP是无法直接处理的。针对这种系统级的按键事件,都是在Event事件分发前处理。Event事件分发后,只有包含有Activity的APP才能处理事件;若APP无法处理,则需要在PhoneWindowManager中处理。
本文所讲的Power键则属于该种情况。即用户触发Power键,底层收到按键会回调InputMonitor的函数dispatchUnhandledKey()。
一、为何最终处理者是PhoneWindowManager?
通过上文可知最终事件的处理是由PhoneWindowManager完成的;那么,按键后,系统是如何传递到PhoneWindowManager?下面就从源码的角度分析一下该过程。
-
- WindowManagerService:Framework 最核心的服务之一,负责窗口管理。
- InputManagerService:输入管理服务。
上述两个服务与Power按键相关,但是两者是如何关联的,就要从它们的创建说起.我们都知道,Android系统中的核心进程是system_server,对应SystemServer类,在其run()方法中会启动一堆的service,当然包括上述两个服务。具体源码分析如下:
1、先创建inputManager,再创建WindowManagerService对象时,可发现作为参数引用了上述inputManager,且创建了PhoneWindowManager实例:
源码路径:frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() { ...... inputManager = new InputManagerService(context); //输入系统服务 【step_SystemServer_1】 ...... //【step_SystemServer_2】 wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore, new PhoneWindowManager()); //PhoneWindowManager实例 ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); }
源码路径:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java private WindowManagerService(Context context, InputManagerService inputManager, boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy) { ...... mPolicy = policy; //实例: PhoneWindowManager对象 【step_InputMonitor_2】
......
}
2、启动inputManager之前,设置了一个回调接口:
//消息分发之前回调--->查看InputManagerService
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
3、InputMonitor.java:【底层是C/C++相关的,博主对C也不太了解,此处就不作分析了】
底层收到按键会回调InputManagerService的dispatchUnhandledKey()--->InputMonitor的函数dispatchUnhandledKey()。具体由底层InputDispatcher.cpp调用。
源码路径: frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
//【Power键属于系统级按键,因此处理方法是dispatchUnhandledKey】
/* Provides an opportunity for the window manager policy to process a key that
* the application did not handle. */
@Override
public KeyEvent dispatchUnhandledKey(
InputWindowHandle focus, KeyEvent event, int policyFlags) {
WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
//此处 mservice: WindowManagerService 【step_InputMonitor_0】
return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
}
由这三步可知,最终由PhoneWindowManager处理。将上述整理成时序图:
二、Power按键触发后的具体执行逻辑分析.
列出几种常见的触发Power键的情况:
情况一:长按Power键
情况二:单独短按Power键
情况三:Power + 音量键(-)
以下也以这三种情况结合源码分析流程。
由上文可知,真正的处理逻辑在PhoneWindowManager类中,该类有两个方法:interceptKeyBeforeDispatching和interceptKeyBeforeQueueing,包括了几乎所有按键的处理。
-
- interceptKeyBeforeDispatching:主要处理Home键、Menu键、Search键等。
- interceptKeyBeforeQueueing:主要处理音量键、电源键(Power键)、耳机键等。
当前Power键处理流程:
dispatchUnhandledKey()------>interceptFallback()---->interceptKeyBeforeQueueing()
下面从interceptKeyBeforeQueueing(KeyEvent event, int policyFlags)分析。
而一个按键包含两个动作Down和UP,因此从这两个方面分析interceptKeyBeforeQueueing()的执行流程。
-
- 按下: interceptPowerKeyDown(KeyEvent event, boolean interactive)
- 释放: interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled)
参数含义:
interactive:是否亮屏
KeyEvent.FLAG_FALLBACK:不被应用处理的按键事件或一些在 键值映射中不被处理的事件(例:轨迹球事件等)。
根据操作按键时系统是否亮屏,代码执行的逻辑也不同,因此每个事件下分别从亮灭屏来分析,具体如下:
(下述代码的执行过程中有对一些变量的判断,而这些值都是系统配置的,在config.xml中,因此具体执行哪个流程以当前平台配置为准)
涉及到的配置信息的相关源码路径: frameworks/base/core/java/android/view/ViewConfiguration.java frameworks/base/core/res/res/values/config.xml
1、按下(ACTION_DOWN):先上源码PhoneWindowManager.java中的interceptPowerKeyDown函数。
1 private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { 2 //FACE_UNLOCK_SUPPORT start 3 Slog.i("FaceUnlockUtil", "interceptPowerKeyDown interactive = " + interactive); 4 Settings.System.putInt(mContext.getContentResolver(), "faceunlock_start", 1); 5 //FACE_UNLOCK_SUPPORT end 6 // Hold a wake lock until the power key is released. 7 if (!mPowerKeyWakeLock.isHeld()) { 8 mPowerKeyWakeLock.acquire(); //获得唤醒锁 9 } 10 11 // Cancel multi-press detection timeout. 12 if (mPowerKeyPressCounter != 0) { 13 mHandler.removeMessages(MSG_POWER_DELAYED_PRESS); 14 } 15 16 // Detect user pressing the power button in panic when an application has 17 // taken over the whole screen. 18 boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive, 19 SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags), 20 isNavBarEmpty(mLastSystemUiFlags)); 21 if (panic) { 22 mHandler.post(mHiddenNavPanic); 23 } 24 25 // Latch power key state to detect screenshot chord. 26 if (interactive && !mScreenshotChordPowerKeyTriggered 27 && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { 28 mScreenshotChordPowerKeyTriggered = true; //标记按下power key,用于组合键截屏,具体参考下述3 29 mScreenshotChordPowerKeyTime = event.getDownTime(); 30 interceptScreenshotChord(); 31 } 32 33 // Stop ringing or end call if configured to do so when power is pressed. 34 TelecomManager telecomManager = getTelecommService(); 35 boolean hungUp = false; 36 if (telecomManager != null) { 37 if (telecomManager.isRinging()) { 38 // Pressing Power while there's a ringing incoming 39 // call should silence the ringer. 40 telecomManager.silenceRinger(); 41 } else if ((mIncallPowerBehavior 42 & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0 43 && telecomManager.isInCall() && interactive) { 44 // Otherwise, if "Power button ends call" is enabled, 45 // the Power button will hang up any current active call. 46 hungUp = telecomManager.endCall(); 47 } 48 } 49 50 GestureLauncherService gestureService = LocalServices.getService( 51 GestureLauncherService.class); 52 boolean gesturedServiceIntercepted = false; 53 if (gestureService != null) { 54 gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive, 55 mTmpBoolean); 56 if (mTmpBoolean.value && mRequestedOrGoingToSleep) { 57 mCameraGestureTriggeredDuringGoingToSleep = true; 58 } 59 } 60 61 // Inform the StatusBar; but do not allow it to consume the event. 62 sendSystemKeyToStatusBarAsync(event.getKeyCode()); // 63 64 // If the power key has still not yet been handled, then detect short 65 // press, long press, or multi press and decide what to do. 66 mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered 67 || mA11yShortcutChordVolumeUpKeyTriggered || gesturedServiceIntercepted; 68 if (!mPowerKeyHandled) { 69 if (interactive) { //亮屏 70 // When interactive, we're already awake. 71 // Wait for a long press or for the button to be released to decide what to do. 72 if (hasLongPressOnPowerBehavior()) { //长按----判断是否为弹出操作界面的逻辑 73 Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS); 74 msg.setAsynchronous(true); 75 mHandler.sendMessageDelayed(msg, 76 ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); //500ms 77 } 78 } else { 79 wakeUpFromPowerKey(event.getDownTime()); //唤醒屏幕---...... 80 81 if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { //当前配置mSupportLongPressPowerWhenNonInteractive=false 82 Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS); 83 msg.setAsynchronous(true); 84 mHandler.sendMessageDelayed(msg, 85 ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout()); 86 mBeganFromNonInteractive = true; 87 } else { 88 final int maxCount = getMaxMultiPressPowerCount(); //当前配置不支持config.xml 89 90 if (maxCount <= 1) { 91 mPowerKeyHandled = true; //执行此处# 92 } else { 93 mBeganFromNonInteractive = true; 94 } 95 } 96 } 97 } 98 } 99 100 ...... 101 102 private int getResolvedLongPressOnPowerBehavior() { 103 if (FactoryTest.isLongPressOnPowerOffEnabled()) { //默认false, 104 return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM; //长按Power,直接关机;对应属性:factory.long_press_power_off 105 } 106 return mLongPressOnPowerBehavior; 107 } 108 109 private boolean hasLongPressOnPowerBehavior() { 110 return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING; 111 }
根据上述代码分析:
(1)亮屏:第69-76行代码。
hasLongPressOnPowerBehavior()---mHanlder发送消息(500ms)--powerLongPress() ------>getResolvedLongPressOnPowerBehavior()根据获取的值来 执行相应的流程:
-
- LONG_PRESS_POWER_GLOBAL_ACTIONS:弹出操作界面---->showGlobalActionsInternal()--->sendCloseSystemWindows(String reason) /mGlobalActions.showDialog()---->PhoneWindow.sendCloseSystemWindows(mContext, reason)....
- LONG_PRESS_POWER_SHUT_OFF/LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:直接关机,相当于点击上述弹框中的关机操作。此处可结合源码查看: 对应属性:factory.long_press_power_off 使用命令:
adb shell getprop/setprop...即可测试效果。
(2)灭屏:第79-96行代码。
wakeUpFromPowerKey()---->wakeUp()---- >mPowerManager.wakeUp()....调用PowerManagerService唤 醒屏幕.
1 private void wakeUpFromPowerKey(long eventTime) { 2 wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey, "android.policy:POWER"); 3 } 4 5 private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, String reason) { 6 ...... 10 final boolean theaterModeEnabled = isTheaterModeEnabled(); 11 if (!wakeInTheaterMode && theaterModeEnabled) { 12 return false; 13 } 14 15 // Settings.Global.THEATER_MODE_ON: 16 if (theaterModeEnabled) { 17 Settings.Global.putInt(mContext.getContentResolver(), 18 Settings.Global.THEATER_MODE_ON, 0); 19 } 20 21 mPowerManager.wakeUp(wakeTime, reason); 22 return true; 23 }
2、释放(ACTION_UP):
1 private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) { 2 final boolean handled = canceled || mPowerKeyHandled; //在interceptPowerKeyDown()中灭屏或长按 对应#上述interceptPowerKeyDown(...)代码中的第91行 mPowerKeyHandled=true 3 mScreenshotChordPowerKeyTriggered = false; 4 cancelPendingScreenshotChordAction(); 5 cancelPendingPowerKeyAction(); //取消长按事件---即500ms内未监听到释放,才执行长按事件 6 7 if (!handled) { //亮屏 短按Power释放时执行此处## 因mPowerKeyHandled=false 8 // Figure out how to handle the key now that it has been released. 9 mPowerKeyPressCounter += 1; 10 11 final int maxCount = getMaxMultiPressPowerCount(); //maxCount=1 12 final long eventTime = event.getDownTime(); 13 if (mPowerKeyPressCounter < maxCount) { //不成立 14 // This could be a multi-press. Wait a little bit longer to confirm. 15 // Continue holding the wake lock. 16 Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS, 17 interactive ? 1 : 0, mPowerKeyPressCounter, eventTime); 18 msg.setAsynchronous(true); 19 mHandler.sendMessageDelayed(msg, ViewConfiguration.getMultiPressTimeout()); 20 return; 21 } 22 23 // No other actions. Handle it immediately. 24 powerPress(eventTime, interactive, mPowerKeyPressCounter); //mPowerKeyPressCounter=1 25 } 26 27 // Done. Reset our state. 28 finishPowerKeyPress(); 29 } 30 31 private void finishPowerKeyPress() { 32 mBeganFromNonInteractive = false; 33 mPowerKeyPressCounter = 0; 34 if (mPowerKeyWakeLock.isHeld()) { 35 mPowerKeyWakeLock.release(); //释放down事件时获得的锁 36 } 37 } 38 ...... 39 40 private void powerPress(long eventTime, boolean interactive, int count) { 41 if (mScreenOnEarly && !mScreenOnFully) { 42 Slog.i(TAG, "Suppressed redundant power key press while " 43 + "already in the process of turning the screen on."); 44 return; 45 } 46 47 if (count == 2) { 48 powerMultiPressAction(eventTime, interactive, mDoublePressOnPowerBehavior); 49 } else if (count == 3) { 50 powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior); 51 } else if (interactive && !mBeganFromNonInteractive) { // mBeganFromNonInteractive=false 52 switch (mShortPressOnPowerBehavior) { //mShortPressOnPowerBehavior : 配置为1 53 case SHORT_PRESS_POWER_NOTHING: 54 break; 55 case SHORT_PRESS_POWER_GO_TO_SLEEP: //执行# 56 goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0); 57 break; 58 ....... 59 } 60 }
65
(1)亮屏:
powerPress()--->goToSleep()--- >mPowerManager.goToSleep()...调用PowerManagerService 使系统睡眠。
(2)灭屏:
finishPowerKeyPress()...
3、组合键(Power + 音量减):功能就是我们常用的屏幕截图的快捷方式。
1 @Override 2 public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { 3 if (!mSystemBooted) { 4 // If we have not yet booted, don't let key events do anything. 5 return 0; 6 } 7 8 ......//代码略 9 final int keyCode = event.getKeyCode(); 10 // Basic policy based on interactive state. 11 int result; 12 13 // Handle special keys. 14 switch (keyCode) { 15 ...... 16 case KeyEvent.KEYCODE_VOLUME_MUTE: { 17 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { //音量键减- 【Power + VOLUME_DOWN】截屏操作 18 if (down) { 19 if (interactive && !mScreenshotChordVolumeDownKeyTriggered 20 && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) { 21 mScreenshotChordVolumeDownKeyTriggered = true; 22 mScreenshotChordVolumeDownKeyTime = event.getDownTime(); 23 mScreenshotChordVolumeDownKeyConsumed = false; 24 cancelPendingPowerKeyAction(); 25 interceptScreenshotChord(); 26 interceptAccessibilityShortcutChord(); 27 } 28 } else { 29 mScreenshotChordVolumeDownKeyTriggered = false; 30 cancelPendingScreenshotChordAction(); 31 cancelPendingAccessibilityShortcutAction(); 32 } 33 } else if(...){ 34 ...... 35 } 36 ...... 37 break; 38 } 39 case KeyEvent.KEYCODE_POWER: { //POWER 键 40 if(SystemProperties.getBoolean("sys.requireKey", false)) break; 41 // Any activity on the power button stops the accessibility shortcut 42 cancelPendingAccessibilityShortcutAction(); 43 result &= ~ACTION_PASS_TO_USER; //不分发按键至应用 44 isWakeKey = false; // wake-up will be handled separately 45 if (down) { 46 interceptPowerKeyDown(event, interactive); //在上述1中interceptPowerKeyDown()的第28行可见标记mScreenshotChordPowerKeyTriggered = true; 47 } else { 48 interceptPowerKeyUp(event, interactive, canceled); 49 } 50 break; 51 } 52 ...... 53 } 54 55 ...... 56 return result; 57 } 58 59 //截屏 60 private void interceptScreenshotChord() { 61 if (mScreenshotChordEnabled 62 && mScreenshotChordVolumeDownKeyTriggered && mScreenshotChordPowerKeyTriggered //同时按下Volum_down + Power, 后执行的该方法 63 && !mA11yShortcutChordVolumeUpKeyTriggered) { 64 final long now = SystemClock.uptimeMillis(); 65 if (now <= mScreenshotChordVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS 66 && now <= mScreenshotChordPowerKeyTime 67 + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) { //150ms 68 mScreenshotChordVolumeDownKeyConsumed = true; 69 cancelPendingPowerKeyAction(); 70 mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); 71 mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); //根据是否在锁屏界面设置不同的延迟时间,了解即可 72 } 73 } 74 } 75 76 private class ScreenshotRunnable implements Runnable { 77 private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; 78 79 public void setScreenshotType(int screenshotType) { 80 mScreenshotType = screenshotType; 81 } 82 83 @Override 84 public void run() { 85 takeScreenshot(mScreenshotType); //# 86 } 87 } 88 89 private static final String SYSUI_PACKAGE = "com.android.systemui"; 90 private static final String SYSUI_SCREENSHOT_SERVICE = 91 "com.android.systemui.screenshot.TakeScreenshotService"; 92 93 private void takeScreenshot(final int screenshotType) { 94 synchronized (mScreenshotLock) { 95 if (mScreenshotConnection != null) { 96 return; 97 } 98 final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE, 99 SYSUI_SCREENSHOT_SERVICE); //SystemUI中的截屏服务 100 final Intent serviceIntent = new Intent(); 101 serviceIntent.setComponent(serviceComponent); 102 ServiceConnection conn = new ServiceConnection() { 103 @Override 104 public void onServiceConnected(ComponentName name, IBinder service) { 105 synchronized (mScreenshotLock) { 106 if (mScreenshotConnection != this) { 107 return; 108 } 109 Messenger messenger = new Messenger(service); 110 Message msg = Message.obtain(null, screenshotType); 111 final ServiceConnection myConn = this; 112 Handler h = new Handler(mHandler.getLooper()) { 113 @Override 114 public void handleMessage(Message msg) { 115 synchronized (mScreenshotLock) { 116 if (mScreenshotConnection == myConn) { 117 mContext.unbindService(mScreenshotConnection); 118 mScreenshotConnection = null; 119 mHandler.removeCallbacks(mScreenshotTimeout); 120 } 121 } 122 } 123 }; 124 msg.replyTo = new Messenger(h); 125 msg.arg1 = msg.arg2 = 0; 126 if (mStatusBar != null && mStatusBar.isVisibleLw()) 127 msg.arg1 = 1; 128 if (mNavigationBar != null && mNavigationBar.isVisibleLw()) 129 msg.arg2 = 1; 130 try { 131 messenger.send(msg); 132 } catch (RemoteException e) { 133 } 134 } 135 } 136 137 @Override 138 public void onServiceDisconnected(ComponentName name) { 139 synchronized (mScreenshotLock) { 140 if (mScreenshotConnection != null) { 141 mContext.unbindService(mScreenshotConnection); 142 mScreenshotConnection = null; 143 mHandler.removeCallbacks(mScreenshotTimeout); 144 notifyScreenshotError(); 145 } 146 } 147 } 148 }; 149 if (mContext.bindServiceAsUser(serviceIntent, conn, 150 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, 151 UserHandle.CURRENT)) { 152 mScreenshotConnection = conn; 153 mHandler.postDelayed(mScreenshotTimeout, 10000); 154 } 155 } 156 }
结合上述代码,可概括流程为:
interceptScreenshotChord():----->启动线程ScreenshotRunnable---->takeScreenshot()----可看出真正的截图操作是在SystemUI中.(感兴趣的话,可自行研究)。
将上述代码执行细节整理出下述流程图:(在线作图:https://www.processon.com/)查看原图
三、总结:
由上文可知,Android系统响应Power键的情况可总述为以下内容:
按下Down:
(1)亮屏长按----弹出操作框(关机、重启.....)
(2)灭屏短按----wakeUp唤醒屏幕
释放Up:
(1)亮屏长按不处理
(2)灭屏不处理
(3)亮屏短按----gotoSleep
暂时就介绍到这里吧,若有什么不对的地方,欢迎交流指正,一起学习进步!
posted on 2019-07-18 15:23 Android之路 阅读(5691) 评论(1) 编辑 收藏 举报