1. 程式人生 > >Android 8.1 之省電模式分析

Android 8.1 之省電模式分析

1. 功能概述

Battery saver是Google在Android L上新增的選項,這個功能是在Setting -> Battery (–> more (androidO以前的路徑)) –> Battery saver,這個功能主要是為了在相同電量下能夠更長時間的使用手機,簡稱:“省電助手”。

開啟之後手機將處於省電模式,省電模式下電池使用量將大大降低,一些不必要的耗電操作將禁止。屬於一個很實用的功能。

2. 實現原理分析

下面我們來介紹一下Battery saver的實現原理與其做了什麼電源管理型別的優化。

2.1 Battery saver介面顯示與功能

圖2.1 Battery Saver介面
圖2.1 Battery Saver介面

從圖2.1我們可以知道Battery saver有一個switch開關,還有一個選項(是否自動開啟battery saver)

1、先來看一下Battery saver的switch開關,當我們開啟Battery saver的時候

//packages/apps/Settings/src/com/android/settings/fuelgauge/BatterySaverSettings.java
    private void trySetPowerSaveMode(boolean mode) {
        if (!mPowerManager.setPowerSaveMode(mode)) {
            …
    }
  • 1
  • 2
  • 3
  • 4
  • 5

2、先來看一下Battery saver的switch開關,當我們開啟Battery saver的時候

//frameworks/base/core/java/android/provider/Settings.java
 public static final String LOW_POWER_MODE_TRIGGER_LEVEL = "low_power_trigger_level";//電源保護模式的觸發門檻值

//packages/apps/Settings/src/com/android/settings/fuelgauge/BatterySaverSettings.java 
// battery_saver_trigger_values是電源保護觸發門檻值的可選值,’0’代表關閉,’5’代表5%電量的時候自動進入電源保護模式,’15’代表15%電量的時候自動進入電源保護模式
mTriggerPref = new SettingPref(SettingPref.TYPE_GLOBAL, KEY_TURN_ON_AUTOMATICALLY, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0, /*default*/ getResources().getIntArray(R.array.battery_saver_trigger_values)) <!-- Battery saver mode: allowable trigger threshold levels. --> <integer-array name="battery_saver_trigger_values" translatable="false" > <item>0</item> <item>5</item> <item>15</item> </integer-array>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.2 進入Power Save Mode的條件

1、當應用呼叫了PMS的setPowerSaveMode進行電源保護模式,會進入PowerManagerService.java,裡面主要包括許可權檢查和進一步設定

//PowerManagerService.java
public boolean setPowerSaveMode(boolean mode) {
    //這個操作需要呼叫者用有DEVICE_POWER許可權
    mContext.enforceCallingOrSelfPermission(
            android.Manifest.permission.DEVICE_POWER, null);
    //......
    //進行電源保護模式內部設定
    return setLowPowerModeInternal(mode);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2、電源保護模式內部設定setLowPowerModeInternal,裡面包含電源保護標誌位LOW_POWER_MODE的設定,電源保護模式mLowPowerModeSetting的設定

private boolean setLowPowerModeInternal(boolean mode) {
        ……
        //此時會設定電源保護標誌位LOW_POWER_MODE設定
        Settings.Global.putInt(mContext.getContentResolver(),
                Settings.Global.LOW_POWER_MODE, mode ? 1 : 0);
        mLowPowerModeSetting = mode;//電源保護模式設定
        ……
        updateLowPowerModeLocked();//電源保護
        return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3、更新電源保護相關設定updateLowPowerModeLocked,該函式包括了大部分電源保護的邏輯處理。

1) 如果在充電中或者”非低電而且沒有開完機”情況下是不允許進入電源保護模式

2) 自動開啟電源保護的條件是:不在充電狀態、設定了自動進入電源保護、使用者沒有主動在低電下關閉低電模式、低電狀態

3) 只要手動設定電源保護mLowPowerModeSetting,或者自動進入電源保護,那麼電源保護模式lowPowerModeEnabled將會給開啟

4) 進行Power HAL的設定,android預設的default Power HAL是power.default.so,裡面的內容預設都是置空,也就是沒有任何操作。

5) 進行電源保護模式切換前會發生一個ACTION_POWER_SAVE_MODE_CHANGING的廣播,然後回撥所有監聽了電源保護的監聽器,最後切換成功後會發生ACTION_POWER_SAVE_MODE_CHANGED的廣播。

6) 會將mLowPowerModeListeners低電模式的監聽onLowPowerModeChanged全部回撥一遍

void updateLowPowerModeLocked() {
//如果在充電中是不允許進入電源保護模式,其中在androidO上新增在非低電量
//(當電量低於15%屬於低電模式,config_lowBatteryWarningLevel可以配置)
//而且還沒開完機的時候也是不允許進入該模式的
  if ((mIsPowered || !mBatteryLevelLow && !mBootCompleted) && mLowPowerModeSetting) { 
      Settings.Global.putInt(mContext.getContentResolver(),
              Settings.Global.LOW_POWER_MODE, 0);
      mLowPowerModeSetting = false;
  }
// autoLowPowerModeEnabled代表是否需要自動開啟電源保護,條件是不在充電狀態、設定了自動進入電源保護、使用者沒有主動在低電下關閉低電模式、低電狀態
  final boolean autoLowPowerModeEnabled = !mIsPowered && mAutoLowPowerModeConfigured
          && !mAutoLowPowerModeSnoozing && mBatteryLevelLow;

//只要手動設定電源保護mLowPowerModeSetting,或者自動進入電源保護,那麼電源保護模式lowPowerModeEnabled將會給開啟
  final boolean lowPowerModeEnabled = mLowPowerModeSetting || autoLowPowerModeEnabled;

  if (mLowPowerModeEnabled != lowPowerModeEnabled) {//電源保護模式進行了改變
      mLowPowerModeEnabled = lowPowerModeEnabled;//設定當前是否處於電源保護模式
      //進行Power HAL的設定,android預設的default Power HAL是power.default.so裡面是做空的
      powerHintInternal(POWER_HINT_LOW_POWER, lowPowerModeEnabled ? 1 : 0);
      //在androidO上是在開機完成後在進行設定
      postAfterBootCompleted( new Runnable() {
          @Override
          public void run() {
              Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
                      .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, mLowPowerModeEnabled)
                      .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
              //傳送ACTION_POWER_SAVE_MODE_CHANGING電源保護正在切換的廣播
              mContext.sendBroadcast(intent);
              ……
              for (int i=0; i<listeners.size(); i++) {
                  final PowerManagerInternal.LowPowerModeListener listener = listeners.get(i);
                  final PowerSaveState result = mBatterySaverPolicy.getBatterySaverPolicy(
                          listener.getServiceType(), lowPowerModeEnabled);
                  //回撥監聽了電源保護模式的listeners,androidO在此處回撥的方法有更加多的內容,包含多種策略
                  listener.onLowPowerModeChanged(result);
               }
              intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
              //新增flag ACTION_POWER_SAVE_MODE_CHANGED),這個廣播在androidN以後就只會給動態註冊者接收
              intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
              // 傳送ACTION_POWER_SAVE_MODE_CHANGED電源保護切換成功後的廣播
              mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
              // 需要帶有相應許可權才會受到的ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL廣播,這個目前原始碼沒有使用
              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);
          }
      });
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

接下來講的是對系統實際的影響

2.3 Power Save Mode對系統的實際影響

2.3.1 螢幕亮度將會變成原來的50%

我們在設定了LOW_POWER_MODE電源保護模式的時候,PowerManagerService本身會對其進行監聽,監聽器改變後會進行顯示裝置狀態更新updateDisplayPowerStateLocked

    private boolean updateDisplayPowerStateLocked(int dirty) {
            ……
            //傳入電源保護模式
            updatePowerRequestFromBatterySaverPolicy(mDisplayPowerRequest); 
            ……
            mDisplayManagerInternal.requestPowerState(mDisplayPowerRequest,
                    mRequestWaitForNegativeProximity);//更新顯示裝置的狀態請求
            ……
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
    void updatePowerRequestFromBatterySaverPolicy(DisplayPowerRequest displayPowerRequest) {
        PowerSaveState state = mBatterySaverPolicy.
                getBatterySaverPolicy(ServiceType.SCREEN_BRIGHTNESS, mLowPowerModeEnabled);
        //設定低電模式和螢幕亮度降低的比例,一般都是降低50%
        displayPowerRequest.lowPowerMode = state.batterySaverEnabled;
        displayPowerRequest.screenLowPowerBrightnessFactor = state.brightnessFactor;
    }

    顯示裝置狀態更新的控制器DisplayPowerController.java
    private void updatePowerState() {
        ……
        // 只要開啟電源保護模式,螢幕亮度減半
        // 當然啦,需要大於最小亮度值
        if (mPowerRequest.lowPowerMode) {
            if (brightness > mScreenBrightnessRangeMinimum) {
                // brightnessFactor就是剛才設定的降低亮度的比例,預設是50%
                final int lowPowerBrightness = (int) (brightness * brightnessFactor);
                brightness = Math.max(lowPowerBrightness, mScreenBrightnessRangeMinimum);
                //預設最小亮度值mScreenBrightnessRangeMinimum一般都是1
                brightness = Math.max(brightness / 2, mScreenBrightnessRangeMinimum);
            }
            ……
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

2.3.2 systemui會對此操作進行一下介面上的更新

圖2.2 開啟電源保護後systemui介面
圖2.2 開啟電源保護後systemui介面

Ps:上述是androidO以前的介面,androidO的狀態列是紅色的。

該橙色/紅色是一種status bar的警告Warning的標識(androidO使用的是error標識是紅色),代表正處於電源保護模式(在傳送ACTION_POWER_SAVE_MODE_CHANGING的時候就會通知status狀態列更新)

//StatusBar.java
private void checkBarMode(int mode, int windowState, BarTransitions transitions) {
    final boolean powerSave = mBatteryController.isPowerSave();//check whether you are in power mode
    ……
    if (powerSave && getBarState() == StatusBarState.SHADE) {
        mode = MODE_WARNING;//change mode to power mode,when in status bar normol state
    }
    transitions.transitionTo(mode, anim);
}

//BarTransitions.java
public void draw(Canvas canvas) {
    int targetGradientAlpha = 0, targetColor = 0;
    if (mMode == MODE_WARNING) {
        targetColor = mWarning;//繪製bar的時候,將warning顏色賦值給status bar顯示

mWarning = Utils.getColorAttr(context, android.R.attr.colorError); 

./res/values/themes.xml
<item name="colorError">@color/red</item>

./res/res/values/colors.xml
//androidO使用的是紅色,RGB只有R有顏色
<color name="red">#ffff0000</color>

//f4 51 1e切換成十進位制的話244 81 30,androidO以前使用的顏色
<color name="battery_saver_mode_color">#fff4511e</color><!-- deep orange 600 -->
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

圖2.3 battery_saver_mode_color的顏色值
圖2.3 battery_saver_mode_color的顏色值

紅色就沒必要查看了,只有紅色分量肯定是紅色。

2.3.3 modem進入低電模式

DeviceStateMonitor.java通過接收ACTION_POWER_SAVE_MODE_CHANGED的廣播將modem設定成低電模式。(AndroidO新增,說明google也意識到了功耗屬於android的通病,在modem這一塊也進行優化)

    private void updateDeviceState(int eventType, boolean state) {
            //..….
            case EVENT_POWER_SAVE_MODE_CHANGED:
                if (mIsPowerSaveOn == state) return;
                mIsPowerSaveOn = state;
                //設定裝置進入低電模式
                sendDeviceState(POWER_SAVE_MODE, mIsPowerSaveOn);
                break;
            //..….

private void sendDeviceState(int type, boolean state) {
        // mPhone一般都是telcom通訊相關,mCi是RIL物件,modem相關設定
        mPhone.mCi.sendDeviceState(type, state, null);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.3.4 語音互動功能將關閉

SoundTriggerHelper.java會接收POWER_SAVE_MODE_CHANGED的廣播,語音互動voiceinteraction功能將關閉

    //啟動語音識別
    private int startRecognitionLocked(ModelData modelData, boolean notify) {
        //……
        if (!isRecognitionAllowed()) {//判斷是否執行識別
            //……
            MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
            return STATUS_OK;
        }
        //……

    private boolean isRecognitionAllowed() {
        //在打電話、該服務禁止掉或者省電模式中不允許語言識別功能
        return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.3.5 地理位置資訊進行限制訪問

GnssLocationProvider.java會接收POWER_SAVE_MODE_CHANGED的廣播,限制gps使用,滅屏後會關閉gps

    private void updateLowPowerMode() {
        // 如果裝置是深度睡眠,不再允許使用GPS
        boolean disableGps = mPowerManager.isDeviceIdleMode();
        final PowerSaveState result =
                mPowerManager.getPowerSaveState(ServiceType.GPS);
        switch (result.gpsMode) {//如果有相關GPS設定,一般都有
            case BatterySaverPolicy.GPS_MODE_DISABLED_WHEN_SCREEN_OFF:
                // 預設情況是低電模式在滅屏情況不允許GPS
                disableGps |= result.batterySaverEnabled && !mPowerManager.isInteractive();
                break;
        }
        //……
    } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.3.6 震動將取消

震動將取消(原來如果有震動的話)的操作是在VibratorService.java
當處於電池保護模式下,除了來電顯示的震動(有條件的震動),其它都將關閉震動

//啟動震動的函式
    private void startVibrationLocked(final Vibration vib) {
        //判斷是否允許震動,關於低電模式是在這裡判斷
        if (!isAllowedToVibrate(vib)) {
        //……
        //在鈴聲靜音(且設定了來電震動)或者不在震動模式(且沒有設定來電震動時)的情況下不允許通知型別的鈴聲震動
        if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE &&
                !shouldVibrateForRingtone()) {
        //……
       //當然,如果你沒有許可權,什麼都不行
        if (mode != AppOpsManager.MODE_ALLOWED) {
        //……
    }

//是否允許震動的函式
    private boolean isAllowedToVibrate(Vibration vib) {
        //如果是非低電模式下是允許震動的
        if (!mLowPowerMode) {//onLowPowerModeChanged時會進行設定
            return true;
        }
        //低電模式對於鈴聲震動不受限制
        if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
            return true;
        }
        //是否允許在低電模式下震動,一般這個值預設是false
        if (!mAllowPriorityVibrationsInLowPowerMode) {
            return false;
        }
        //如果允許在低電模式下震動,僅支援alarm,助手、網路電話進行震動
        if (vib.mUsageHint == AudioAttributes.USAGE_ALARM ||
            vib.mUsageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY ||
            vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {

            return true;
        }
        //其它預設是不允許震動的
        return false;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

2.3.7 對於網路資料鏈接會進行限制接入

NetworkPolicyManagerService.java會進行相應的限制接入處理,專門設定了一套FIREWALL_CHAIN_POWERSAVE低電模式的防火牆體系.

1、更新白名單範圍(允許訪問的網路的範圍),範圍之外的將停止socket網路連結

    // 更新防火牆規則,低電模式下enabled==true,chain==FIREWALL_CHAIN_POWERSAVE此模式下防火牆屬於白名單模式,除了白名單以外的都不執行訪問網路
    private void updateRulesForWhitelistedPowerSaveUL(boolean enabled, int chain,
            SparseIntArray rules) {
        if (enabled) {
            // 更新臨時白名單、白名單、除了idleapp之外的白名單都將允許網路訪問
            for (int ui = users.size() - 1; ui >= 0; ui--) {
                UserInfo user = users.get(ui);
                updateRulesForWhitelistedAppIds(uidRules, mPowerSaveTempWhitelistAppIds, user.id);
                updateRulesForWhitelistedAppIds(uidRules, mPowerSaveWhitelistAppIds, user.id);
                if (chain == FIREWALL_CHAIN_POWERSAVE) {
                    updateRulesForWhitelistedAppIds(uidRules,
                            mPowerSaveWhitelistExceptIdleAppIds, user.id);
                }
            }
            // 如果是程序優先順序是前臺服務以上的允許網路訪問(前臺服務一般是PERCEPTIBLE使用者可感知的程序優先順序以上的服務)
            for (int i = mUidState.size() - 1; i >= 0; i--) {
                if (isProcStateAllowedWhileIdleOrPowerSaveMode(mUidState.valueAt(i))) {
                    uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
                }
            }
            setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
        } else {
            setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2、更新所有應用的訪問規則,除了前臺程序和在白名單內的程序,都將不允許訪問網路

    // 更新應用在低電模式下所有app的訪問規則
    private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
        //當uid是media或者drm型別的不需要,或者之前已經授權INTERNET網路訪問的app,不許用更新
        if (!isUidValidForBlacklistRules(uid)) {
            if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
            return RULE_NONE;
        }
        //是否處於空閒態
        final boolean isIdle = !paroled && isUidIdle(uid);
        //如果是低電模式會將進入受限模式
        final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
        //是否前臺程序,和上面isProcStateAllowedWhileIdleOrPowerSaveMode類似
        final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
        //是否處在電源白名單之內
        final boolean isWhitelisted = isWhitelistedBatterySaverUL(uid, mDeviceIdleMode);
        final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
        int newRule = RULE_NONE;
        //......
        if (isForeground) {
            if (restrictMode) {
                //如果是前臺程序,就算是受限模式下也會允許訪問網路
                newRule = RULE_ALLOW_ALL;
            }
        } else if (restrictMode) {
            //其它程序,非白名單將設定成拒絕訪問RULE_REJECT_ALL
            newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
        }
        //......
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

2.3.8 WindowManagerService的動畫將關閉

WindowManagerService.java會將動畫關閉(onLowPowerModeChanged),但是此處由於一些第三方應用如果沒有考慮省電模式關閉動畫的話,可能會出現一些問題(設計時需要考慮動畫在低電模式關閉後的效果)。

public void onLowPowerModeChanged(PowerSaveState result) {
    synchronized (mWindowMap) {
        final boolean enabled = result.batterySaverEnabled;
        //mAllowAnimationsInLowPowerMode代表是否在允許在低電模式下繼續使用動畫(一般是false,就是不允許),如果在低電模式下會把WMS的動畫都關閉
        if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {
            mAnimationsDisabled = enabled;//是否關閉動畫mAnimationsDisabled
            dispatchNewAnimatorScaleLocked(null);//將動畫設定更新到系統中去
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3. 具體使用

我們如果要使用這個新功能還是比較簡單的,

1、在AndroidManifest.xml加上許可權

<uses-permission android:name="android.permission.DEVICE_POWER" />
  • 1

2、在原始碼中呼叫

mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mPowerManager.setPowerSaveMode(mode)//需要DEVICE_POWER許可權
  • 1
  • 2

4. 實際效果

實驗步驟

1、電量是100%

2、登入QQ,然後按home鍵切換到後臺

3、開啟或者關閉省電模式

4、設定螢幕超時30分鐘,1個小時後觀察結果(就是半個小時亮屏半個小時滅屏)

5、開啟關閉省電模式各自取一個結果

=>關閉省電模式的結果
圖4.1 關閉省電模式的結果
圖4.1 關閉省電模式的結果

=>開啟省電模式的結果
圖4.2 開啟省電模式的結果

原文連結