這是之前團隊進行技術交流時,我選擇的一個主題,那段時間解決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
暫時就介紹到這里吧,若有什么不對的地方,歡迎交流指正,一起學習進步!