从Bugreport 解读 Android电量统计原理


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/res/res/xml/power_profile.xml

经过各硬件厂商定制后,存放路径可能发生变化,如高通8952的power_profile.xml路径:
./device/[平台]/[项目]/overlay/frameworks/base/core/res/res/xml/power_profile.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的应用都是这样计算的。

第二部分:硬件耗电量统计

processMiscUsage()
具体与Android相关版本代码实现,有待研究

三. 功耗问题分析定位及优化

a. bugreport 获取

设置中电池的截图,adb dumpsys bugreport > bugreport.txtadb dumpsys batterystats > batterystats.txt, 及 离线日志。

b. bugreport 分析

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         
关于Estimated power use (mAh) Unaccounted

实际上Android 无法直接将电池使用情况归因于应用,将CPU,收音机,wifi,相机,GPS和蓝牙等其他东西归功于应用程序,然后只是说“1小时的GPS消耗10mAh的电池和这个应用程序使用半小时的GPS,因此5mAh的电池”,并加上所有的数字。 这可以估算应用消耗的电量。 它不能保证准确。

当设备上所有应用的所有估算值加起来小于所用电池的数量时,您会收到Unaccounted “未计入”。 例如。 如果您的电池电量用了90%且设备上的所有应用程序合计估计已使用了60%,那么有30%是未计算的。

我强烈建议你完全忽略这些估计。 查看原始统计数据通常会告诉您更多信息。

搜索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与截图一致。测试如果提出某一项功耗异常时可以先看每一项的功耗。

Per-PID Stats:
  • PID 2616 wake time: +13m59s933ms
  • PID 2616 wake time: +7m4s223ms
    com.google.android.gms.persistent (pid 2616)
Estimated power use (mAh)
  • Uid 1036: 1087 ( cpu=1087 ) Excluded from smearing

  • Uid 1000: 736 ( cpu=736 wake=0.0140 sensor=0.0834 ) Excluded from smearing

    UID PACKAGE BYTES NUM
    1000 system 184594 27
    0 root 14060 -3.0% 5
    10048 com.android.systemui 8570
    10021 com.google.android.gms 6050 +3.8%
    2000 shell 2278 1
    1036 auditd 1960

Per-app mobile ms per packet

Uid u0a74: 336 (1 packets over 336ms) 1x
Uid 1000: 326 (1242 packets over 6m 44s 299ms) 319x
Uid 0: 235 (1665 packets over 6m 31s 33ms) 329x
Uid u0a37: 215 (676 packets over 2m 25s 506ms) 5x
Uid u0a21: 175 (1822 packets over 5m 19s 199ms) 8x
u0a21:com.google.android.gms

All kernel wake locks

Kernel Wake lock PowerManagerService.WakeLocks : 1h 27m 33s 216ms (410182 times) realtime
Kernel Wake lock qcril : 4m 21s 669ms (426174 times) realtime
Kernel Wake lock IPA_WS : 2m 19s 717ms (2450 times) realtime
Kernel Wake lock qcom_rx_wakelock : 2m 7s 415ms (790 times) realtime
Kernel Wake lock ipc000000f8_2608_main : 1m 14s 452ms (432698 times) realtime
Kernel Wake lock IPCRTR_mpss_rx : 1m 0s 487ms (448065 times) realtime
Kernel Wake lock NETLINK : 44s 897ms (10835 times) realtime
Kernel Wake lock [timerfd] : 29s 139ms (1215 times) realtime
Kernel Wake lock mpss_IPCRTR : 20s 369ms (940759 times) realtime
Kernel Wake lock IPCRTR_lpass_rx : 1s 595ms (3343 times) realtime

All partial wake locks

Wake lock 1001 RILJ: 37m 19s 811ms (426160 times) max=340 actual=2300034 realtime
Wake lock u0a34 job/com.sprint.sdcplus/.receivers.NetworkJobScheduler: 16m 50s 356ms (8 times) max=600015 actual=2244548 realtime
Wake lock u0a39 job/com.sprint.ms.smf.services/com.sprint.ms.smf.internal.push.FcmRegisterJobService: 16m 49s 855ms (9 times) max=600018 actual=2239032 realtime

All wakeup reasons

Wakeup reason Abort:Last active Wakeup Source: PowerManagerService.WakeLocks: 4m 12s 973ms (22744 times) realtime
Wakeup reason Abort:Pending Wakeup Sources: PowerManagerService.WakeLocks : 40s 535ms (1795 times) realtime
Wakeup reason unknown: 3m 25s 503ms (341 times) realtime
Wakeup reason Abort:Pending Wakeup Sources: PowerManagerService.WakeLocks qcril : 5s 921ms (254 times) realtime
Wakeup reason Abort:Last active Wakeup Source: qcril: 1s 146ms (100 times) realtime

Process statistics in detail

u0a21: (com.google.android.gms)
Mobile network: 17.93KB received, 116.42KB sent (packets 201 received, 1621 sent)
WiFi AP wakeups: 342
Wake lock net_scheduler: 11m 16s 765ms partial (88 times) max=190036 actual=837470 realtime
Wake lock dexopt realtime
Wake lock NlpWakeLock: 464ms partial (101 times) max=136 actual=1655 realtime

Dumpsys alarm

Top Alarms:
+1m19s161ms running, 0 wakeups, 2 alarms: 1000:android
alarm:com.android.server.action.NETWORK_STATS_POLL
+58s163ms running, 12 wakeups, 12 alarms: u0a21:com.google.android.gms
walarm:com.google.android.intent.action.GCM_RECONNECT
+14s57ms running, 11 wakeups, 11 alarms: u0a21:com.google.android.gms
walarm:com.google.android.gms.gcm.ACTION_CHECK_QUEUE
Alarm Stats:
1000:android +1m22s235ms running, 21 wakeups:
u0a21:com.google.android.gms +1m15s306ms running, 28 wakeups:

Dumpsys sensorservice

Active sensors:
WakeLock Status: not held
0 active connections
0 direct connections
Previous Registrations:
09:54:57 - 0x00000006 pid= 1722 uid= 1000 package=com.android.server.display.AutomaticBrightnessController
09:54:47 + 0x00000001 pid= 1722 uid= 1000 package=com.android.server.policy.WindowOrientationListener samplingPeriod=66667us batchingPeriod=100000us
09:44:41 - 0x00000005 pid= 1979 uid=10048 package=com.android.systemui.classifier.FalsingManager
09:38:13 - 0x00000001 pid= 2616 uid=10021 package=awuk
09:38:09 + 0x00000001 pid= 2616 uid=10021 package=awuk samplingPeriod=20000us batchingPeriod=0us
09:38:09 - 0x00000001 pid= 2616 uid=10021 package=awrk
09:38:07 - 0x00000001 pid= 2616 uid=10021 package=awuk

pid 2616 [com.google.android.gms.persistent]

Pms wakelock
  • Kernel Wake lock PowerManagerService.WakeLocks : 1h 27m 33s 216ms (410182 times) realtime
  • Refer to KBA-170331010505
IPC_binder wakelock
  • Get the IPC logs:

echo 0x2 > /sys/module/ipc_router_core/parameters/debug_mask
[ 119.151740] [IPCRTR] CLI RX Len:0x81 T:0x1 CF:0x0 SVC:<0x10:0x2> SRC:<0x3:0x29> DST:<0x1:0x66> DATA: 24006604 01007a00

  • Check the service type ID, what’s the IPC message
Sub-system sleep failure

Get the RPM and sub-system vddmin counter

cat /sys/kernel/debug/rpm_stats
cat /sys/kernel/debug/rpm_master_stats
Kernel wake up
  • Enable wake up irq debug nodes

    echo 1 > /sys/module/msm_show_resume_irq/parameters/debug_mask

  • Refer to KBA-170629201457

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%)          
(1).屏幕亮度等级电量统计

对于屏幕会有两部分计算的和组成:

屏幕亮屏的时间(根据屏幕的状态来获取时间长度) * 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          

这部分是移动网络待机的耗电详细情况,计算方式如下:

(2).移动网络待机耗电统计
  • 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            
(3).Wifi的功耗统计

统计wifi的耗电量会减掉每个应用消耗的wifi的电量,并且不包含wifi扫描消耗电量的统计。

Bluetooth on: 0ms (0.0%)           
Bluetooth states: (no activity)           
(4).蓝牙的功耗统计
补充两个统计算法
a.手机待机—耗电统计

(统计的耗电总时间 - 屏幕亮屏的时间 )* power_profile.xml中配置的<item name="cpu.idle">18</item>

对应的类型:BatterySipper.DrainType.IDLE

b.语音通话—耗电统计
       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的日志基本是按时间先后顺序依次累积的,除了明确问题发生时间段(相对不长)用这种方法好使外,逐个查看数天的记录,确实是个体力活,也不太现实。

c. 通过可视化工具解析bugreport

1). battery-historian

工具对应的网址: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 从硬件角度和低层作功耗优化的总结建议

注:本篇文章大部分内容参考一位同仁的文章https://blog.csdn.net/wqhlmark64/article/details/79915207,并修改一些错误,加上自己的见解,并补充一些额外内容。

Wu_Being博客声明:本人博客欢迎转载,请标明博客原文和原链接!谢谢!
《从Bugreport 解读 Android电量统计原理》:
https://wu-being.blog.csdn.net/article/details/88317636

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页