從Bugreport 解讀 Android電量統計原理


應用商店中關於電池管理的應用做的極其絢爛,可耗電應用排行、剩餘時間計算、關閉耗電程序以節省電量等功能是如何實現的,遇到功耗高的問題從哪些方面入手分析和定位,這裏簡要總結如下。
在這裏插入圖片描述

一. 電量值的獲取和計算

首先解釋下各軟硬件耗電量的計算。假設設備(如WIFI)單位時間內消耗的電量爲w,運行時間爲t,則其在這段時間內的耗電量爲W=w*t。根據物理學中的知識,電功率(即所謂電量)計算公式爲W=UIt,其中U爲電壓值,I爲電流值,t爲運行時間。由於在一部機器中,電壓值U是恆定不變的(一般如此),因此可以忽略掉參數U,單獨通過電流及時間即可表示電量(比如電池容量爲2000mA、2500mA等,以mA爲單位進行恆量)。根據以上描述,只要我們獲得了某程序或某設備運行的時間,以及其運行時所需要電流值,則可以計算出其消耗的電量(以上理論會在代碼中體現)。

某程序或硬件設備的運行時間可以分別通過BatteryStats.Uid.ProcBatteryStatsImpl中的相關接口獲得,這裏主要講下電流值(即單位時間消耗電量)的獲取。

a. PowerProfile.java

./frameworks/base/core/java/com/android/internal/os/PowerProfile.java

代碼中定義了幾十種不同設備的耗電類型:

/**     * No power consumption, or accounted for elsewhere.     */
public static final String POWER_NONE = "none;


/**     * Power consumption when CPU is in power collapse mode.     */
public static final String POWER_CPU_IDLE = "cpu.idle";
public static final String POWER_CPU_AWAKE = "cpu.awake";
/**     * Power consumption when CPU is in power collapse mode.     */
@Deprecated    public static final String POWER_CPU_ACTIVE = "cpu.active";
public static final String POWER_WIFI_SCAN = "wifi.scan";
public static final String POWER_WIFI_ON = "wifi.on";
public static final String POWER_WIFI_ACTIVE = "wifi.active";
public static final String POWER_WIFI_CONTROLLER_IDLE = "wifi.controller.idle";
public static final String POWER_WIFI_CONTROLLER_RX = "wifi.controller.rx";
public static final String POWER_WIFI_CONTROLLER_TX = "wifi.controller.tx";
public static final String POWER_WIFI_CONTROLLER_OPERATING_VOLTAGE = "wifi.controller.voltage";

public static final String POWER_BLUETOOTH_CONTROLLER_IDLE = "bluetooth.controller.idle";
public static final String POWER_BLUETOOTH_CONTROLLER_RX = "bluetooth.controller.rx";
public static final String POWER_BLUETOOTH_CONTROLLER_TX = "bluetooth.controller.tx";
public static final String POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE =
       "bluetooth.controller.voltage";

public static final String POWER_GPS_ON = "gps.on";
public static final String POWER_BLUETOOTH_ON = "bluetooth.on";
public static final String POWER_BLUETOOTH_ACTIVE = "bluetooth.active";
public static final String POWER_BLUETOOTH_AT_CMD = "bluetooth.at";
public static final String POWER_SCREEN_ON = "screen.on";
public static final String POWER_RADIO_ON = "radio.on";
public static final String POWER_RADIO_SCANNING = "radio.scanning";
public static final String POWER_RADIO_ACTIVE = "radio.active";
public static final String POWER_SCREEN_FULL = "screen.full";
public static final String POWER_AUDIO = "dsp.audio";
public static final String POWER_VIDEO = "dsp.video";
public static final String POWER_FLASHLIGHT = "camera.flashlight";
public static final String POWER_CAMERA = "camera.avg";
public static final String POWER_CPU_SPEEDS = "cpu.speeds";
public static final String POWER_WIFI_BATCHED_SCAN = "wifi.batchedscan";
public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
  • (1) public double getAveragePower(String type)

此方法返回在type子系統下消耗的電流值,單位爲mA。type可取PowerProfile中定義的常量值,包括POWER_CPU_IDLE(CPU空閒時),POWER_CPU_ACTIVE(CPU處於活動時),POWER_WIFI_ON(WIFI開啓時)等各種狀態。例如,如下調用getAveragePower(POWER_CPU_ACTIVE)將返回CPU處於活動時的電流值;getAveragePower(POWER_WIFI_ON)將返回維持WIFI啓動狀態所需的電流值。結合之前的描述,假設WIFI開啓的時間爲t(假設此段時間未使用WIFI傳輸數據,因爲WIFI傳輸數據需要額外的電能消耗),那麼在此段時間內WIFI所消耗的電量爲W=t*getAveragePower(POWER_WIFI_ON)

  • (2) public double getAveragePower(String type, int level)

相比於方法(1),此接口需要傳入參數level,現在來解釋下level的含義。我們知道,android系統中CPU可以以多種速度運行(假設分別爲600MHz,800MHz,1GHZ等),速度不同時CPU消耗的電量也不同,參數level即代表不同的運行頻率,顯然,方法getAveragePower(String type, int level)將返回type子系統在CPU運行速度級別爲level時單位時間內所消耗的電量(即電流值)。

  • (3) public double getBatteryCapacity() 獲取電池總電量。

  • (4) public int getNumSpeedStepsInCpuCluster(int index) 獲取CPU可以以幾種速度運行。

b. power_profile.xml

事實上,通過閱讀PowerProfile.java代碼及相關注釋即可知,此類中各接口返回的電流等數值都是通過讀取power_profile.xml文件獲得的,即各種子系統消耗的電量值、CPU運行速度值、總電量等信息都是以固定值的形式存儲在power_profile.xml中。由於硬件之間的差異,各子系統耗電信息是不同的,因此此文件需要各生產廠商進行定製。android系統原生的power_profile.xml文件的存放路徑爲:
frameworks/base/core/java/com/android/internal/os/PowerProfile.java
經過各硬件廠商定製後,存放路徑可能發生變化,如高通8952的power_profile.xml路徑:
./device/qcom/msm8952_64/overlay/frameworks/base/core/res/res/xml/power_profile.xml

其內容如下:

<device name="Android">
  <item name="none">0</item>
  <item name="screen.on">80</item>  <!-- ~150mA -->
  <item name="screen.full">300</item>  <!-- ~240mA -->
  <item name="bluetooth.active">5</item> <!-- Bluetooth data transfer, ~5mA -->
  <item name="bluetooth.on">0.1</item>  <!-- Bluetooth on & connectable, but not connected, ~0.1mA -->
  <item name="wifi.on">3</item>  <!-- ~3mA -->
  <item name="wifi.active">200</item>  <!-- WIFI data transfer, ~200mA -->
  <item name="wifi.scan">100</item>  <!-- WIFI network scanning, ~100mA -->
  <item name="dsp.audio">10</item> <!-- ~10mA -->
  <item name="dsp.video">50</item> <!-- ~50mA -->
  <item name="camera.flashlight">200</item> <!-- Avg. power for camera flash, ~200mA -->
  <item name="camera.avg">650</item> <!-- Avg. power use of camera in standard usecases, ~650mA -->
  <item name="radio.active">180</item> <!-- ~200mA -->
  <item name="radio.scanning">42</item> <!-- cellular radio scanning for signal, ~10mA -->
  <item name="gps.on">50</item> <!-- ~50mA -->
  <!-- Current consumed by the radio at different signal strengths, when paging -->
  <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
      <value>2</value> <!-- ~2mA -->
      <value>1</value> <!-- ~1mA -->
  </array>
  <!-- A list of heterogeneous CPU clusters, where the value for each cluster represents the
       number of CPU cores for that cluster.       Ex:
       <array name="cpu.clusters.cores">
         <value>4</value> // cluster 0 has cpu0, cpu1, cpu2, cpu3
         <value>2</value> // cluster 1 has cpu4, cpu5
       </array> -->
  <array name="cpu.clusters.cores">
      <value>4</value>
      <value>4</value>
  </array>
    <!-- Different CPU speeds for cluster 0 as reported in       /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state.
       There must be one of these for each cluster, labeled:      cpu.speeds.cluster0, cpu.speeds.cluster1, etc... -->
  <array name="cpu.speeds.cluster0">
      <value>400000</value> <!-- 400 MHz CPU speed -->
      <value>691200</value> <!-- 691 MHz CPU speed -->
      <value>806400</value> <!-- 800 MHz CPU speed -->
      <value>1017600</value> <!-- 1000 MHz CPU speed -->
      <value>1190400</value> <!-- 1200 MHz CPU speed -->
      <value>1305600</value> <!-- 1300 MHz CPU speed -->
      <value>1382400</value> <!-- 1380 MHz CPU speed -->
      <value>1401600</value> <!-- 1400 MHz CPU speed -->
  </array>
  <!-- Current at each CPU speed for cluster 0, as per 'cpu.speeds.cluster0'.
       Like cpu.speeds.cluster0, there must be one of these present for     each heterogeneous CPU cluster. -->
  <array name="cpu.active.cluster0">
      <value>180</value>  <!-- ~180mA -->
      <value>210</value>  <!-- ~210mA -->
      <value>260</value>  <!-- ~260mA -->
      <value>300</value>  <!-- ~300mA -->
      <value>400</value>  <!-- ~400mA -->
      <value>480</value>  <!-- ~480mA -->
      <value>510</value>  <!-- ~510mA -->
      <value>540</value>  <!-- ~540mA -->
  </array>

  <!-- Current at each CPU speed for cluster 1, as per 'cpu.speeds.cluster0'.
       Like cpu.speeds.cluster1, there must be one of these present for    each heterogeneous CPU cluster. -->
 <array name="cpu.speeds.cluster1">
      <value>400000</value> <!-- 400 MHz CPU speed -->
      <value>883200</value> <!-- 883 MHz CPU speed -->
      <value>940800</value> <!-- 940 MHz CPU speed -->
      <value>998400</value> <!-- 998 MHz CPU speed -->
      <value>1056000</value> <!-- 1056 MHz CPU speed -->
      <value>1113600</value> <!-- 1113 MHz CPU speed -->
      <value>1190400</value> <!-- 1190 MHz CPU speed -->
      <value>1248000</value> <!-- 1248 MHz CPU speed -->
      <value>1305600</value> <!-- 1305 MHz CPU speed -->
      <value>1382400</value> <!-- 1382 MHz CPU speed -->
      <value>1612800</value> <!-- 1612 MHz CPU speed -->
      <value>1747200</value> <!-- 1747 MHz CPU speed -->
      <value>1804800</value> <!-- 1804 MHz CPU speed -->
  </array>
  <!-- Current at each CPU speed for cluster 1, as per 'cpu.speeds.cluster0'.
       Like cpu.speeds.cluster1, there must be one of these present for     each heterogeneous CPU cluster. -->
  <array name="cpu.active.cluster1">
      <value>180</value>  <!-- ~180mA -->
      <value>230</value>  <!-- ~230mA -->
      <value>250</value>  <!-- ~250mA -->
      <value>310</value>  <!-- ~310mA -->
      <value>330</value>  <!-- ~330mA -->
      <value>360</value>  <!-- ~360mA -->
      <value>390</value>  <!-- ~390mA -->
      <value>410</value>  <!-- ~410mA -->
      <value>440</value>  <!-- ~440mA -->
      <value>460</value>  <!-- ~460mA -->
      <value>500</value>  <!-- ~500mA -->
      <value>550</value>  <!-- ~550mA -->
      <value>600</value>  <!-- ~600mA -->
  </array>
  <!-- Current when CPU is idle -->
  <item name="cpu.idle">18</item>
  <!-- This is the battery capacity in mAh (measured at nominal voltage) -->  <item name="battery.capacity">3000</item>
  <array name="wifi.batchedscan"> <!-- mA -->
      <value>.0002</value> <!-- 1-8/hr -->
      <value>.002</value>  <!-- 9-64/hr -->
      <value>.02</value>   <!-- 65-512/hr -->
      <value>.2</value>    <!-- 513-4,096/hr -->
      <value>2</value>    <!-- 4097-/hr -->
  </array>
</device>

高通MSM8952依然是八核心Cortex-A53架構設計,其中“大核”部分的四顆核心主頻最高1.7GHz,“小核”部分的四顆核心頻率最高1.2GHz。所以,在power_profile.xm中,此型號機器配置了兩個cluster(0,1)name="cpu.speeds.cluster0"及兩個cluster下不同速度下的耗電量name=“cpu.active.cluster0”。

通過對比代碼段1可知,PowerProfile.java中定義的常量即對應於power_profile.xml中各屬性名。因此,PowerProfile.java只是用於讀取power_profile.xml的接口而已,後者纔是存儲系統耗電信息的核心文件。並且,其中的各項值通常都會根據實際情況做校準或調整。

二. 應用功耗的計算方法

App耗電量統計processAppUsage()硬件耗電量統計processMiscUsage()

第一部分:App耗電量統計:processAppUsage()

processAppUsage 是上一次拔掉設備後 ~ 至今 的App耗電量統計。
具體的統計流程 都在for循環裏,所以processAppUsage真實統計是Uid。
Uid與App關係:2個App簽名和sharedUserId相同,則在運行時,他們擁有相同Uid。就是說processAppUsage統計的可能是多個App的耗電量數據,對於普通App,出現這種情況的機率較少,而對於Android系統應用則較爲常見。

耗電量計算公式 - 部分1:計算Uid屬下每個Process的耗電量數據,並求和。

Uid_Power1 = (Process1_Power ++ ProcessN_Power);
       Process1_Power = (CPUSpeed_Time * POWER_CPU_ACTIVE);
       ...
       ProcessN_Power = (CPUSpeed_Time * POWER_CPU_ACTIVE);

也就是每一個uid包含的所有pid的cpu的運行時間 * power_profile.xml中配置的

<array name="cpu.active">
   <value>19</value>
      ....
</array>

不同cpu的頻率有不同的cpu.active與之對應。

耗電量計算公式 - 部分2:計算Uid的wake lock耗電量

這裏,Android只計算了partial wake lock的耗電量。

Uid_Power2 = PartialWakeLock_Time * POWER_CPU_WAKE 
(power_profile.xml中配置的<item name="cpu.awake">70</item>)

耗電量計算公式 - 部分3:計算Uid的數據流量(data traffic)耗電量

Uid_Power3 = (app.mobileActive * mPowerRadioOn) / (1000*60*60)
或者
Uid_Power3 = (app.mobileRxPackets + app.mobileTxPackets)
           * getMobilePowerPerPacket(rawRealtimeUs, statsType)

耗電量計算公式 - 部分4:計算Uid WIFI耗電量。

Uid_Power4 = wifiRunningTimeMs * POWER_WIFI_ON
  (power_profile.xml中配置的<item name="wifi.on">4</item> )
  
Uid_Power5 = wifiScanTimeMs * POWER_WIFI_SCAN
 (power_profile.xml中配置的<item name="wifi.scan">88</item> )
 
Uid_Power6 = batchScanTimeMs * POWER_WIFI_BATCHED_SCAN
 (power_profile.xml中配置的<array name="wifi.batchedscan"> )

耗電量計算公式 - 部分5:計算Uid其他傳感器耗電量。

Uid_Power7 = (Sensor1_Power + … + SensorN_Power)
Sensor_Power = Sensor_Time * Power_Sensor

總結App耗電量計算公式:

Uid_Power(App耗電量,單位:mAh) = Uid_Power1 + Uid_Power2 + Uid_Power3 + Uid_Power4 + Uid_Power5 +Uid_Power6 +Uid_Power7
Uid_Power1 = (Process1_Power ++ ProcessN_Power);
Uid_Power2 = PartialWakeLock_Time * POWER_CPU_WAKE             
Uid_Power3 = (app.mobileActive * mPowerRadioOn) / (1000*60*60)
或者
Uid_Power3 = (app.mobileRxPackets + app.mobileTxPackets)
* getMobilePowerPerPacket(rawRealtimeUs, statsType)
Uid_Power4 = wifiRunningTimeMs * POWER_WIFI_ON
Uid_Power5 = wifiScanTimeMs * POWER_WIFI_SCAN
Uid_Power6 = batchScanTimeMs * POWER_WIFI_BATCHED_SCAN
Uid_Power7 = (Sensor1_Power ++ SensorN_Power)

注意除了以上的計算方法以爲還有一部分需要注意:
一旦有應用申請partial wake lock 除了wake lock計算一部分耗電量還有部分

if (num != 0) {
   for (int i=0; i<N; i++) {
       StopwatchTimer st = mPartialTimers.get(i);
       if (st.mInList) {
           Uid uid = st.mUid;
           if (uid != null && uid.mUid != Process.SYSTEM_UID) {
               int myUTime = utime/num;
               int mySTime = stime/num;
               utime -= myUTime;
               stime -= mySTime;
               num--;
               Uid.Proc proc = uid.getProcessStatsLocked("*wakelock*");
               proc.addCpuTimeLocked(myUTime, mySTime);
               proc.addSpeedStepTimes(cpuSpeedTimes);
}     }      }      }       

wakelock會在內部維護一個進程並統計他自己佔用cpu的時間,因此經常會看到好多uid中都會有這樣的進程存在。

Proc *wakelock*:
      CPU: 3s 40ms usr + 5s 50ms krn ; 0ms fg

以上是整體的一個app的計算方法,也就是除了硬件耗電統計的那些條目所有有uid的應用都是這樣計算的。

三. 功耗問題分析定位及優化

a. 準備

設置中電池的截圖,adb dumpsys bugreport > bugreport.txtadb dumpsys batterystats > batterystats.txt, 及 離線日誌。

b. 日誌分析:

1). 分析這個標誌 Estimated power use (mAh)

Estimated power use (mAh):
Capacity: 3000, Computed drain: 792, actual drain: 1320-1350

電池總容量:3000 mAh, 統計到的耗電量:792 mAh, 真實耗電量:1320mAh
注意:(真實耗電電量回避統計到的耗電量多,這個是正常的,因爲各種聲音的播放系統目前是無法統計到。同時底層對各個模塊的功耗值也就是powerprofile.txt這個文件的配置有時是不精確導致。如果統計到的耗電量比真實耗電量多時,那麼就是算法已經出問題了,需要調查計算方法以及統計時間)

Unaccounted: 528        //        統計不到的耗電量:528 mAh
Screen: 136                  //       統計的屏幕的耗電量:136 mAh
Uid u0a151: 111         

搜索u0a151找到對應這個uid的應用如下所示:

u0a151:
     Mobile network: 73.29KB received, 103.96KB sent (packets 1040 received, 1343 sent)
     Mobile radio active: 3h 35m 21s 572ms (32.6%) 22x @ 5422 mspp
     Wi-Fi network: 172.93KB received, 202.58KB sent (packets 1836 received, 2450 sent)
     Wifi Running: 0ms (0.0%)
     Full Wifi Lock: 0ms (0.0%)
     Wifi Scan: 21s 6ms (0.1%)
     Wake lock PushService: 2s 677ms partial (29 times) realtime
     Wake lock *sync*/com.sina.weibo.sync.authority/com.sina.weibo.account/Letv_RaykinX: 1m 12s 246ms partial (9 times) realtime
     Wake lock *alarm*: 3s 568ms partial (15 times) realtime
     TOTAL wake: 1m 18s 491ms partial realtime
     Sync com.sina.weibo.sync.authority/com.sina.weibo.account/Letv_RaykinX: 2m 1s 556ms realtime (10 times)
     Foreground activities: 852ms realtime (1 times)
     Foreground for: 2h 39m 59s 616ms
     Active for: 11h 8m 38s 330ms
     Proc com.sina.weibo:remote:
         CPU: 44s 830ms usr + 13s 710ms krn ; 0ms fg
             6 proc starts
     Proc *wakelock*:
         CPU: 24s 550ms usr + 4s 920ms krn ; 0ms fg
     Proc com.sina.weibo:
         CPU: 45s 280ms usr + 12s 230ms krn ; 410ms fg
           6 proc starts

可以看到新浪微薄的每一項耗電的統計總和爲111 mAh,其它UID的與此類似,如

Uid 1000: 95.2
Uid 0: 82.8
 Cell standby: 66.7        //        移動網絡待機的功耗:66.7 mAh
Uid u0a116: 64.3
Idle: 50.8                     //        手機待機的功耗:50.8 mAh

通過Estimated power use裏面的內容找出那個應用或硬件的功耗較高,此log需要與截圖對應看,功耗順序都是從大到小排列log與截圖一致。測試如果提出某一項功耗異常時可以先看每一項的功耗。

2). 應用功耗異常的分析定位

(1) 查看功耗統計中的異常值

查看應用移動無線裝置運行時間也就是Uid_Power3中提到的算法發現應用功耗較高時詳細分析這個應用的uid對應的統計的每一項信息,查找異常例如上述的統計中Mobile radio active: 3h 35m 21s 572ms (32.6%) 22x
@ 5422mspp(packets 1040 received, 1343 sent)傳輸2000多個數 據包需要三個小時這個時間就是比較異常的。

eg: u0a151:
 Mobile network: 73.29KB received, 103.96KB sent (packets 1040 received, 1343 sent)
 Mobile radio active: 3h 35m 21s 572ms (32.6%) 22x @ 5422 mspp
(2).查看應用申請的鎖wake lock 這項的統計時間申請鎖時間較長功耗較高(此部分也將kernel的鎖表示出來)

這三個標誌分別表示底層kernel的鎖和partial鎖還有系統喚醒的原因。

eg:  All kernel wake locks:
         Kernel Wake lock DIAG_WS     : 1h 45m 10s 261ms (269788 times) realtime

這個kernel 鎖名字DIAG_WS以及使用時間 1h 45m 10s 261ms這樣的鎖會導致系統不能進入深度睡眠, (269788 times) 申請鎖的次數因此會功耗較高。

eg:  All partial wake locks:
       Wake lock 1000 LocationService: 15h 17m 27s 726ms (1 times) realtime

這個partial 鎖申請這個鎖的是LocationService申請的,LocationService這個也是申請鎖時候的TAG ,申請的時間 15h 17m 27s 726ms,(1 times)申請鎖的次數。

eg:  All wakeup reasons:
        Wakeup reason unknown: 656ms (7 times) realtime
        Wakeup reason Abort:Last active Wakeup Source: eventpoll: 454ms (1 times) realtime

表示各種喚醒的原因,454ms喚醒系統的時間, (1 times)喚醒系統的次數。
這個次數如果是(0 times)說明這個鎖一直沒有被釋放掉。

(3).查看應用使用sensor的時間sensor使用時間較長功耗較高

注意:sensor handle 與類型的對應。

adb shell dumpsys sensorservice > sensorservice.log

finger wakeup sensor| Qualcomm Technologies, Inc.| version=1 |android.sensor.wake_gesture| 0x0000002b
 | "" | type=23 | one-shot | maxDelay=0us |minDelay=0us |no batching | wakeUp | last=<>

eg:      1000:
            Sensor 43: 8h 10m 44s 804ms realtime (60 times)

這個sensor 43就是sensor的hanldle 對應的16進制就是0x0000002b,查看類型type=23,sensor名稱:android.sensor.wake_gesture
對於gps耗電會在對應應用的uid下面詳細的顯示出使用的時間Sensor GPS: 9h 13m 25s 124ms

 eg:
  u0a117:
      Wi-Fi network: 358.49KB received, 97.48KB sent (packets 610 received, 658 sent)
      Wifi Running: 0ms (0.0%)
      Full Wifi Lock: 0ms (0.0%)
      Wifi Scan: 5s 765ms (0.0%)
      Wake lock *alarm*: 671ms partial (29 times) realtime
      Wake lock LocationManagerService realtime
      TOTAL wake: 671ms partial realtime
      Sensor GPS: 9h 13m 25s 124ms realtime (0 times)
      Foreground for: 9h 13m 25s 124ms
      Proc com.tencent.mobileqq:
       CPU: 16s 150ms usr + 4s 410ms krn ; 0ms fg
        3 proc starts
      Proc com.tencent.mobileqq:MSF:
        CPU: 7s 0ms usr + 2s 70ms krn ; 0ms fg
      Apk com.tencent.mobileqq:
        39 wakeup alarms
      Service com.tencent.mobileqq.app.CoreService$KernelService:
        Created for: 14m 46s 370ms uptime
        Starts: 2, launches: 2
      Service com.tencent.mobileqq.app.CoreService:
        Created for: 14m 46s 849ms uptime
        Starts: 2, launches: 2
(4).查看應用使用的cpu的時間使用時間長功耗較高。

例如:android操作系統和系統服務中也就是uid爲0和uid爲1000的每一個包含的進程使用cpu的時間。

0:
    Proc kworker/u16:0:
        CPU: 0ms usr + 38m 27s 10ms krn ; 0ms fg
    Proc kworker/u16:1:
        CPU: 0ms usr + 39m 33s 120ms krn ; 0ms fg
    Proc kworker/u16:2:
        CPU: 0ms usr + 29m 15s 210ms krn ; 0ms fg
    Proc kworker/u16:3:
        CPU: 0ms usr + 39m 28s 140ms krn ; 0ms fg
    Proc kworker/u16:4:
        CPU: 0ms usr + 41m 56s 730ms krn ; 0ms fg
    Proc kworker/u16:5:
        CPU: 0ms usr + 33m 24s 810ms krn ; 0ms fg

這就是異常的一種情況。這幾個進程使用cpu時間過長,會導致功耗較多。

3). 硬件功耗高的定位方式

對應硬件使用的時間如下所示:

Statistics since last charge:          
System starts: 0, currently on battery: true          
Time on battery: 9h 13m 25s 124ms (100.0%) realtime, 34m 58s 669ms (6.3%) uptime
    // Time on battery-->統計運行的總時間9h 13m 25s 124ms          
    
Time on battery screen off:8h 59m 14s 895ms(97.4%)realtime, 20m 48s 441ms(3.8%)uptime
Time on battery screen off-->統計滅屏的時間8h 59m 14s 895ms          
Total run time: 9h 13m 25s 83ms realtime, 34m 58s 628ms uptime          
Battery time remaining: 1d 12h 8m 1s 242ms          
Start clock time: 2018-02-16-21-09-37          
Screen on: 14m 10s 229ms (2.6%) 2x, Interactive: 14m 9s 947ms (2.6%)          
Time on battery screen off-->統計滅屏的時間8h 59m 14s 895ms         
 
Screen brightnesses:            
 dark 4m 6s 686ms (29.0%)            
 dim 10m 3s 543ms (71.0%)          
屏幕量度分等級:統計電量。

對於屏幕會有兩部分計算的和組成:

屏幕亮屏的時間(根據屏幕的狀態來獲取時間長度) * power_profile.xml中配置的<item name="screen.on">80</item> + 屏幕亮度等級持續的時間(屏幕亮度分爲5個等級:分別計算5個等級的亮度對應的時間)* power_profile.xml中配置的<item name="screen.full">300</item>

Total full wakelock time: 2m 8s 857ms          
Total partial wakelock time: 11m 8s 140ms         

申請鎖的時間一種是full wakelock 一種是partial wakelock。目前電量的統計中只統計partial wakelock這個鎖的耗電。

Mobile total received: 0B, sent: 0B (packets received 0, sent 0)          
Phone signal levels:            
 moderate 29s 773ms (0.1%) 5x           
 good 9h 7m 51s 689ms (99.0%) 13x            
 great 5m 3s 662ms (0.9%) 8x         
 Signal scanning time: 0ms            

Radio types:               
 none 9h 13m 23s 439ms (100.0%) 1x               
 lte 1s 685ms (0.0%) 1x           
 Mobile radio active time: 0ms (0.0%) 0x          

這部分是移動網絡待機的耗電詳細情況,計算方式如下:

移動網絡待機–耗電統計:
  • 1.信號在不同強弱等級持續的時間 * power_profile.xml中配置的
<array name="radio.on"> <!-- Strength 0 to BINS-1 -->              
<value>3.4</value> <!-- ~2mA -->              
<value>3.4</value> <!-- ~1mA -->          
</array>          
  • 2.搜索網絡的時間 * power_profile.xml中配置的 <item name="radio.scanning">80</item>

  • 3.(使用數據流量上傳下載的時間 - 每一個應用上傳下載數據包所用的時間) * power_profile.xml中配置的 <item name="radio.active">170</item>

三部分總體的功耗作爲移動網路待機耗電統計.

對應的類型:BatterySipper.DrainType.CELL

Wi-Fi total received: 19.88MB, sent: 1.19MB (packets received 18726, sent 13286) 
Wifi on: 2h 42m 39s 290ms (29.4%),
Wifi running: 9h 13m 24s 969ms (100.0%)           
Wifi states: (no activity)           
Wifi supplicant states:              
associating 3ms (0.0%) 1x              
associated 3ms (0.0%) 1x              
4-way-handshake 7ms (0.0%) 1x              
group-handshake 45ms (0.0%) 1x              
completed 9h 13m 25s 66ms (100.0%) 1x           
Wifi signal levels: 
   level(1) 9s 26ms (0.0%) 2x             
   level(2) 5m 50s 226ms (1.1%) 22x              
   level(3) 9h 7m 25s 872ms (98.9%) 20x            

Wifi的功耗統計詳情統計wifi的耗電量會減掉每個應用消耗的wifi的電量,並且不包含wifi掃描消耗電量的統計。

Bluetooth on: 0ms (0.0%)           
Bluetooth states: (no activity)           
藍牙的功耗統計詳情。

補充兩個算法:

手機待機—耗電統計

(統計的耗電總時間 - 屏幕亮屏的時間 )* power_profile.xml中配置的<item name="cpu.idle">18</item> 對應的類型:BatterySipper.DrainType.IDLE

語音通話—耗電統計
TelephonyRegistry.java -> broadcastCallStateChanged -> 電話的狀態爲CALL_STATE_RINGING 和CALL_STATE_OFFHOOK時統計的時間 * power_profile.xml中配置的<item name="radio.active">180</item>

對應的類型:BatterySipper.DrainType.PHONE

4). 分析Battery History 這個標誌

查看每一個進程申請鎖的頻率。

Battery History (1% used, 30KB used of 2048KB, 156 strings using 10084):
  0 (9) RESET:TIME: 2016-02-16-21-09-37
+1s793ms (2) 100 f0500018 +wake_lock_in=1000:"*walarm*:com.android.server.WifiManager.action.START_SCAN"
 +1s793ms--->標示距離統計時間的時間間隔, 100 --> 電量,  +wake_lock_in 申請鎖的標誌。
 "*walarm*:com.android.server.WifiManager.action.START_SCAN"是AlarmManager組成的字符串作爲申請鎖的tag。
 +1s793ms (2) 100 f0500018 -wake_lock_in=1000:"*walarm*:com.android.server.WifiManager.action.START_SCAN"
 +1s793ms--->標示距離統計時間的時間間隔, 100 --> 電量,  -wake_lock_in 釋放鎖的標誌。
*walarm*這個alarm是系統的一種喚醒alarm能夠在系統休眠狀態下將系統喚醒,如果頻繁使用這個alarm會引起待機功耗較高。

查看每個進程申請鎖和釋放鎖的時間間隔。時間間隔較長,說明對鎖的使用需要優化。不過,從日誌來看,BatteryHistory的日誌基本是按時間先後順序依次累積的,除了明確問題發生時間段(相對不長)用這種方法好使外,逐個查看數天的記錄,確實是個體力活,也不太現實。

5). 通過工具解析bugreport.txt 文件

工具對應的網址:https://github.com/google/battery-historian
注意需要使用vpn環境下配置環境,按照步驟解析文件。

(1).查看這個標識時間Kernel Overhead Time時間比較長說明系統沒有休眠,一直運行。
(2).看這個Kernel wakesources標識kernel的一些進程申請鎖的情況如何,說明系統沒有休眠申請鎖時間較長功耗較大。
(3).看這個Userspace partial wakelocks標識標識上層應用申請partial wakelock的情況,說明系統沒有休眠申請鎖時間較長功耗較大。
(4).查看Historian 2.0可以完整的耗電情況。

需要關注分析的項如下所示:

 1、CPU running 這項有條紋標誌表示系統沒有進入休眠模式。
 2、Partial wakelock 這項有條紋標誌表示系統申請的Partial類型的鎖。
 3、Screen 這項有條紋標誌表示屏幕亮屏
 4、Brightness 這項有條紋標誌表示屏幕亮度的等級。(屏幕亮度分爲5個等級)
 5、SyncManager app 這項有條紋標誌表示某個應用正在做sync的動作。用鼠標點擊可以看到那個進程對應可以看到包名做sync。
 6、Wifi scan 這項有條紋標誌表示進行wifi的掃描。
 7、Mobile radio 這項有條紋標誌表示使用流量上傳過程中傳輸數據包的時間。
 8、Data connection 這項有條紋標誌表示有數據連接,說明有應用使用數據流量。
 9、Signal strength 這項有條紋標誌表示電話的有信號不同顏色的條紋表示信號強弱不同用鼠標點擊可以看到級別不同的信號強度耗電情況也不一樣(上面對這個算法有描述)。
 10、Network connectivity 這項有條紋標誌表示有網絡連接,鼠標點擊時會顯示出什麼類型的網絡連接(TYPE_WIFI, TYPE_MOBILE)。
 11、Sensor 這項有條紋標誌表示有不同類型的sensor被使用。
 12、Top app 這項有條紋標誌表示有app正在使用,在屏幕的第一個界面使用中。
 13、Foreground process 這項有條紋標誌表示後臺運行的進程。
 14、Active process 這項有條紋標誌表示運行在前臺的進程。
 15、Wifi running 這項有條紋標誌表示wifi運行。
 16、Wifi on 這項有條紋標誌表示wifi開啓。
 17、charging status 這項有條紋標誌表示充電的狀態。
 18、Audio 這項有條紋標誌表示有聲音播放。
 19、Phone call 這項有條紋標誌表示撥打或接聽電話。
 20、Phone scanning 這項有條紋標誌表示無卡掃描。
 21、中間的藍色的線表示整個電池耗電的情況,上升表示充電,下降表示放電。
 22、最下面的座標軸表示時間軸,最右面的座標軸表示電池電量值。       

附:發現一篇完整介紹Android BatteryStatService代碼結構和流程的文章,分享下

https://blog.csdn.net/Gaugamela/article/details/52931949 Android7.0 BatteryStatsService
https://wu-being.blog.csdn.net/article/details/88319690 從硬件角度和低層作功耗優化的總結建議

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章