从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 从硬件角度和低层作功耗优化的总结建议

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