Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> BatteryStatsService電池電量統計服務分析

BatteryStatsService電池電量統計服務分析

編輯:關於Android編程

BatteryStatsService主要負責電池電量的統計信息,首先我們簡單的看下電量統計服務的啟動過程。

 

BatteryStatsService啟動過程

AA

 

 

從BatteryStatsService的啟動時序圖可以看出,BatteryStatsService服務是在ActivityManagerService服務中啟動的

1.在SystemServer中startBootstrapServices()方法中創建了ActivityManagerService服務的對象,並調用了SystemServiceManager的startService()方法啟動了BatteryStatsService服務

首先分析BatteryStatsService的構造方法

mStats = new BatteryStatsImpl(systemDir, handler, mHandler);

構造方法中創建了一個BatterystatsImpl對象,BatteryStatsService真正的處理邏輯其實都是在BatteryStatsImpl類中。

在BatteryStatsImpl類的構造函數中首先創建了一個文件

if (systemDir != null) {
            mFile = new JournaledFile(new File(systemDir, "batterystats.bin"),
                    new File(systemDir, "batterystats.bin.tmp"));
        } else {
            mFile = null;
        }

在手機的data/system/目錄下創建一個batterystats.bin文件

該文件用於保存BatteryStats電量的統計信息,系統會不定時的將電量統計信息BatteryStats寫入到文件中。

然後在構造函數中初始化了mScreenOnTimer,mPhoneOnTimer,

WifiOnTimer等一系列的StopWatchTimer,這些Timer主要用於統計各種模塊耗電的時間。

2.在ActivityManagerService服務的構造方法中創建了BatteryStatsService服務的對象,調用了readLocked()方法讀取電池統計信息,初始化BatteryStats

3.調用BatteryStatsService的initPwerManagerment()方法初始化電量統計服務,將BatteryStatsService服務注冊到ServiceManager中

 

收集、存儲電量統計信息

收集有很多的硬件模塊,CPU、顯示屏、WiFi、藍牙、閃光燈等,在Android系統中,手機的電壓是一定的,所以手機的電量計算就是模塊使用時間*模塊耗電。

其中關鍵就是模塊使用時間的統計。電量信息的收集在內存中是有BatteryStats來描述的,由於收集的電量會從不同的維度來計算,所以該類設計的比較復雜。

電量收集的關系圖。

BB

 

在BatterystatsService中有很多note***方法,這些方法就是其他的模塊調用用來統計相關的電量使用時間的。比如在PMS類中經常會有調用Notifier類相關的方法來進行通知,這些方法最終都會調用到BatteryStatsService中note***方法來通知該服務統計電量信息

通過其中一個時間收集的例子來學習下模塊電量時間收集的過程。

比如使用閃光燈的時間收集過程。

在/frameworks/av/services/camera/libcameraservice/CameraService.cpp類中。

使用閃光燈的時候會調用notifier.noteFlashlightOn(cameraId,newUid)

方法,當停止使用閃光燈的時候會調用notifier.noteFlashlightOff(cameraId,oldUid)方法。

首先我們看下noteFlashLigntOn方法,該方法最終調用BatteryStatsImpl類中的noteFlashLightOnLocked方法來處理

public void noteFlashlightOnLocked(int uid) {
    //獲取當前調用應用程序的Uid
        uid = mapUid(uid);
        //獲取真實系統時間
        final long elapsedRealtime = SystemClock.elapsedRealtime();
        //獲取系統運行時間
        final long uptime = SystemClock.uptimeMillis();
        if (mFlashlightOnNesting++ == 0) {
……
            addHistoryRecordLocked(elapsedRealtime, uptime);
            //調用Timer開始計時
            mFlashlightOnTimer.startRunningLocked(elapsedRealtime);
        }
        //根據UiD來統計該應用中flash的使用時間
        getUidStatsLocked(uid).noteFlashlightTurnedOnLocked(elapsedRealtime);
    }

該方法中會調用Timer開始統計時間,同時根據UID來統計該每個應用使用Flash的時間。這邊有兩中Timer,一種是總體的Timer,用來統計該模塊總共的使用時長,另一種在每個應用相關的UID中也有Timer,用來統計每個應用使用該模塊的時長

我們看下Timer的startRunningLocked方法,看看如何開始統計使用時間。


     void startRunningLocked(long elapsedRealtimeMs) {
            if (mNesting++ == 0) {
                //計算當前的batteryRealTime時間
                final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
		//根據當前時間記錄更新時間mUpdateTime
                mUpdateTime = batteryRealtime;
                ……
                mCount++;
                mAcquireTime = mTotalTime;
                
            }
        }

該方法其實也是比較簡單,在調用該方法的時候記錄下更新時間mUpdateTime

noteFlashLightOff方法同樣是調用Timer停止統計FlashLight的使用時間,同時按UID停止統計調用應用程序的使用時間。

Timer的stopRunningLocked方法

void stopRunningLocked(long elapsedRealtimeMs) {
            if (mNesting == 0) {
                return;
            }
            if (--mNesting == 0) {
                //計算現在的batteryRealTime時間
                final long batteryRealtime = mTimeBase.getRealtime(elapsedRealtimeMs * 1000);
                ……
                    mNesting = 1;
                    //計算該Timer統計的已經使用總時長totalTime
                    mTotalTime = computeRunTimeLocked(batteryRealtime);
                    mNesting = 0;
                ……
            }
        }

該方法中在停止統計的時候,根據batteryRealTime計算出該Timer統計的已經使用的總時長

結束當前的時間–開始的時間=模塊使用時長

computeRunTimeLocked計算總時長=總時長+(結束當前的時間–開始的時間)

return mTotalTime + (batteryRealTime - mUpdateTime)

到此為止可以看出,統計各個模塊的使用時間實際就是當該模塊使用或者結束的時候,調用相應的方法開始統計和結束統計,該時間段的使用時間就是結束的時間–開始的時間。該模塊的使用的總時間就是各個使用時間段的總和。

各個模塊的使用時間統計最終保存在BatteryStats類中的各個timer中。

 

電量計算

 

計算耗電量離不開BatteryStatsHelper這個類,在Settings設置中顯示電量相關信息就是調用BatteryStatsHelper的接口來實現的。

BatteryStatsHelper主要作用是用來計算所有應用和服務的用電量信息,使用BatteryStatsHelper接口的時候,必須在Activity或者Fragment初始化的時候調用BatteryStatsHelper的create()來初始化它,同時在activity或者Fragment銷毀的時候調用destroy()方法來銷毀它。

 

首先我們看下BatteryStatsHelper的create()初始化方法。

 public void create(Bundle icicle) {
        mBatteryInfo = IBatteryStats.Stub.asInterface(
                ServiceManager.getService(BatteryStats.SERVICE_NAME));
        mPowerProfile = new PowerProfile(mContext);
    }

Create()方法中主要做了兩件事:

1.獲取了一個BatteryStatsService服務的對象。用於和BatteryStatsService進行通信。

2.創建了一個PowerProfile文件對象。我們知道Android系統中關於電量計算公式:耗電量=模塊使用時間* 單位時間內的耗電量

模塊使用時間我們已經在BatteryStatsService中統計過了,那單位時間的耗電量其實就是在PowerProfile文件定義好了,每個手機的power_profile文件都不一樣,需要廠商自己定義,只需要根據各個廠商定義的這個文件來計算相關的耗電量即可。

 public PowerProfile(Context context) {
        if (sPowerMap.size() == 0) {
            readPowerValuesFromXml(context);
        }
    }

    private void readPowerValuesFromXml(Context context) {
        int id = com.android.internal.R.xml.power_profile;
        final Resources resources = context.getResources();
        XmlResourceParser parser = resources.getXml(id);
        boolean parsingArray = false;
        ArrayList array = new ArrayList();
        String arrayName = null;

        try {
            XmlUtils.beginDocument(parser, TAG_DEVICE);
			……

PowerProfile類初始化的時候,直接讀取power_profile的xml文件,然後將預定義的值保存到MAP中。

Power_profile.xml文件



  0
  0.1  
  0.1  
  0.1 
  0.1  
  0.1  
  0.1  
  0.1  
  0.1 
  0.1 
  ……

PowerProfile文件中定義了各個模塊在不同狀態下的耗電量,由於各個硬件模塊的耗電量不相同,所以這個文件是由手機的硬件廠商來定制的。

BatteryStatsHelper的refreshStats()方法用來刷新計算的用電量信息,是BatteryStatsHelper類的關鍵接口,電量計算也是在該方法在中進行處理的。

 

 public void refreshStats(int statsType, SparseArray asUsers, long rawRealtimeUs,
            long rawUptimeUs) {
        //初始化基本的狀態
        mMaxPower = 0;
        mMaxRealPower = 0;
        mComputedPower = 0;
        mTotalPower = 0;
        //重置相關的列表
        ……
        //初始化相關的電量計算工具,各種計算工具都繼承自PowerCalculator類,用於計算不同模塊的耗電量
        if (mCpuPowerCalculator == null) {
            mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
        }
        mCpuPowerCalculator.reset();

        ……

        if (mFlashlightPowerCalculator == null) {
            mFlashlightPowerCalculator = new FlashlightPowerCalculator(mPowerProfile);
        }
        mFlashlightPowerCalculator.reset();

		//初始化相關的信息
        ……
		//獲取電池剩余時間
        mBatteryTimeRemaining = mStats.computeBatteryTimeRemaining(rawRealtimeUs);
		//獲取充電剩余時間
        mChargeTimeRemaining = mStats.computeChargeTimeRemaining(rawRealtimeUs);
        ……
		//計算各個應用的耗電量
        processAppUsage(asUsers);

 ……

		//計算其他雜項的耗電量
        processMiscUsage();

        Collections.sort(mUsageList);

        
		//計算耗電量最大的應用,並計算所有的應用的總耗電量
        if (!mUsageList.isEmpty()) {
            mMaxRealPower = mMaxPower = mUsageList.get(0).totalPowerMah;
            final int usageListCount = mUsageList.size();
            for (int i = 0; i < usageListCount; i++) {
                mComputedPower += mUsageList.get(i).totalPowerMah;
            }
        }

        mTotalPower = mComputedPower;
        //統計的總耗電量和實際總耗電量之間的差距
        if (mStats.getLowDischargeAmountSinceCharge() > 1) {
			//電池耗電量大於統計的總耗電量
            if (mMinDrainedPower > mComputedPower) {
                double amount = mMinDrainedPower - mComputedPower;
                mTotalPower = mMinDrainedPower;
                BatterySipper bs = new BatterySipper(DrainType.UNACCOUNTED, null, amount);

                // Insert the BatterySipper in its sorted position.
                int index = Collections.binarySearch(mUsageList, bs);
                if (index < 0) {
                    index = -(index + 1);
                }
                mUsageList.add(index, bs);
                mMaxPower = Math.max(mMaxPower, amount);
		    //電池耗電量小於統計的總耗電量
            } else if (mMaxDrainedPower < mComputedPower) {
                double amount = mComputedPower - mMaxDrainedPower;

                // Insert the BatterySipper in its sorted position.
                BatterySipper bs = new BatterySipper(DrainType.OVERCOUNTED, null, amount);
                int index = Collections.binarySearch(mUsageList, bs);
                if (index < 0) {
                    index = -(index + 1);
                }
                mUsageList.add(index, bs);
                mMaxPower = Math.max(mMaxPower, amount);
            }
        }
    }

 

這個方法主要邏輯就是來刷新電量統計,重新計算各個應用各個模塊的電量。首先就重置了相關的基本變量,初始化了相關的電量計算工具。

最重要的是使用processAppUsage()方法來計算各個應用的耗電量。

private void processAppUsage(SparseArray asUsers) {
        final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
        mStatsPeriod = mTypeBatteryRealtime;

        BatterySipper osSipper = null;
        final SparseArray uidStats = mStats.getUidStats();
        final int NU = uidStats.size();
		//循環計算每個應用的耗電量
        for (int iu = 0; iu < NU; iu++) {
            final Uid u = uidStats.valueAt(iu);
            final BatterySipper app = new BatterySipper(BatterySipper.DrainType.APP, u, 0);
            //計算cpu耗電量
            mCpuPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
	    //計算的wakeLock的耗電量
            mWakelockPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
	    //計算手機射頻的耗電量
            mMobileRadioPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
	    //計算WiFI的耗電量
            mWifiPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
	    //計算藍牙的耗電量
            mBluetoothPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
	    //計算傳感器的耗電量
            mSensorPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
	    //計算Camera的耗電量
            mCameraPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);
	    //計算閃光燈的耗電量
            mFlashlightPowerCalculator.calculateApp(app, u, mRawRealtime, mRawUptime, mStatsType);

	    //計算這個應用耗電量的總和
            final double totalPower = app.sumPower();

            if (totalPower != 0 || u.getUid() == 0) {
                //根據應用的類型,將應用耗電量信息添加到對應的列表
                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) {
                    //如果該應用是單獨某個用戶下的應用則添加的userSippers列表中
                    if (list == null) {
                        list = new ArrayList<>();
                        mUserSippers.put(userId, list);
                    }
                    list.add(app);
                } else {
		    //否則直接添加到普通的應用列表
                    mUsageList.add(app);
                }

                if (uid == 0) {
		  //如果應用id為0,則這個應用為android系統
                    osSipper = app;
                }
            }
        }

        if (osSipper != null) {
            mWakelockPowerCalculator.calculateRemaining(osSipper, mStats, mRawRealtime,
                                                        mRawUptime, mStatsType);
            osSipper.sumPower();
        }
    }

processAppUsage()方法中利用循環的方法,一次計算每一個應用的耗電量。

1.根據應用的uid來計算該應用的CPU,藍牙,wifi,手機射頻,傳感器,camera等硬件的耗電量,所計算的耗電量信息都存儲在了BatterySipper這個對象中。

2.將各個硬件模塊的耗電量相加計算出該應用的總耗電量

3.根據應用的類型,將應用的耗電信息添加的不同的列表歸類。其中若uid==0表示當前應用為系統,然後計算出系統的耗電量

那耗電量到底是怎麼計算的呢?我們看其中一個電量計算工具的實現方法

 

WiFiPowerCalculator

 

public WifiPowerCalculator(PowerProfile profile) {
	    //獲取WIFI三種模式下單位時間的耗電量
        mIdleCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_IDLE);
        mTxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_TX);
        mRxCurrentMa = profile.getAveragePower(PowerProfile.POWER_WIFI_CONTROLLER_RX);
    }

    @Override
    public void calculateApp(BatterySipper app, BatteryStats.Uid u, long rawRealtimeUs,
                             long rawUptimeUs, int statsType) {
		//獲取WiFi三種模式,每種模式的使用時長
        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);
		//該應用的WiFi使用總時長
        app.wifiRunningTimeMs = idleTime + rxTime + txTime;
		//計算該應用WIFi耗電總量
        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);

    }

 

可以看出在構造方法中,首先從PowerProfile中來獲取預定義的WiFi單位時間的耗電量,在計算app耗電量calculateApp()方法中,獲取該應用WiFi三種模式的使用時長,將三種模式的使用時長相加得出該應用使用WiFI的總時長,耗電量就是app.wifiPowerMah=((idleTime*mIdleCurrentMa)+(txTime*mTxCurrentMa)+(rxTime*mRxCurrentMa))/(1000*60*60);

WiFi相應模式的使用時長* 相應模式的單位時間耗電量。

最後還計算不同模式下的傳輸的數據包。

從代碼實現中可以看出電量計算公式:耗電量=耗電時長*單位時間耗電量

其他模塊的耗電量和WiFi耗電量計算類似,不再一一分析。

接著看refreshStats()方法。

當計算完各個應用的耗電量後調用processMiscUsage()方法,根據方法名稱就可以看出該方法主要用來計算各種雜項的耗電量

 private void processMiscUsage() {
        addUserUsage();
        addPhoneUsage();
        addScreenUsage();
        addWiFiUsage();
        addBluetoothUsage();
        addIdleUsage(); // Not including cellular idle power
        // 如果手機只是支持wifi,就不在計算射頻的電量
        if (!mWifiOnly) {
            addRadioUsage();
        }
    }

該方法中主要用來計算那些WiFi,Phohe,Bluetooth等用電量,並將該用電信息添加到用電列表,以WiFi為例,計算WiFi的耗電量:WIFI模塊的整體耗電量減去App中WiFI的耗電量

private void addWiFiUsage() {
        BatterySipper bs = new BatterySipper(DrainType.WIFI, null, 0);
        計算WiFi的耗電量
        mWifiPowerCalculator.calculateRemaining(bs, mStats, mRawRealtime, mRawUptime, mStatsType);
        將之前WiFi類型的應用電量相加
        aggregateSippers(bs, mWifiSippers, "WIFI");
        if (bs.totalPowerMah > 0) {
            mUsageList.add(bs);
        }
    }

在計算應用的耗電量的時候,我們會為每個應用計算相應模塊WiFI的使用時長,同時我們也會統計WiFi模塊的整體使用時長,兩者之間的耗電量會有差距,我們將這部分差值就是WiFi耗電量

public void calculateRemaining(BatterySipper app, BatteryStats stats, long rawRealtimeUs,
                                   long rawUptimeUs, int statsType) {
    //獲取WiFi各種模式下使用總時長
    final long idleTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_IDLE_TIME,
                statsType);
    final long rxTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_RX_TIME,
                statsType);
    final long txTimeMs = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_TX_TIME,
                statsType);
    app.wifiRunningTimeMs = idleTimeMs + rxTimeMs + txTimeMs;

    //獲取WiFi模塊消耗的總電量
    double powerDrainMah = stats.getWifiControllerActivity(BatteryStats.CONTROLLER_POWER_DRAIN,
                statsType) / (double)(1000*60*60);
    if (powerDrainMah == 0) {
       //如果WiFi模塊消耗的總電量=0;重新計算WiFI消耗總電量.
       powerDrainMah = ((idleTimeMs * mIdleCurrentMa) + (txTimeMs * mTxCurrentMa)
                    + (rxTimeMs * mRxCurrentMa)) / (1000*60*60);
        }
        //求出WiFI消耗總電量 – 各個應用wifi消耗總電量
        app.wifiPowerMah = Math.max(0, powerDrainMah - mTotalAppPowerDrain);
    }

這個方法中,求出了應用中WiFI模塊使用總電量和Wifi消耗總電量之間的差值,屬於沒有應用認領的消耗的電量。

然後調用aggregateSippers()方法將我們以前統計的WIFI類型的應用耗電量相加,計算WiFi的耗電量。添加到usageList列表中。

其他屏幕,藍牙耗電量類似。

 

然後再次回到refreshStats()方法中

調用Collection.sort()將電量使用排序,找到消耗電量最多的應用

將usageList列表中各個應用的耗電量相加得出統計的總耗電量,最後根據統計的總耗電量和實際的總耗電量之間的差距,作為未統計的類型,加入到耗電量的列表中

到此為止我們對於手機電量的統計與計算就分析完了

總結:

電量統計:實際使用StopWatchTImer來統計不同模塊的使用時長,調用note**startLocked和note**stopLocked方法來開始和停止統計。

電量計算:根據統計的各個模塊的使用時長* 各個模塊的單位時間的耗電量得出電量消耗。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved