編輯:關於Android編程
在Android手機通話過程中,用戶將手機靠近/遠離頭部,會導致手機屏幕滅/亮,這實際上是Proximity Sensor在起作用(參考1)。通俗的來講Proximity Sensor就是近距離傳感器,後文簡寫為PSensor,近距離傳感器可用於測量物體靠近或遠離。根據PSensor的這一特征,在計數以及自動化控制等領域都有使用近距離傳感器(參考2,參考3)。目前,市面上主流的智能手機均包含了近距離傳感器。PSensor檢測到手機被遮擋則關閉屏幕,反之則點亮屏幕,一方面可以省電(LCD是耗電大戶),另一方面可以防止用戶近耳接聽時觸碰到屏幕引發誤操作。
本文主要分析PSensor在整個通話過程中實現屏幕亮滅的控制原理。
在Android 4.4以後,Phone分為了TeleService和InCallUI兩個部分,通話過程中PSensor的控制由packages/apps/InCallUI/src/com/android/incallui/ProximitySensor.java負責。在以前發布的文章《Android 4.4 Kitkat Phone工作流程淺析(七)__來電(MT)響鈴流程》和《Android 4.4 Kitkat Phone工作流程淺析(九)__狀態通知流程分析》中已經分析通話狀態變更流程 ( Modem->RILC->RILJ->Framework->Telephony->InCallUI ) ,而TeleService與InCallUI建立連接是在通話狀態變更之後。因此ProximitySensor的初始化建立在通話狀態第一次變更後,整個流程如圖1所示:
圖 1 ProximitySensor初始化流程
InCallUI與TeleService通過兩種方式進行通信,即Broadcast和AIDL。當MO流程發起時,InCallUI最終會通過廣播的方式將MO請求傳遞給TeleService。而當通話狀態返回以及通話控制命令發起時,InCallUI和TeleService之間將會通過AIDL的方式進行通信,即CallHandlerService和CallHandlerServiceProxy,以及CallCommandService和CallCommandClient。使用AIDL方式通信需要建立連接(bindService),而在建立連接的時候對ProximitySensor進行了初始化操作。
ProximitySensor初始化小結1. ProximitySensor初始化是在InCallPresenter中完成;
在InCallPresenter中實例化了ProximitySensor對象並將其添加到Set
2. ProximitySensor的初始化依賴於InCallUI中CallHandlerService的綁定;
當TeleService通過bindService連接CallHandlerServiceProxy和CallHandlerService時,經過逐步調用後完成ProximitySensor的初始化。而bindService的調用則依賴於Call State的改變,Call State的改變有兩種情況:incoming 和 update,即當有來電或者通話狀態改變時,會觸發TeleService的bindService操作。
通話狀態變更之後ProximitySensor完成初始化。ProximitySensor.java的關鍵方法如下:
// 構造方法,完成初始化 // PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK是@hide的,普通app無法獲取 public ProximitySensor(Context context, AudioModeProvider audioModeProvider) { mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { mProximityWakeLock = mPowerManager.newWakeLock( PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); } else { mProximityWakeLock = null; } mAccelerometerListener = new AccelerometerListener(context, this); mAudioModeProvider = audioModeProvider; mAudioModeProvider.addListener(this); } // 完成善後工作,初始化以及掛斷電話後會被調用 public void tearDown() { mAudioModeProvider.removeListener(this); mAccelerometerListener.enable(false); if (mProximityWakeLock != null && mProximityWakeLock.isHeld()) { mProximityWakeLock.release(); } } // 當設備的方向改變之後會執行,用於判斷是否啟用PSensor(注:在滅屏狀態下不會觸發) // mOrientation的值包含: // ORIENTATION_UNKNOWN = 0 // ORIENTATION_VERTICAL = 1 垂直擺放 // ORIENTATION_HORIZONTAL = 2 水平擺放 @Override public void orientationChanged(int orientation) { mOrientation = orientation; updateProximitySensorMode(); } // 當通話狀態有改變則會通過InCallPresenter回調 @Override public void onStateChange(InCallState state, CallList callList) { // 如果是來電則無須啟用PSensor boolean isOffhook = (InCallState.INCALL == state || InCallState.OUTGOING == state); if (isOffhook != mIsPhoneOffhook) { mIsPhoneOffhook = isOffhook; mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN; mAccelerometerListener.enable(mIsPhoneOffhook); updateProximitySensorMode(); } } //... ...省略 // 當通話過程中Audio模式有變化則執行,包括: // 1. 藍牙耳機連接 // 2. 有線耳機連接 // 3. 開啟揚聲器 @Override public void onAudioMode(int mode) { updateProximitySensorMode(); } // 撥號盤顯示時回調,此時禁用PSensor以便用戶輸入 public void onDialpadVisible(boolean visible) { mDialpadVisible = visible; updateProximitySensorMode(); } // Config改變時回調,如開啟物理鍵盤(如今許多設備都已不再配備物理鍵盤) public void onConfigurationChanged(Configuration newConfig) { mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; // Update the Proximity sensor based on keyboard state updateProximitySensorMode(); } // InCallUI顯示或隱藏時回調 public void onInCallShowing(boolean showing) { if (showing) { mUiShowing = true; } else if (mPowerManager.isScreenOn()) { mUiShowing = false; } updateProximitySensorMode(); }通過查看不難發現,以上方法(除了構造方法以外)均調用了updateProximitySensorMode方法,整個通話過程中正是由其觸發PSensor的控制,關鍵代碼如下:
/** * 根據當前設備狀態,使用wake lock鎖來控制PSensor的行為 * On devices that have a proximity sensor, to avoid false touches * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock * whenever the phone is off hook. (When held, that wake lock causes * the screen to turn off automatically when the sensor detects an * object close to the screen.) * 以上為google解釋通話過程中PSensor的工作原理,即 * 在通話過程中持有PROXIMITY_SCREEN_OFF_WAKE_LOCK的wake lock,如果檢測物體靠近 * 則關閉屏幕以防止用戶誤點擊 * * 以下情況將會禁用PSensor的亮滅屏控制,即PSensor無效: * 1) 設備已連接藍牙耳機 * 2) 設備已插入有線耳機 * 3) 設備開啟揚聲器 * 4) 設備開啟物理鍵盤(如今許多設備都已不再配備物理鍵盤) */ private void updateProximitySensorMode() { if (proximitySensorModeEnabled()) { synchronized (mProximityWakeLock) { final int audioMode = mAudioModeProvider.getAudioMode(); // 以下情況將會禁用PSensor,是否禁用需根據update時具體狀態決定: // 1. 插入有線耳機 // 2. 接入藍牙耳機 // 3. 開啟揚聲器 // 4. 開啟物理鍵盤(如今許多設備都已不再配備物理鍵盤) // 5. 設備水平放置 // screenOnImmediately = true表示禁用PSensor // screenOnImmediately = false表示啟用PSensor,此時屏幕的亮滅則根據是否 // 有物體靠近或遠離PSensor決定,靠近則滅屏,遠離則亮屏 boolean screenOnImmediately = (AudioMode.WIRED_HEADSET == audioMode || AudioMode.SPEAKER == audioMode || AudioMode.BLUETOOTH == audioMode || mIsHardKeyboardOpen); //... ...省略 // 當設備水平放置,且InCallActivity不在前台顯示時,此時!mUiShowing && horizontal = true // 從而screenOnImmediately = true,並最終禁用PSensor final boolean horizontal = (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL); screenOnImmediately |= !mUiShowing && horizontal; // 當設備水平放置且開啟撥號盤時,screenOnImmediately = true,禁用PSensor screenOnImmediately |= mDialpadVisible && horizontal; if (mIsPhoneOffhook && !screenOnImmediately) { final String logStr = turning on proximity sensor: ; if (!mProximityWakeLock.isHeld()) { Log.i(this, logStr + acquiring); // 如果沒有執行過acquire方法則執行wakelock申請的動作 // 該方法用於Enable PSensor的亮滅屏控制 mProximityWakeLock.acquire(); } else { // 無須再次acquire Log.i(this, logStr + already acquired); } } else { final String logStr = turning off proximity sensor: ; if (mProximityWakeLock.isHeld()) { Log.i(this, logStr + releasing); // Wait until user has moved the phone away from his head if we are // releasing due to the phone call ending. // Qtherwise, turn screen on immediately // 禁用PSensor的亮滅屏控制包含兩種情況: // 第一種: // 如果當前設備不再是OFFHOOK狀態,比如已經掛斷電話即 mIsPhoneOffhook = false // 此時screenOnImmediately有可能為false,即設備還處於滅屏的狀態,這種情況下會根據 // PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE 這個FLAG來release wake lock // 也就是說會立即釋放鎖,但需要等用戶遠離PSensor時才會禁用PSensor並點亮屏幕 // 第二種: // 當update時發現需要禁用PSensor,比如在PSensor被遮擋設備滅屏的情況下插入有線耳機 // 此時會導致Audio模式的改變從而調用updateProximitySensorMode,同時,在這種情況下 // screenOnImmediately = true,則會立即禁用PSensor並release wake lock點亮屏幕 int flags = (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE); mProximityWakeLock.release(flags); } else { Log.i(this, logStr + already released); } } } } }通過以上代碼的分析可以知道,屏幕的亮滅有多種情況。在InCallUI中通過mProximityWakeLock.acquire()和mProximityWakeLock.release()申請/釋放wake lock來 Enable/Unenable PSensor,從而讓PSensor來控制屏幕的亮滅。從這一點也可以看出,通話過程中屏幕的亮滅的控制,實際上與Telephony沒有多大關系。
ProximitySensor使用流程小結
1. 在InCallUI中通過ProximitySensor提供的public接口,調用updateProximitySensorMode()更新PSensor的Enable/Unenable狀態。
public接口包括:
orientationChanged():設備方向改變後回調;
onStateChange():設備InCallState改變後回調;
onAudioMode():設備Audio模式改變後回調,Audio模式包括連接藍牙耳機,插入有線耳機,開啟揚聲器;
onDialpadVisible():設備通話時時Dialpad顯示狀態改變後回調;
onConfigurationChanged():設備Configuration改變後回調,如彈出物理鍵盤;
onInCallShowing():設備InCallActivity顯示狀態改變後回調;
2. PSensor的控制在ProximitySensor中通過如下方式完成:
①. 實例化PowerManager.WakeLock
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { mProximityWakeLock = mPowerManager.newWakeLock( PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); } else { mProximityWakeLock = null; }②. 通過WakeLock的acquire和release方法Enable/Unenable PSensor
mProximityWakeLock.acquire(); // 申請,即Enable PSensor if (mProximityWakeLock.isHeld()) { int flags =(screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE); mProximityWakeLock.release(flags); // 釋放,即Unenable PSensor }
在分析了InCallUI中ProximitySensor的初始化流程和控制流程之後,還需具體查看在整個通話過程中,PSensor是如何完成工作屏幕亮滅控制的。在前文的分析中,ProximitySensor通過mProximityWakeLock.acquire()和mProximityWakeLock.release()來實現PSensor的Enable/Unenable,從而讓PSensor完成屏幕亮滅的控制。整個流程大致分為:WakeLock申請、PSensor關閉/點亮屏幕、系統休眠/喚醒三大部分。
可能有童鞋要問了:WakeLock是什麼?PSensor與WakeLock有什麼關系?申請和釋放WakeLock的作用什麼?後文會為大家一一解釋。
WakeLock也可稱之為喚醒鎖,是Android提供的一種機制,用於阻止設備進入深度休眠(CPU、Memory掉電參考4 參考5)。當一個應用程序申請了WakeLock後,即使用戶按下Power鍵關閉屏幕,該應用依然可以保持運行,如音樂播放器。
Android提供了五種類型的WakeLock,即PARTIAL_WAKE_LOCK、SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK、FULL_WAKE_LOCK、PROXIMITY_SCREEN_OFF_WAKE_LOCK。其中SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK、FULL_WAKE_LOCK不再建議使用,取而代之的是android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON。PARTIAL_WAKE_LOCK表示設備CPU 持續運轉,屏幕和鍵盤背光允許關閉,普通應用可以獲取,而PROXIMITY_SCREEN_OFF_WAKE_LOCK則是PSensor的專用,只有系統APP才有權使用。
用戶在通話過程中自然不希望設備進入深度休眠,因此電話應用(這裡指InCallUI)會申請WakeLock,而這一步實際上在ProximitySensor的updateProximitySensorMode方法中完成,關鍵代碼如下:
private final PowerManager mPowerManager; private final PowerManager.WakeLock mProximityWakeLock; mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { mProximityWakeLock = mPowerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); // 申請WakeLock mProximityWakeLock.acquire(); // 釋放WakeLock int flags = (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE); mProximityWakeLock.release(flags);這裡需要注意:
1. PROXIMITY_SCREEN_OFF_WAKE_LOCK在PowerManager定義為@hide因此普通APP無法直接調用;
2. 申請WakeLock需要在AndroidManifest.xml添加android.permission.WAKE_LOCK權限;
3. flags為0表示立即釋放鎖,屏幕會立即點亮。flags為PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE則表示立即釋放鎖,但此時已然Enable PSensor,需要等待物體遠離後再亮屏;
普通應用也可以申請/釋放WakeLock,但PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK只有系統應用才有權申請/釋放,也就是說該類型的WakeLock是為PSensor量身打造的。在acquire/release WakeLock的過程中,實際上也一並完成了PSensor的Enable/Unenable,具體將在下一節給大家解釋。
本文提及的PSensor關閉/點亮屏幕指的是,Enable PSensor並滿足特定條件(靠近/遠離)後,觸發屏幕關閉/開啟流程(實際上是休眠/喚醒流程,後文解釋),而整個流程分為兩步:注冊PSensor Listener及PSensor狀態觸發亮滅屏。
InCallUI中完成WakeLock的申請後,便進入到frameworks/base/core/java/anroid/os/PowerManager.java的acquire方法中,並開啟了WakeLock的處理流程,關鍵代碼如下:
public void acquire() { //... ...省略 acquireLocked(); } Private void acquireLocked() { // ... ..省略 mService即PowerManagerService對象 mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource); }對於上層應用來說PowerManager實際上只是一個接口,真正實現在frameworks/base/services/java/com/android/server/power/PowerManagerService.java中:
@Override public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, WorkSource ws) { //... ...省略 try { acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid); } finally { Binder.restoreCallingIdentity(ident); } } private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName, WorkSource ws, int uid, int pid) { synchronized (mLock) { //... ...省略 updatePowerStateLocked(); } }因為在本節中我們主要關心關閉/點亮屏幕的操作,期間省略了很多狀態更新以及權限檢查的代碼。在acquireWakeLockInternal最後調用了updatePowerStateLocked,該方法是PowerManagerService中最為重要的方法,也即Android的休眠/喚醒入口方法。其中具有具有5大關鍵步驟,核心代碼如下:
private void updatePowerStateLocked() { //... ...省略 // Phase 0: Basic state updates. // 基本狀態更新 updateIsPoweredLocked(mDirty); updateStayOnLocked(mDirty); // 是否有開啟充電保持喚醒功能 // Phase 1: Update wakefulness. // Loop because the wake lock and user activity computations are influenced // by changes in wakefulness. final long now = SystemClock.uptimeMillis(); int dirtyPhase2 = 0; for (;;) { int dirtyPhase1 = mDirty; dirtyPhase2 |= dirtyPhase1; mDirty = 0; //檢查當前系統中所有激活的(沒有釋放)WakeLock updateWakeLockSummaryLocked(dirtyPhase1); //更新主動申請的系統狀態如bright/dim updateUserActivitySummaryLocked(now, dirtyPhase1); // 如果經過前面的檢查和更新後,沒有新的狀態變更則退出循環准備休眠 if (!updateWakefulnessLocked(dirtyPhase1)) { break; } } // Phase 2: Update dreams and display power state. // 更新dream屏保狀態 updateDreamLocked(dirtyPhase2); // 更新顯示狀態,包含關閉/點亮屏幕 updateDisplayPowerStateLocked(dirtyPhase2); // Phase 3: Send notifications, if needed. // 休眠/喚醒是否准備完成 if (mDisplayReady) { sendPendingNotificationsLocked(); } // Phase 4: Update suspend blocker. // Because we might release the last suspend blocker here, we need to make sure // we finished everything else first! // 休眠/喚醒前最後一步,之後會跳轉到native層執行申請/釋放鎖的操作 updateSuspendBlockerLocked(); }經過前文的分析,申請WakeLock之後會調用到updatePowerStateLocked()中,而在該方法中通過調用updateDisplayPowerStateLocked()更新顯示狀態,關鍵方法如下:
private void updateDisplayPowerStateLocked(int dirty) { //... ...省略 mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest, mRequestWaitForNegativeProximity); //... ...省略 }mDisplayPowerController是frameworks/base/services/java/com/android/server/power/DisplayPowerController.java的對象,在PowerManagerService的SystemReady()方法中完成初始化。繼續查看DisplayPowerController的requestPowerState方法:
public boolean requestPowerState(DisplayPowerRequest request, boolean waitForNegativeProximity) { //... ...省略 if (changed && !mPendingRequestChangedLocked) { mPendingRequestChangedLocked = true; sendUpdatePowerStateLocked(); } //... ...省略 }這裡會執行sendUpatePowerStateLocked方法,該方法中會使用sendMessage()發送一個MSG_UPDATE_POWER_STATE消息,而該消息的接收者正是DisplayPowerController的內部類DisplayControllerHandler,並執行updatePowerState()方法。當第一次執行updatePowerState()方法時,僅register PSensor的Listener而不會繼續往後執行,關鍵代碼如下:
private void updatePowerState() { //... ...省略 setProximitySensorEnabled(true); //... ....省略 } private void setProximitySensorEnabled(boolean enable) { if (enable) { if (!mProximitySensorEnabled) { // Register the listener. mProximitySensorEnabled = true; mSensorManager.registerListener(mProximitySensorListener, mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler); } } else { if (mProximitySensorEnabled) { // Unregister the listener. //... ...省略 mSensorManager.unregisterListener(mProximitySensorListener); //... ...省略 } } }
整個注冊PSensor Listener流程如圖2所示:
圖 2 PSensor Enable流程
當PSensor的狀態發生改變之後便會回調其Listener的onSensorChanged()方法,並判斷當前遮擋物是靠近還是遠離PSensor:
private final SensorEventListener mProximitySensorListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (mProximitySensorEnabled) { final long time = SystemClock.uptimeMillis(); //從Event中獲取distance值,這個值與driver上報有關,不同設備有可能不同 //從當前測試設備上返回的數據來看,靠近時distance=0.0,遠離時distance=1.0 final float distance = event.values[0]; //mProximityThreshold的默認值為5.0f,具體是在driver中設置的 //靠近:distance = 0.0 -> positive = true -> POSITIVE //遠離:distance = 1.0 -> positive = false -> NEGATIVE boolean positive = distance >= 0.0f && distance < mProximityThreshold; handleProximitySensorEvent(time, positive); } } //... ...省略 };
以上信息也可以在log中獲取到,如:
01-02 08:51:56.963 D/PowerManagerDisplayController( 735): P-Sensor Changed: false ->遠離 01-02 08:51:59.621 D/PowerManagerDisplayController( 735): P-Sensor Changed: true ->靠近 01-02 08:52:22.510 D/PowerManagerDisplayController( 735): P-Sensor Changed: false ->遠離
在onSensorChanged()方法中會繼續調用handleProximitySensorEvent()進而調用debounceProximitySensor()方法,最後會調用到updatePowerState()方法。此時,該方法會根據PSensor的狀態決發起真正的關閉/點亮屏幕流程,關鍵代碼如下:
private void updatePowerState() { //... ...省略 // Apply the proximity sensor. //acquire即Enable PSensor if (mPowerRequest.useProximitySensor) { // 如果當前不是滅屏狀態就執行 if (mPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { // 注冊PSensor監聽,即Enable PSensor setProximitySensorEnabled(true); // 如果不需要強制喚醒則執行 if (!mPowerRequest.forceWakeUpEnable){ // 默認mScreenOffBecauseOfProximity = false // 同時當前PSensor處於被遮擋當狀態,即物體靠近 if (!mScreenOffBecauseOfProximity && mProximity == PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = true; //回調PowerManagerService中的updatePowerStateLocked() sendOnProximityPositiveWithWakelock(); if (DEBUG) { Slog.d(TAG, setScreenOn false becaue of P-sensor); } // 根據PSensor狀態關閉屏幕,這塊原生AOSP是放在後面做的,MTK將其提前了 setScreenOn(false); } } } else if (mScreenOffBecauseOfProximity && mProximity == PROXIMITY_POSITIVE) { // 通話過程中設備PSensor被遮擋,屏幕關閉 // 此時,對方掛斷電話後屏幕會保持關閉,直到PSensor不再被遮擋才會亮屏 if (DEBUG) { Slog.d(TAG, enable P-sensor and wait for P-sensor negative); } setProximitySensorEnabled(true); } else if (mPowerRequest.forceProximitySensorEnable) { if (DEBUG) { Slog.d(TAG, force enable P-sensor for talking screen off timeout); } setProximitySensorEnabled(true); if (!mScreenOffBecauseOfProximity && mProximity == PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = true; } } else { } } else { //realse WakeLock後執行,即Unenable PSensor if (mWaitingForNegativeProximity && mScreenOffBecauseOfProximity && mProximity == PROXIMITY_POSITIVE) { // 通話過程中設備PSensor被遮擋,屏幕關閉 // 此時,對方掛斷電話後屏幕會保持關閉,直到PSensor不再被遮擋才會亮屏 if (DEBUG) { Slog.d(TAG, proximity wakelock released but still enable P-sensor and wait for P-sensor negative); } setProximitySensorEnabled(true); } else { setProximitySensorEnabled(false); mWaitingForNegativeProximity = false; } } //... ...省略 } else { mWaitingForNegativeProximity = false; } //... ...省略 // Animate the screen on or off. if (!mScreenOffBecauseOfProximity || mPowerRequest.forceWakeUpEnable) { if (wantScreenOn(mPowerRequest.screenState)) { // Want screen on if (!mElectronBeamOffAnimator.isStarted()) { // PSensor遮擋物遠離,點亮屏幕 setScreenOn(true); //... ...省略 } } else { // Want screen off. if (!mElectronBeamOnAnimator.isStarted()) { if (!mElectronBeamOffAnimator.isStarted()) { if (mPowerState.getElectronBeamLevel() == 0.0f || mShutDownFlag_D) { // 動畫執行完畢後關閉屏幕 setScreenOn(false); //... ...省略 } } } } //... ...省略 }
updatePowerState主要功能如下:
1. 啟用或禁用PSensor;這部分之前有做分析,不再贅述。在MTK代碼中,PSensor的滅屏操作也放在了這塊代碼中處理,而原生AOSP滅屏操作放在動畫執行之前。
2. 設置亮度具體值;該亮度值主要用於設置亮滅屏動畫。animateScreenBrightness(clampScreenBrightness(target),slow ? BRIGHTNESS_RAMP_RATE_SLOW : BRIGHTNESS_RAMP_RATE_FAST);
3. 執行滅屏/亮屏動畫;執行動畫前會調用setScreenOn()方法關閉/點亮屏幕。原生AOSP將PSensor的亮滅屏處理也放在這塊代碼中,AOSP中代碼結構如下:
if (mScreenOffBecauseOfProximity) { setScreenOn(false); } else if(wantScreenOn(mPowerRequest.screenState)) { // Want screen on // 亮屏動畫 mElectronBeamOnAnimator.start(); //... ...省略 mElectronBeamOnAnimator.end(); } else { // Want screen off. // 滅屏動畫 mElectronBeamOffAnimator.start(); //... ...省略 mElectronBeamOffAnimator.end(); }
MTK則是將 if (mScreenOffBecauseOfProximity)提到了PSensor啟用/禁用代碼中,修改之後的代碼邏輯更為清晰。
通過setScreenOn()會最終執行到LightService的setLightLocked()方法中,並通過JNI的方式將亮度值傳遞給背光模組。
setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
PSensor關閉/點亮屏幕小結
屏幕的關閉與點亮最終是由LightService來做的,而PSensor僅僅充當了觸發條件。在設備屏幕關閉/點亮流程中,需要注意以下關鍵點:
1. 第一次執行WakeLock的acquire()方法時並不會關閉屏幕,而是注冊PSensor的EventListener;
2. PROXIMITY_SCREEN_OFF_WAKE_LOCK鎖acquire/release表示register/unregister PSensor EventListener;
即決定是否將PSensor作為屏幕關閉/點亮的控制條件,acquire()表示啟用該控制條件,release()表示取消該控制條件。
3. PSensor EventListener回調onSensorChanged()觸發屏幕背光控制;
onSensorChanged()最終會調用updatePowerState()方法,根據PSensor是否被遮擋從而執行屏幕背光的設置,並在背光設置完成後回調到PowerManagerService的updatePowerStateLocked方法中繼續後面的流程。
4. 屏幕關閉/點亮會觸發Android休眠/喚醒機制;
在PowerManagerService中,updatePowerStateLocked是系統休眠/喚醒的上層入口,當更新完設備狀態後通過JNI的方式,觸發系統進入休眠/喚醒流程。
PSensor控制屏幕背光流程如圖3:
圖 3 PSensor控制屏幕背光流程
前文分析了PSensor是如何控制屏幕關閉/點亮,即通過PowerManagerService中的updateDisplayPowerStateLocked()並最終調用LightService的setLight_native()方法完成。當完成屏幕關閉/點亮之後會繼續執行updatePowerStateLocked()中余下流程,其中最重要的就是updateSuspendBlockerLocked()。該方法觸發系統了休眠/喚醒機制,關鍵代碼如下:
private void updateSuspendBlockerLocked() { //... ...省略 //acquire WakeLock,系統中如有WakeLock沒有釋放則無法進入深度休眠 if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) { mWakeLockSuspendBlocker.acquire(); mHoldingWakeLockSuspendBlocker = true; } //... ...省略 // release WakeLock,釋放系統的WakeLock以使系統可以正常進入休眠 if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) { mWakeLockSuspendBlocker.release(); mHoldingWakeLockSuspendBlocker = false; } //... ..省略 }這裡的acquire和release最終調用到frameworks/base/services/jni/com_android_server_power_PowerManagerService.cpp中:
static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) { ScopedUtfChars name(env, nameStr); acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str()); } static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass clazz, jstring nameStr) { ScopedUtfChars name(env, nameStr); release_wake_lock(name.c_str()); }
從代碼中可以看到,系統實際處理的WakeLock只有PARTIAL_WAKE_LOCK,再往後就是Android的休眠/喚醒流程了,這裡不再詳述。
需要注意的是Android JellyBean之前的系統休眠/喚醒流程大致如:earlysuspend -> wake_unlock -> suspend -> late suspend -> sleep -> wakeup -> early resume -> resume -> late resume,而在Android JellyBean之後Android使用AutoSleep逐漸替代了earlysuspend。earlysuspend和WakeLock是Android創造的一種高效休眠機制,無奈這種修改得不到Linux社區的承認,最終只能在Linux的Wakeup機制上重新修改。雖然Android在JellyBean中已經移除了earlysuspend機制,但MTK依然在AutoSleep的基礎上加入了經過修改後的earlysuspend,而Qcom默認使用的是AutoSleep,不過也有個別ODM廠商將earlysuspend機制加入到Qcom代碼中。
系統休眠/喚醒小結
在PSensor觸發滅屏操作之後,系統實際上會進入休眠流程,關於休眠/喚醒的具體流程可查看參考6 參考7。通過前面的分析可以知道,因為申請了PARTIAL_WAKE_LOCK,因此在try_to_suspend()方法中會一直循環檢測,不會執行pm_suspend()。也就是說此時系統並沒有完全休眠( MTK平台會完成earlysuspend ),休眠流程開始部分時序如圖4:
圖 4 休眠流程(開始部分)
本文主要分析了在InCallUI中PSensor如何控制屏幕的亮滅及其實現原理。全文分為三大部分,即:ProximitySensor初始化流程,ProximitySensor使用流程和PSensor工作流程。前兩部分主要分析了上層應用如何使用PSensor提供的接口,第三部分則詳細分析了PSensor如何控制屏幕的關閉/點亮。
ProximitySensor初始化流程需要注意:
1. ProximitySensor初始化是在InCallPresenter中完成;
2. ProximitySensor的初始化依賴於InCallUI中CallHandlerService的綁定,Call 狀態改變觸發綁定;
ProximitySensor使用流程需要注意:
1.在InCallUI中通過ProximitySensor提供的接口,調用updateProximitySensorMode()更新PSensor的Enable狀態;
2. 在ProximitySensor中,通過acquire/release PowerManager.WakeLock來 Enable/Unenalbe PSensor;
PSensor工作流程需要注意:
因為PSensor工作流程細分為WakeLock申請、PSensor關閉/點亮屏幕、系統休眠/喚醒三個部分,各個部分都有一些需要注意的地方。
WakeLock:
1. PROXIMITY_SCREEN_OFF_WAKE_LOCK普通APP無法申請,在系統中即PSensor專用 ( 實際在底層對應的還是PARTIAL_WAKE_LOCK );
2. 使用WakeLock需要在AndroidManifest.xml添加android.permission.WAKE_LOCK權限;
PSensor點亮/關閉屏幕需要注意:
PSensor關閉/點亮屏幕流程分為兩步:注冊PSensor Listener和PSensor狀態觸發亮滅屏,其中需要注意:
1. 第一次執行WakeLock的acquire()方法時並不會關閉屏幕,僅僅是注冊PSensor的EventListener;
2. PROXIMITY_SCREEN_OFF_WAKE_LOCK鎖acquire/release表示register/unregister PSensor EventListener;
3. 根據PSensor EventListener回調onSensorChanged()觸發屏幕背光控制;
4. 屏幕亮滅會觸發Android休眠/喚醒機制;
系統休眠/喚醒需要注意:
1. PROXIMITY_SCREEN_OFF_WAKE_LOCK實際上對應底層的PARTIAL_WAKE_LOCK,該鎖會保持CPU持續運轉不會進入休眠;
2. Android JellyBean之後默認不再使用earlysuspend,取而代之的是AutoSleep機制,對上層來說沒有影響;
3. 如果系統支持earlysuspend,則PSensor滅屏會使系統走完earlysuspend流程,最終在try_to_sleep()方法中一直循環檢測;
綜上,PSensor在整個通話過程中實現屏幕亮滅的控制流程如圖5所示:
圖 5 PSensor通話過程中控制屏幕亮滅
java的數據類型分為基本數據類型和引用數據類型。 基本數據類型分為數值型、字符型(char)、布爾型(boolean) 數值型變量 1、整
前言在我們的項目中,我們幾乎天天和一些固定的代碼打交道,比如在Activity中你要寫findViewById(int)方法來找到控件,然而這樣子的代碼對於一個稍微有點資
通過View提供的方法獲取高度方式有兩種:1, 當前顯示的view中直接獲取當前view高寬2, 通過Activity的getWindow().findViewById(
很多的Android入門程序猿來說對於Android自定義View,可能都是比較恐懼的,但是這又是高手進階的必經之路,所有准備在自定義View上面花一些功夫,多寫一些文章