Android Pie 省電模式
Android的平臺上,耗電量的問題一直被人所詬病。從Lollipop開始,Google也一直非常重視對於省電模式的改造。本篇文章將會基於最新的Android Pie的程式碼,來系統分析現在Android的省電模式流程,並且對一些可以繼續優化的點來給出一些建議。本篇文章將會從SystemUI開始講起。

QuickSettings
這個圖片相信使用android手機的同學都會有所印象,是屬於SystemUI的QuickSettings。
@Override protected void handleClick() { mBatteryController.setPowerSaveMode(!mPowerSave); }
BatterySaverTile的handleCllick響應了對於省電模式的點選。
mBatteryController是BatteryController類的一個例項化的物件,所以setPowerSaveMode是BatteryControllerImpl中進行了實現。
@Override public void setPowerSaveMode(boolean powerSave) { BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true); }
BatterySaverUtils
是 frameworks/base/packages/SettingsLib
中的一個類,這個類的方法都是static型別,方便其他的類進行方法的呼叫。
當我們點選了省電模式的按鈕,啟動省電模式的話,這裡的引數 powerSave
將會被置為true,並且 needFirstTimeWarning
也一定會為true。
/** * Enable / disable battery saver by user request. * - If it's the first time and needFirstTimeWarning, show the first time dialog. * - If it's 4th time through 8th time, show the schedule suggestion notification. * * @param enable true to disable battery saver. * * @return true if the request succeeded. */ public static synchronized boolean setPowerSaveMode(Context context, boolean enable, boolean needFirstTimeWarning) { if (DEBUG) { Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF")); } final ContentResolver cr = context.getContentResolver(); if (enable && needFirstTimeWarning && maybeShowBatterySaverConfirmation(context)) { return false; } if (enable && !needFirstTimeWarning) { setBatterySaverConfirmationAcknowledged(context); } if (context.getSystemService(PowerManager.class).setPowerSaveMode(enable)) { if (enable) { final int count = Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1; Secure.putInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, count); final Parameters parameters = new Parameters(context); if ((count >= parameters.startNth) && (count <= parameters.endNth) && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0 && Secure.getInt(cr, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) { showAutoBatterySaverSuggestion(context); } } return true; } return false; }
這個類裡面,實現的方法主要為 context.getSystemService(PowerManager.class).setPowerSaveMode(enable)
。
會通過 getSystemService
去拿到 PowerManager
的物件,然後去呼叫 setPowerSaveMode
的函式進行具體的設定。
PowerManager是android的核心service之一,其程式碼位於frameworks/base/core/java/android/os/PowerManager.java
/** * Set the current power save mode. * * @return True if the set was allowed. * * @see #isPowerSaveMode() * * @hide */ public boolean setPowerSaveMode(boolean mode) { try { return mService.setPowerSaveMode(mode); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
PowerManager是framework暴露對外的介面,真正的實現是在 PowerManagerService
。
@Override // Binder call public boolean setPowerSaveMode(boolean enabled) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.DEVICE_POWER, null); final long ident = Binder.clearCallingIdentity(); try { return setLowPowerModeInternal(enabled); } finally { Binder.restoreCallingIdentity(ident); } }
這邊有幾個需要注意的地方:
-
enforceCallingOrSelfPermission
是Android framework的一個安全記住,主要是檢查當前呼叫程序的UID是否具有操作某一項系統的許可權。比如省電模式,因為是SystemUI裡面進行操作,所以SystemUI
的AndroidManifest.xml
裡面,一定會宣告DEVICE_POWER
的許可權,否則將會被做為異常丟擲。
<uses-permission android:name="android.permission.DEVICE_POWER" />
- 當SystemUI通過呼叫PowerManager且檢查完許可權之後,
Binder.clearCallingIdentity
將會清除SystemUI Process的UID
,PID
的資訊,將其換成PownerManager所在程序UID
,PID
的內容。因為這塊涉及到了binder通訊,所以我們將會在後續Binder的內容中對其進行闡述。 - 在try{}finally{}中,呼叫了
Binder.restoreCallingIdentity()
。
這個作用是恢復遠端呼叫端的uid和pid資訊,正好是clearCallingIdentity
的反過程。 - 接下來,就是核心呼叫了,
setLowPowerModeInternal(enable)
這裡的引數,在點選省電模式的情況,enable = true
。
private boolean setLowPowerModeInternal(boolean enabled) { synchronized (mLock) { if (DEBUG) { Slog.d(TAG, "setLowPowerModeInternal " + enabled + " mIsPowered=" + mIsPowered); } if (mIsPowered) { return false; } mBatterySaverStateMachine.setBatterySaverEnabledManually(enabled); return true; } }
setLowPowerModeInternal
的操作其實是很簡單的,使用 BatterySaverStateMachine
的物件 mBatterySaverStateMachine
,去呼叫了 setBatterySaverEnabledManually
。
/** * {@link com.android.server.power.PowerManagerService} calls it when * {@link android.os.PowerManager#setPowerSaveMode} is called. * * Note this could? be called before {@link #onBootCompleted} too. */ public void setBatterySaverEnabledManually(boolean enabled) { if (DEBUG) { Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled); } synchronized (mLock) { enableBatterySaverLocked(/*enable=*/ enabled, /*manual=*/ true, (enabled ? BatterySaverController.REASON_MANUAL_ON : BatterySaverController.REASON_MANUAL_OFF), (enabled ? "Manual ON" : "Manual OFF")); } }
這邊因為 enabled
之前傳進來的為 true
,所以可以將其翻譯為
enableBatterySaverLocked(true, true, BatterySaverController.REASON_MANUAL_ON, "Manual ON" );
public static final int REASON_MANUAL_ON = 2;
接下來的 enableBatterySaverLocked
函式,將會將內容更新到 global setting
中。
/** * Actually enable / disable battery saver. Write the new state to the global settings * and propagate it to {@link #mBatterySaverController}. */ private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason, String strReason) { if (DEBUG) { Slog.d(TAG, "enableBatterySaver: enable=" + enable + " manual=" + manual + " reason=" + strReason + "(" + intReason + ")"); } final boolean wasEnabled = mBatterySaverController.isEnabled(); if (wasEnabled == enable) { if (DEBUG) { Slog.d(TAG, "Already " + (enable ? "enabled" : "disabled")); } return; } if (enable && mIsPowered) { if (DEBUG) Slog.d(TAG, "Can't enable: isPowered"); return; } mLastChangedIntReason = intReason; mLastChangedStrReason = strReason; if (manual) { if (enable) { updateSnoozingLocked(false, "Manual snooze OFF"); } else { // When battery saver is disabled manually (while battery saver is enabled) // when the battery level is low, we "snooze" BS -- i.e. disable auto battery saver. // We resume auto-BS once the battery level is not low, or the device is plugged in. if (isBatterySaverEnabled() && mIsBatteryLevelLow) { updateSnoozingLocked(true, "Manual snooze"); } } } mSettingBatterySaverEnabled = enable; putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0); if (manual) { mSettingBatterySaverEnabledSticky = enable; putGlobalSetting(Global.LOW_POWER_MODE_STICKY, enable ? 1 : 0); } mBatterySaverController.enableBatterySaver(enable, intReason); if (DEBUG) { Slog.d(TAG, "Battery saver: Enabled=" + enable + " manual=" + manual + " reason=" + strReason + "(" + intReason + ")"); } }
-
wasEnabled
首先會去判斷是否之前已經是enable的狀態,如果是的話,那麼就return。 -
isPowered
如果為true的話的也是會直接返回。 -
mLastChangedIntReason
,mLastChangedStrReason
的值會被儲存為之前傳進來的值,也就是mLastChangedIntReason=2
,mLastChangedStrReason="Manual ON"
. -
manual
和enable
都是true
的狀態,所以會upateSnoozingLocked
.而這個函式,其實只是設定mBatterySaverSnoozing
的值為ture
。而這個值的使用,我們後續還會遇到。 -
putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0);
,將LOW_POWER_MODE
的值在資料庫中置為1. -
putGlobalSetting(Global.LOW_POWER_MODE_STICKY, enable ? 1 : 0);
,將LOW_POWER_MODE_STICKY
的值在資料庫中置為1. -
mBatterySaverController.enableBatterySaver(enable, intReason);
在儲存完相應的資料庫之後,將會呼叫這個函式進行真正的操作。
/** * Called by {@link PowerManagerService} to update the battery saver stete. */ public void enableBatterySaver(boolean enable, int reason) { synchronized (mLock) { if (mEnabled == enable) { return; } mEnabled = enable; mHandler.postStateChanged(/*sendBroadcast=*/ true, reason); } }
在呼叫controller的enableBatterySaver函式中,主要是講mEnable設定為true。並且將實際的reason給傳遞到Handler裡面。
public void postStateChanged(boolean sendBroadcast, int reason) { obtainMessage(MSG_STATE_CHANGED, sendBroadcast ? ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, reason).sendToTarget(); }
這個函式的作用就是填充訊息並且sendToTarget。
@Override public void dispatchMessage(Message msg) { switch (msg.what) { case MSG_STATE_CHANGED: handleBatterySaverStateChanged( msg.arg1 == ARG_SEND_BROADCAST, msg.arg2); break; case MSG_SYSTEM_READY: for (Plugin p : mPlugins) { p.onSystemReady(BatterySaverController.this); } break; } }
- 因為
MSG_STATE_CHANGED
是之前填充併發送的訊息,所以會到MSG_STATE_CHANGED
的case中進行處理。 -
handleBatterySaverStateChanged(msg.arg1 == ARG_SEND_BROADCAST, msg.arg2);
的函式中,第一個引數由於之前傳遞的也是ARG_SEND_BROADCAST
, 所以為true;第二個引數是之前填充的reason
,所以為2
.handleBatterySaverStateChanged
的函式非常的複雜,涉及到了jni
,native
,devices
,broadcast
等資訊,所以接下來是真正的重頭戲了。
/** * Dispatch power save events to the listeners. * * This method is always called on the handler thread. * * This method is called only in the following cases: * - When battery saver becomes activated. * - When battery saver becomes deactivated. * - When battery saver is on the interactive state changes. * - When battery saver is on the battery saver policy changes. */ void handleBatterySaverStateChanged(boolean sendBroadcast, int reason) { final LowPowerModeListener[] listeners; final boolean enabled; final boolean isInteractive = getPowerManager().isInteractive(); final ArrayMap<String, String> fileValues; synchronized (mLock) { EventLogTags.writeBatterySaverMode( mPreviouslyEnabled ? 1 : 0, // Previously off or on. mEnabled ? 1 : 0, // Now off or on. isInteractive ?1 : 0, // Device interactive state. mEnabled ? mBatterySaverPolicy.toEventLogString() : "", reason); mPreviouslyEnabled = mEnabled; listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]); enabled = mEnabled; mIsInteractive = isInteractive; if (enabled) { fileValues = mBatterySaverPolicy.getFileValues(isInteractive); } else { fileValues = null; } } final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); if (pmi != null) { pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0); } updateBatterySavingStats(); if (ArrayUtils.isEmpty(fileValues)) { mFileUpdater.restoreDefault(); } else { mFileUpdater.writeFiles(fileValues); } for (Plugin p : mPlugins) { p.onBatterySaverChanged(this); } if (sendBroadcast) { if (DEBUG) { Slog.i(TAG, "Sending broadcasts for mode: " + enabled); } // Send the broadcasts and notify the listeners. We only do this when the battery saver // mode changes, but not when only the screen state changes. Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING) .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); // Send internal version that requires signature permission. intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.DEVICE_POWER); for (LowPowerModeListener listener : listeners) { final PowerSaveState result = mBatterySaverPolicy.getBatterySaverPolicy( listener.getServiceType(), enabled); listener.onLowPowerModeChanged(result); } } }
這個函式非常的大,進行的操作詳細分解如下:
-
final boolean isInteractive = getPowerManager().isInteractive();
此處,從
PowerManager
中獲取是否為Interactive
的狀態。isInteractive
很簡單:
/** * Returns true if the wakefulness state represents an interactive state * as defined by {@link android.os.PowerManager#isInteractive}. */ public static boolean isInteractive(int wakefulness) { return wakefulness == WAKEFULNESS_AWAKE || wakefulness == WAKEFULNESS_DREAMING; }
其實就是在判斷 wakefulness
的值是否為 WAKEFULNESS_AWAKE
或者 WAKEFULNESS_DREAMING
,那麼這個值代表什麼呢?
WAKEFULNESS_ASLEEP:表示系統當前處於休眠狀態,只能被wakeUp()呼叫喚醒。
WAKEFULNESS_AWAKE:表示系統目前處於正常執行狀態。
WAKEFULNESS_DREAMING:表示系統當前正處於互動屏保的狀態。
WAKEFULNESS_DOZING:表示系統正處於“doze”狀態
由於這邊我們分析的是省電模式,所以也就不詳細的展開。
這邊在正常使用點選省電模式的按鈕的時候, isInteractive
返回的是 true
。
-
listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
這裡的mListeners
其實是之前註冊的時候,所有新增LowPowerModeListener
的service。包含了VibratorService
,NetworkPolicyManagerService
等。這些內容都會進行一次儲存,方便後面的訊息分發。 - 此處
PowerManagerInternal
的呼叫比較複雜,但是其實並無實際的作用。但是針對各個廠商來說,可以在此處關注,因為後續cpu的頻率可以順著這條線進行設定。
final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); if (pmi != null) { pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0); }
首先是powerHint的函式
@Override // Binder call public void powerHint(int hintId, int data) { if (!mSystemReady) { // Service not ready yet, so who the heck cares about power hints, bah. return; } mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); powerHintInternal(hintId, data); }
和之前一樣,這邊仍然會check一下是否有 DEVICE_POWER
的許可權。
然後呼叫 powerHintInternal
函式進行處理。
private void powerHintInternal(int hintId, int data) { // Maybe filter the event. switch (hintId) { case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate. if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) { return; } break; } nativeSendPowerHint(hintId, data); }
在這個函式中,我們傳進來的hintId是5,而 PowerHint.LAUNCH
的值是在 PowerHint
的類中定義的,這個值為 public static final int LAUNCH = 8;
.
所以會直接呼叫native的方法。
對應的檔案和函式為:
frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
static void nativeSendPowerHint(JNIEnv* /* env */, jclass /* clazz */, jint hintId, jint data) { sendPowerHint(static_cast<PowerHint>(hintId), data); }
因為是jni的呼叫,所以這邊只是一個簡單的封裝。
static void sendPowerHint(PowerHint hintId, uint32_t data) { sp<IPowerV1_1> powerHalV1_1 = getPowerHalV1_1(); Return<void> ret; if (powerHalV1_1 != nullptr) { ret = powerHalV1_1->powerHintAsync(hintId, data); processPowerHalReturn(ret, "powerHintAsync"); } else { sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0(); if (powerHalV1_0 != nullptr) { ret = powerHalV1_0->powerHint(hintId, data); processPowerHalReturn(ret, "powerHint"); } } }
筆者用的手機是pixel xl,其對應的devices為marlin,且powerHal是powerHalV1_1的 版本,所以會走到
ret = powerHalV1_1->powerHintAsync(hintId, data);processPowerHalReturn(ret, "powerHintAsync");
所以對應的函式為:
powerHintAsync device/google/marlin/power/Power.cpp
以下是具體實現:
Return<void> Power::powerHintAsync(PowerHint hint, int32_t data) { // just call the normal power hint in this oneway function return powerHint(hint, data); }
又是一個抓狂的封裝。。。
Return<void> Power::powerHint(PowerHint hint, int32_t data) { if (android::base::GetProperty("init.svc.vendor.perfd", "") != "running") { ALOGW("perfd is not started"); return Void(); } power_hint(static_cast<power_hint_t>(hint), data ? (&data) : NULL); return Void(); }
對於 perfd
,我們這邊暫時不做分析。因為正常情況下,是繼續在 power_hint
。
接下來的 power_hint
,實現的code很大,但是其實都是無用功。
這個函式在設定不同電源狀態下的cpu頻率,可是並沒有對傳進來的 hint=5
進行判斷,所以並無實際作用。。。但是當我們如果想設定cpu的低頻狀態的處理,這邊無疑是一個最好的選擇。
void power_hint(power_hint_t hint, void *data) { /* Check if this hint has been overridden. */ if (power_hint_override(hint, data) == HINT_HANDLED) { ALOGE("power_hint_override"); /* The power_hint has been handled. We can skip the rest. */ return; } switch(hint) { case POWER_HINT_VSYNC: break; case POWER_HINT_SUSTAINED_PERFORMANCE: { ... break; } case POWER_HINT_VR_MODE: { ... break; } case POWER_HINT_INTERACTION: { ... break; } default: break; }
這裡走的是default break......
-
我們繼續回到剛才的主函式中進行分析。
分析完了pmi的呼叫後,就來到了
updateBatterySavingStats();
private void updateBatterySavingStats() { final PowerManager pm = getPowerManager(); if (pm == null) { Slog.wtf(TAG, "PowerManager not initialized"); return; } final boolean isInteractive = pm.isInteractive(); final int dozeMode = pm.isDeviceIdleMode() ? DozeState.DEEP : pm.isLightDeviceIdleMode() ? DozeState.LIGHT : DozeState.NOT_DOZING; synchronized (mLock) { if (mIsPluggedIn) { mBatterySavingStats.startCharging(); return; } mBatterySavingStats.transitionState( mEnabled ? BatterySaverState.ON : BatterySaverState.OFF, isInteractive ? InteractiveState.INTERACTIVE : InteractiveState.NON_INTERACTIVE, dozeMode); } }
這個函式的核心在於 transitionState
,但是隻是用於儲存當前的狀態,所以我們不深究。
fileValues
for (Plugin p : mPlugins) { p.onBatterySaverChanged(this); }
但是真正的實現,aosp只實現了一種:
onBatterySaverChanged
frameworks/base/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java
這裡的具體實現為:
private void updateLocationState(BatterySaverController caller) { final boolean kill = (caller.getBatterySaverPolicy().getGpsMode() == PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) && caller.isEnabled() && !caller.isInteractive(); boolean gpsMode = (caller.getBatterySaverPolicy().getGpsMode() == PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF); if (DEBUG) { Slog.d(TAG, "Battery saver " + (kill ? "stopping" : "restoring") + " location."); } Settings.Global.putInt(mContext.getContentResolver(), Global.LOCATION_GLOBAL_KILL_SWITCH, kill ? 1 : 0); }
killer = false
; 所以這邊設定 Global Settings
資料庫的時候,將 LOCATION_GLOBAL_KILL_SWITCH
置為 0
。
- 接下來就是向各個service,package來進行傳送廣播要求進行相應的處理了。
Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING) .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL); // Send internal version that requires signature permission. intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.DEVICE_POWER); for (LowPowerModeListener listener : listeners) { final PowerSaveState result = mBatterySaverPolicy.getBatterySaverPolicy( listener.getServiceType(), enabled); listener.onLowPowerModeChanged(result); }
這裡主要是三個廣播: ACTION_POWER_SAVE_MODE_CHANGING
, ACTION_POWER_SAVE_MODE_CHANGED
, ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL
,
我們接下來對著三個廣播進行一對一的分析。
-
ACTION_POWER_SAVE_MODE_CHANGING
FLAG_RECEIVER_REGISTERED_ONLY
該廣播首先加了一個flag為
FLAG_RECEIVER_REGISTERED_ONLY
,表示了只有動態註冊的接收才可以。接受的地方主要為:
BatterySaverReceiver.java(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING.equals(action))
BatteryControllerImpl.java(action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING))
Packages/apps/Settings
一個是
frameworks/base/packages/SystemUI/
對於 BatterySaverReceiver
來說這裡更新的主要是settings裡面的狀態。
對於 sBatteryControllerImpl
來說,這裡的呼叫為
private void setPowerSave(boolean powerSave) { if (powerSave == mPowerSave) return; mPowerSave = powerSave; // AOD power saving setting might be different from PowerManager power saving mode. PowerSaveState state = mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD); mAodPowerSave = state.batterySaverEnabled; if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off")); firePowerSaveChanged(); }
這裡的會對 PowerSave
的狀態進行儲存,並且呼叫 firePowerSaveChanged
方法來進行實現。
private void firePowerSaveChanged() { synchronized (mChangeCallbacks) { final int N = mChangeCallbacks.size(); for (int i = 0; i < N; i++) { mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave); } } }
這裡會去遍歷 mChangeCallbacks
,並且回撥 onPowerSaveChanged
的方法。
實現回撥的方法主要為:
BatteryMeterView.javaStatusBar.javaKeyguardStatusBarView.javaLightBarController.javaBatterySaverTile.java