1. 程式人生 > >Android效能專項測試之耗電量統計API

Android效能專項測試之耗電量統計API

耗電量API

Android系統中很早就有耗電量的API,只不過一直都是隱藏的,Android系統的設定-電池功能就是呼叫的這個API,該API的核心部分是呼叫了com.android.internal.os.BatteryStatsHelper類,利用PowerProfile類,讀取power_profile.xml檔案,我們一起來看看具體如何計算耗電量,首先從最新版本6.0開始看

6.0的API

原始碼

BatteryStatsHelper
其中計算耗電量的方法為490行的processAppUsage,下來一步一步來解釋該方法。

App耗電量的計算探究

private
void processAppUsage(SparseArray<UserHandle> asUsers) {

方法的引數是一個SparseArray陣列,儲存的物件是UserHandle,官方文件給出的解釋是,代表一個使用者,可以理解為這個類裡面儲存了使用者的相關資訊.

final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
mStatsPeriod = mTypeBatteryRealtime;

然後給公共變數int型別的mStatsPeriod賦值,這個值mTypeBatteryRealtime

的計算過程又在320行的refreshStats方法中:

mTypeBatteryRealtime = mStats.computeBatteryRealtime(rawRealtimeUs, mStatsType);

這裡面用到了BatteryStats(mStats)類中的computeBatteryRealtime方法,該方法計算出此次統計電量的時間間隔。好,歪樓了,回到BatteryStatsHelper中。

BatterySipper osSipper = null;
final SparseArray<? extends Uid> uidStats = mStats.getUidStats();
final
int NU = uidStats.size();

首先建立一個BatterySipper物件osSipper,該物件裡面可以儲存一些後續我們要計算的值,然後通過BatteryStats類物件mStats來得到一個包含Uid的物件的SparseArray組數,然後計算了一下這個陣列的大小,儲存在變數NU中。

for (int iu = 0; iu < NU; iu++) {
final Uid u = uidStats.valueAt(iu);
            final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);

然後for迴圈計算每個Uid代表的App的耗電量,因為BatterySipper可計算的型別有三種:應用, 系統服務, 硬體型別,所以這個地方傳入的是DrainType.APP,還有其他可選型別如下:

public enum DrainType {
        IDLE,
        CELL,
        PHONE,
        WIFI,
        BLUETOOTH,
        FLASHLIGHT,
        SCREEN,
        APP,
        USER,
        UNACCOUNTED,
        OVERCOUNTED,
        CAMERA
    }

列舉了目前可計算耗電量的模組。

mCpuPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
            mWakelockPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
            mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
            mWifiPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
            mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
            mSensorPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
            mCameraPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
            mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);

其中mStatsType的值為BatteryStats.STATS_SINCE_CHARGED,代表了我們的計算規則是從上次充滿電後資料,還有一種規則是STATS_SINCE_UNPLUGGED是拔掉USB線後的資料。而mRawRealtime是當前時間,mRawUptime是執行時間。6.0的對各個模組的消耗都交給了單獨的類去計算,這些類都繼承於PowerCalculator抽象類:

藍芽耗電:BluetoothPowerCalculator.java
攝像頭耗電:CameraPowerCalculator.java
Cpu耗電:CpuPowerCalculator.java
手電筒耗電:FlashlightPowerCalculator.java
無線電耗電:MobileRadioPowerCalculator.java
感測器耗電:SensorPowerCalculator.java
Wakelock耗電:WakelockPowerCalculator.java
Wifi耗電:WifiPowerCalculator.java

這一部分我一會單獨拿出來挨個解釋,現在我們還是回到BatteryStatsHelper繼續往下走

final double totalPower = app.sumPower();

BatterySipper#sumPower方法是統計總耗電量,方法詳情如下,其中usagePowerMah這個值有點特殊,其他的上面都講過.

/**
     * Sum all the powers and store the value into `value`.
     * @return the sum of all the power in this BatterySipper.
     */
    public double sumPower() {
        return totalPowerMah = usagePowerMah + wifiPowerMah + gpsPowerMah + cpuPowerMah +
                sensorPowerMah + mobileRadioPowerMah + wakeLockPowerMah + cameraPowerMah +
                flashlightPowerMah;
    }

然後根據是否是DEBUG版本列印資訊,這個沒啥可說的,然後會把剛才計算的電量值新增到列表中:

 // Add the app to the list if it is consuming power.
            if (totalPower != 0 || u.getUid() == 0) {
                //
                // Add the app to the app list, WiFi, Bluetooth, etc, or into "Other Users" list.
                //
                final int uid = app.getUid();
                final int userId = UserHandle.getUserId(uid);
                if (uid == Process.WIFI_UID) {
                    mWifiSippers.add(app);
                } else if (uid == Process.BLUETOOTH_UID) {
                    mBluetoothSippers.add(app);
                } else if (!forAllUsers && asUsers.get(userId) == null
                        && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
                    // We are told to just report this user's apps as one large entry.
                    List<BatterySipper> list = mUserSippers.get(userId);
                    if (list == null) {
                        list = new ArrayList<>();
                        mUserSippers.put(userId, list);
                    }
                    list.add(app);
                } else {
                    mUsageList.add(app);
                }

                if (uid == 0) {
                    osSipper = app;
                }
            }

首先判斷totalPower的值和當前uid號是否符合規則,規則為總耗電量不為0或者使用者id為0.當uid表明為WIFI或者藍芽時,新增到下面對應的列表中,一般情況下正常的應用我們直接儲存到下面的mUsageList中就行就行,但是也有一些例外:

/**
     * List of apps using power.
     */
    private final List<BatterySipper> mUsageList = new ArrayList<>();

    /**
     * List of apps using wifi power.
     */
    private final List<BatterySipper> mWifiSippers = new ArrayList<>();

    /**
     * List of apps using bluetooth power.
     */
    private final List<BatterySipper> mBluetoothSippers = new ArrayList<>();

如果我們的系統是單使用者系統,且當前的userId號不在我們的統計範圍內,且其程序id號是大於Process.FIRST_APPLICATION_UID(10000,系統分配給普通應用的其實id號),我們就要將其存放到mUserSippers陣列中,定義如下:

private final SparseArray<List<BatterySipper>> mUserSippers = new SparseArray<>();

最後判斷uid為0的話,代表是Android作業系統的耗電量,賦值給osSipper(494行定義)就可以了,這樣一個app的計算就完成了,遍歷部分就不說了,儲存這個osSipper是為了最後一步計算:

if (osSipper != null) {
            // The device has probably been awake for longer than the screen on
            // time and application wake lock time would account for.  Assign
            // this remainder to the OS, if possible.
            mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtime,
                                                        mRawUptime, mStatsType);
            osSipper.sumPower();
        }

主流程我們已經介紹完了,下面來看各個子模組耗電量的計算

Cpu耗電量


<device name="Android">
  <!-- Most values are the incremental current used by a feature,
       in mA (measured at nominal voltage).
       The default values are deliberately incorrect dummy values.
       OEM's must measure and provide actual values before
       shipping a device.
       Example real-world values are given in comments, but they
       are totally dependent on the platform and can vary
       significantly, so should be measured on the shipping platform
       with a power meter. -->
  <item name="none">0</item>
  <item name="screen.on">0.1</item>  <!-- ~200mA -->
  <item name="screen.full">0.1</item>  <!-- ~300mA -->
  <item name="bluetooth.active">0.1</item> <!-- Bluetooth data transfer, ~10mA -->
  <item name="bluetooth.on">0.1</item>  <!-- Bluetooth on & connectable, but not connected, ~0.1mA -->
  <item name="wifi.on">0.1</item>  <!-- ~3mA -->
  <item name="wifi.active">0.1</item>  <!-- WIFI data transfer, ~200mA -->
  <item name="wifi.scan">0.1</item>  <!-- WIFI network scanning, ~100mA -->
  <item name="dsp.audio">0.1</item> <!-- ~10mA -->
  <item name="dsp.video">0.1</item> <!-- ~50mA -->
  <item name="camera.flashlight">0.1</item> <!-- Avg. power for camera flash, ~160mA -->
  <item name="camera.avg">0.1</item> <!-- Avg. power use of camera in standard usecases, ~550mA -->
  <item name="radio.active">0.1</item> <!-- ~200mA -->
  <item name="radio.scanning">0.1</item> <!-- cellular radio scanning for signal, ~10mA -->
  <item name="gps.on">0.1</item> <!-- ~50mA -->
  <!-- Current consumed by the radio at different signal strengths, when paging -->
  <array name="radio.on"> <!-- Strength 0 to BINS-1 -->
      <value>0.2</value> <!-- ~2mA -->
      <value>0.1</value> <!-- ~1mA -->
  </array>
  <!-- Different CPU speeds as reported in
       /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state -->
  <array name="cpu.speeds">
      <value>400000</value> <!-- 400 MHz CPU speed -->
  </array>
  <!-- Current when CPU is idle -->
  <item name="cpu.idle">0.1</item>
  <!-- Current at each CPU speed, as per 'cpu.speeds' -->
  <array name="cpu.active">
      <value>0.1</value>  <!-- ~100mA -->
  </array>
  <!-- This is the battery capacity in mAh (measured at nominal voltage) -->
  <item name="battery.capacity">1000</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>

這個裡面儲存了Cpu(cpu.speeds)的主頻等級,以及每個主頻每秒消耗的毫安(cpu.active),好,現在回到CpuPowerCalculator中,先來看構造方法

public CpuPowerCalculator(PowerProfile profile) {
        final int speedSteps = profile.getNumSpeedSteps();
        mPowerCpuNormal = new double[speedSteps];
        mSpeedStepTimes = new long[speedSteps];
        for (int p = 0; p < speedSteps; p++) {
            mPowerCpuNormal[p] = profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE, p);
        }
    }

第一步獲得Cpu有幾個主頻等級,因為不同等級消耗的電量不一樣,所以要區別對待,根據主頻的個數,然後初始化mPowerCpuNormalmSpeedStepTimes,前者用來儲存不同等級的耗電速度,後者用來儲存在不同等級上耗時,然後給mPowerCpuNormal的每個元素附上值。構造方法就完成了其所有的工作,現在來計算方法calculateApp,

final int speedSteps = mSpeedStepTimes.length;

        long totalTimeAtSpeeds = 0;
        for (int step = 0; step < speedSteps; step++) {
            mSpeedStepTimes[step] = u.getTimeAtCpuSpeed(step, statsType);
            totalTimeAtSpeeds += mSpeedStepTimes[step];
        }
        totalTimeAtSpeeds = Math.max(totalTimeAtSpeeds, 1);

首先得到Cpu主頻等級個數,然後BatteryStats.Uid得到不同主頻上執行時間,計算Cpu總耗時儲存在totalTimeAtSpeeds中,

app.cpuTimeMs = (u.getUserCpuTimeUs(statsType) + u.getSystemCpuTimeUs(statsType)) / 1000;

Cpu的執行時間分很多部分,但是我們關注UserKernal部分,也就是上面的UserCpuTimeSystemCpuTime

double cpuPowerMaMs = 0;
        for (int step = 0; step < speedSteps; step++) {
            final double ratio = (double) mSpeedStepTimes[step] / totalTimeAtSpeeds;
            final double cpuSpeedStepPower = ratio * app.cpuTimeMs * mPowerCpuNormal[step];
            if (DEBUG && ratio != 0) {
                Log.d(TAG, "UID " + u.getUid() + ": CPU step #"
                        + step + " ratio=" + BatteryStatsHelper.makemAh(ratio) + " power="
                        + BatteryStatsHelper.makemAh(cpuSpeedStepPower / (60 * 60 * 1000)));
            }
            cpuPowerMaMs += cpuSpeedStepPower;
        }

上面的程式碼就是將不同主頻的消耗累加到一起,但是其中值得注意的是,他並不是用各個主頻的消耗時間*主頻單位時間內消耗的電量,而是用一個radio變數來計算得到各個主頻段執行時間佔總時間的百分比,然後用cpuTimeMs來換算成各個主頻的Cpu實際消耗時間,這比5.0的API多了這麼一步,我估計是發現了計算的不嚴謹性,這也是Android遲遲不放出統計電量方式的原因,其實google自己對這塊也沒有把握,所以才會造成不同API計算方式的差異。好,計算完我們的總消耗後,是不是就算完事了?如果你只需要得到一個App的耗電總量,上面的講解已經足夠了,但是6.0的API計算了每個App的不同程序的耗電量,這個我們就只當看看就行,暫時沒什麼實際意義。

// Keep track of the package with highest drain.
        double highestDrain = 0;

        app.cpuFgTimeMs = 0;
        final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
        final int processStatsCount = processStats.size();
        for (int i = 0; i < processStatsCount; i++) {
            final BatteryStats.Uid.Proc ps = processStats.valueAt(i);
            final String processName = processStats.keyAt(i);
            app.cpuFgTimeMs += ps.getForegroundTime(statsType);

            final long costValue = ps.getUserTime(statsType) + ps.getSystemTime(statsType)
                    + ps.getForegroundTime(statsType);

            // Each App can have multiple packages and with multiple running processes.
            // Keep track of the package who's process has the highest drain.
            if (app.packageWithHighestDrain == null ||
                    app.packageWithHighestDrain.startsWith("*")) {
                highestDrain = costValue;
                app.packageWithHighestDrain = processName;
            } else if (highestDrain < costValue && !processName.startsWith("*")) {
                highestDrain = costValue;
                app.packageWithHighestDrain = processName;
            }
        }

        // Ensure that the CPU times make sense.
        if (app.cpuFgTimeMs > app.cpuTimeMs) {
            if (DEBUG && app.cpuFgTimeMs > app.cpuTimeMs + 10000) {
                Log.d(TAG, "WARNING! Cputime is more than 10 seconds behind Foreground time");
            }

            // Statistics may not have been gathered yet.
            app.cpuTimeMs = app.cpuFgTimeMs;
        }

上面統計同一App下不同的程序的耗電量,得到消耗最大的程序名,儲存到BatterySipper物件中,然後得出AppCpuforeground消耗時間,將foreground時間與之前計算得到的cpuTimeMs進行比較,如果foreground時間比cpuTimeMs還要大,那麼就將cpuTimeMs的時間改變為foreground的值,但是這個值的變化對之前耗電總量的計算沒有絲毫影響。

// Convert the CPU power to mAh
        app.cpuPowerMah = cpuPowerMaMs / (60 * 60 * 1000);

最後的最後,將耗電量用mAh單位來表示,所以在毫秒的基礎上除以60*60*1000

總結:Cpu耗電量的計算是要區分不同主頻的,頻率不同,單位時間內消耗的電量是有區分的,這一點要明白。還有一點就是不同主頻上的執行時間不是通過BatteryStats.Uid#getTimeAtCpuSpeed方法得到的,二十是通過百分比和BatteryStats.Uid#getUserCpuTimeUsgetSystemCpuTimeUs計算得到cpuTimeMs乘積得到的。最後一點就是,cpuTimeMs時間是會在計算完畢後進行比較,比較的物件是CPUforeground時間。

WakeLock耗電量的計算

從構造方法開始,

public WakelockPowerCalculator(PowerProfile profile) {
        mPowerWakelock = profile.getAveragePower(PowerProfile.POWER_CPU_AWAKE);
    }

首先得到power_profile.xmlcpu.awake表示的值,儲存在mPowerWakelock變數中。構造方法只做了這麼點事,下面進入calculateApp方法。

@Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {
        long wakeLockTimeUs = 0;
        final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelockStats =
                u.getWakelockStats();
        final int wakelockStatsCount = wakelockStats.size();
        for (int i = 0; i < wakelockStatsCount; i++) {
            final BatteryStats.Uid.Wakelock wakelock = wakelockStats.valueAt(i);

            // Only care about partial wake locks since full wake locks
            // are canceled when the user turns the screen off.
            BatteryStats.Timer timer = wakelock.getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
            if (timer != null) {
                wakeLockTimeUs += timer.getTotalTimeLocked(rawRealtimeUs, statsType);
            }
        }
        app.wakeLockTimeMs = wakeLockTimeUs / 1000; // convert to millis
        mTotalAppWakelockTimeMs += app.wakeLockTimeMs;

        // Add cost of holding a wake lock.
        app.wakeLockPowerMah = (app.wakeLockTimeMs * mPowerWakelock) / (1000*60*60);
        if (DEBUG && app.wakeLockPowerMah != 0) {
            Log.d(TAG, "UID " + u.getUid() + ": wake " + app.wakeLockTimeMs
                    + " power=" + BatteryStatsHelper.makemAh(app.wakeLockPowerMah));
        }
    }

首先獲得Wakelock的數量,然後逐個遍歷得到每個Wakelock物件,得到該物件後,得到BatteryStats.WAKE_TYPE_PARTIAL的喚醒時間,然後累加,其實wakelock有4種,為什麼只取partial的時間,具體程式碼google也沒解釋的很清楚,只是用一句註釋打發了我們。得到總時間後,就可以與構造方法中的單位時間waklock消耗電量相乘得到Wakelock消耗的總電量。

Wifi耗電量的計算

首先來看構造方法,來了解一下WIFI的耗電量計算用到了power_profile.xml中的哪些屬性:

public WifiPowerCalculator(PowerProfile profile) {
        mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE);
        mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX);
        mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX);
    }
 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";

知道對應的xml的屬性後我們直接看calculateApp方法:

@Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {
        final long idleTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME,
                statsType);
        final long txTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME, statsType);
        final long rxTime = u.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME, statsType);
        app.wifiRunningTimeMs = idleTime + rxTime + txTime;
        app.wifiPowerMah =
                ((idleTime * mIdleCurrentMa) + (txTime * mTxCurrentMa) + (rxTime * mRxCurrentMa))
                / (1000*60*60);
        mTotalAppPowerDrain += app.wifiPowerMah;

        app.wifiRxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_RX_DATA,
                statsType);
        app.wifiTxPackets = u.getNetworkActivityPackets(BatteryStats.NETWORK_WIFI_TX_DATA,
                statsType);
        app.wifiRxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_RX_DATA,
                statsType);
        app.wifiTxBytes = u.getNetworkActivityBytes(BatteryStats.NETWORK_WIFI_TX_DATA,
                statsType);

        if (DEBUG && app.wifiPowerMah != 0) {
            Log.d(TAG, "UID " + u.getUid() + ": idle=" + idleTime + "ms rx=" + rxTime + "ms tx=" +
                    txTime + "ms power=" + BatteryStatsHelper.makemAh(app.wifiPowerMah));
        }
    }

這裡的計算方式也是差不多,先根據Uid得到時間,然後乘以構造方法裡對應的wifi型別單位時間內消耗電量值,沒什麼難點,就不一一分析,需要注意的是,這裡面還計算了wifi傳輸的資料包的數量和位元組數。

藍芽耗電量的計算

藍芽關注的power_profile.xml中的屬性如下:

 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";

但是還沒有單獨為App計算耗電量的,所以這個地方是空的。

@Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {
        // No per-app distribution yet.
    }

攝像頭耗電量的計算

攝像頭的耗電量關注的是power_profile.xmlcamera.avg屬性代表的值,儲存到mCameraPowerOnAvg,

public static final String POWER_CAMERA = "camera.avg";

計算方式如下:

@Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {

        // Calculate camera power usage.  Right now, this is a (very) rough estimate based on the
        // average power usage for a typical camera application.
        final BatteryStats.Timer timer = u.getCameraTurnedOnTimer();
        if (timer != null) {
            final long totalTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
            app.cameraTimeMs = totalTime;
            app.cameraPowerMah = (totalTime * mCameraPowerOnAvg) / (1000*60*60);
        } else {
            app.cameraTimeMs = 0;
            app.cameraPowerMah = 0;
        }
    }

先計算攝像頭開啟的時間totalTime,然後根據這個值乘以mCameraPowerOnAvg得到攝像頭的耗電量。

手電筒耗電量的計算

public static final String POWER_FLASHLIGHT = "camera.flashlight";

跟攝像頭類似,也是先得到時間,然後乘積,不想說了,沒意思。

無線電耗電量的計算

關注的是power_profile.xml中如下三個屬性:

/**
     * Power consumption when screen is on, not including the backlight power.
     */
    public static final String POWER_SCREEN_ON = "screen.on";

    /**
     * Power consumption when cell radio is on but not on a call.
     */
    public static final String POWER_RADIO_ON = "radio.on";

    /**
     * Power consumption when cell radio is hunting for a signal.
     */
    public static final String POWER_RADIO_SCANNING = "radio.scanning";

當無限量連線上時,根據訊號強度不同,耗電量的計算是有區別的,所以在構造方法,當無線電的狀態為on時,是要特殊處理的,其他兩個狀態(active和scan)就正常取值就可以了。

/**
     * Power consumption when screen is on, not including the backlight power.
     */
    public static final String POWER_SCREEN_ON = "screen.on";

    /**
     * Power consumption when cell radio is on but not on a call.
     */
    public static final String POWER_RADIO_ON = "radio.on";

    /**
     * Power consumption when cell radio is hunting for a signal.
     */
    public static final String POWER_RADIO_SCANNING = "radio.scanning";

計算的方式分兩種,以無線電處於active狀態的次數為區分,當active大於0,我們用處於active狀態的時間來乘以它的單位耗時。另一種情況就要根據網路轉化的資料包來計算耗電量了。

感測器耗電量的計算

只關注一個屬性:

 public static final String POWER_GPS_ON = "gps.on";

計算方式如下:

@Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {
        // Process Sensor usage
        final SparseArray<? extends BatteryStats.Uid.Sensor> sensorStats = u.getSensorStats();
        final int NSE = sensorStats.size();
        for (int ise = 0; ise < NSE; ise++) {
            final BatteryStats.Uid.Sensor sensor = sensorStats.valueAt(ise);
            final int sensorHandle = sensorStats.keyAt(ise);
            final BatteryStats.Timer timer = sensor.getSensorTime();
            final long sensorTime = timer.getTotalTimeLocked(rawRealtimeUs, statsType) / 1000;
            switch (sensorHandle) {
                case BatteryStats.Uid.Sensor.GPS:
                    app.gpsTimeMs = sensorTime;
                    app.gpsPowerMah = (app.gpsTimeMs * mGpsPowerOn) / (1000*60*60);
                    break;
                default:
                    final int sensorsCount = mSensors.size();
                    for (int i = 0; i < sensorsCount; i++) {
                        final Sensor s = mSensors.get(i);
                        if (s.getHandle() == sensorHandle) {
                            app.sensorPowerMah += (sensorTime * s.getPower()) / (1000*60*60);
                            break;
                        }
                    }
                    break;
            }
        }
    }

當感測器的型別為GPS時,我們計算每個感測器的時間然後乘以耗電量,和所有的耗電量計算都是一樣,不同的是,當感測器不是GPS時,這個時候計算就根據SensorManager得到所有感測器型別,這個裡面儲存有不同感測器的單位耗電量,這樣就能計算不同感測器的耗電量。

總結

至此我已經把App耗電量的計算講完了(還有硬體),前後花費3天時間,好痛苦(此處一萬隻草泥馬),不過好在自己也算對這個耗電量的理解有了一定的認識。google官方對耗電量的統計給出的解釋都是不能代表真實資料,只能作為參考值,因為受power_profile.xml的干擾太大,如果手機廠商沒有嚴格設定這個檔案,那可想而知出來的值可能是不合理的。

提示

騰訊的GT團隊前幾天推出了耗電量的計算APK,原理是一樣的,大家可以試用下GT