編輯:關於Android編程
BatteryStatsService主要負責電池電量的統計信息,首先我們簡單的看下電量統計服務的啟動過程。
從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來描述的,由於收集的電量會從不同的維度來計算,所以該類設計的比較復雜。
電量收集的關系圖。
在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; ArrayListarray = 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, SparseArrayasUsers, 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(SparseArrayasUsers) { 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方法來開始和停止統計。
電量計算:根據統計的各個模塊的使用時長* 各個模塊的單位時間的耗電量得出電量消耗。
本文主要有以下內容:* 自定義View的分類* 自定義View的注意事項* 自定義View的實現* 自定義View使其支持wrap_content和padding* 自定
Android提供的系統服務之--AudioManager(音頻管理器)
通常情況下,Android實現自定義控件無非三種方式。 Ⅰ、繼承現有控件,對其控件的功能進行拓展。 Ⅱ、將現有控件進行組合,實現功能更加強大控件。 Ⅲ、重寫View
本文中闡述如何自定義EditText實現搜索框自定義的樣式以及擠壓字母的思路等自定義EditText 相關的drawable文件 主界面以及相關的適配器 結果展示定義要呈