硬件消耗電量 來執(zhí)行任務(wù)的過程逢慌,叫做超時(shí)電流消耗
主要消耗:
1.最大的耗電是我們的屏幕
2.蜂窩式無線數(shù)據(jù)交換(3G4G)
3.叫醒鬧鐘 wake lock,AlarmManager,JobSchedulerAPI
4.應(yīng)用耗電
圖中主要是CPU喚醒時(shí)的高峰線 可以看到在喚醒的時(shí)候電量消耗是非常大的
值得注意的是當(dāng)工作完成后回窘,設(shè)備會(huì)主動(dòng)進(jìn)行休眠兽狭,這非常重要师郑,在不使用或者很少使用的情況下,長時(shí)間保持屏幕喚醒會(huì)迅速消耗電池的電量。
當(dāng)設(shè)備通過無線網(wǎng)發(fā)送數(shù)據(jù)的時(shí)候,為了使用硬件像寒,這里會(huì)出現(xiàn)一個(gè)喚醒好點(diǎn)高峰。
接下來還有一個(gè)高數(shù)值谜洽,這是發(fā)送數(shù)據(jù)包消耗的電量萝映,
然后接受數(shù)據(jù)包也會(huì)消耗大量電量 也看到一個(gè)峰值。
另外WiFi連接下阐虚,網(wǎng)絡(luò)傳輸?shù)碾娏肯囊纫苿?dòng)網(wǎng)絡(luò)少很多,應(yīng)該盡量減少移動(dòng)網(wǎng)絡(luò)下的數(shù)據(jù)傳輸蚌卤,多在WiFi環(huán)境下傳輸數(shù)據(jù)实束。
而這次我主要分析的是應(yīng)用耗電的分析奥秆。
使用工具:Battery Historian中的Battery History
地址:https://github.com/google/battery-historian
我們看下安裝方式,有兩種咸灿,一種直接使用Docker构订,Docker上有我們所需要使用的所有的環(huán)境
第二種是自己安裝所有的環(huán)境。(這里我就不做具體的介紹了避矢,只是將一些細(xì)節(jié)記錄下)
電池?cái)?shù)據(jù)收集悼瘾,打開電池?cái)?shù)據(jù)的獲取及重置:
adb shell dumpsys batterystats --enable full-wake-history
adb shell dumpsys batterystats --reset
(主要是為了清除干擾數(shù)據(jù),然后做測試审胸,注意將數(shù)據(jù)線拔掉亥宿,防止充放電數(shù)據(jù)干擾)
對生成的bugReport,然后,我們需要將txt轉(zhuǎn)為html
命令:python historian.py -a bugreport.txt > battery.html
當(dāng)然要執(zhí)行這句命令也需要將github上的scripts目錄下面砂沛,需要下載到命令行所在的目錄
命令介紹后烫扼,然后我們就可以看到bugreport.txt和battery.html兩個(gè)文件,使用google瀏覽器打開html文件:
好碍庵,現(xiàn)在就上圖做一個(gè)簡要參數(shù)記錄:(橫向的主要是時(shí)間映企,單位為秒)
battery_level:電池水平,可以根據(jù)時(shí)間找到對應(yīng)的電量情況
plugged:插入静浴,就是代表充電的狀態(tài)堰氓,是否連接電源
screen:屏幕,這邊代表屏幕是否點(diǎn)亮苹享,可以看到睡眠狀態(tài)和點(diǎn)亮狀態(tài)下電量的是用情況
top:上豆赏,這邊的意思是當(dāng)前是那個(gè)程序在最上層
wake_lock*: 喚醒鎖 記錄的是wake_lock模塊的工作時(shí)間
running:運(yùn)行,界面的狀態(tài)富稻,主要判斷是否處于idle的狀態(tài)掷邦。用來判斷無操作狀態(tài)下電量的消耗。
wake_lock_in: 喚醒鎖椭赋,wake_lock有不同的組件抚岗,這個(gè)地方記錄在某一個(gè)時(shí)刻,有哪些部件開始工作哪怔,以及工作的時(shí)間宣蔚。
GPS:gps是否開啟
Phone in Call:是否進(jìn)行通話
Sync:同步:可以把鼠標(biāo)停在某一項(xiàng)上面∪暇常可以看到何時(shí)sync同步 啟動(dòng)的胚委,持續(xù)時(shí)間Duration多久。電池容量不會(huì)顯示單一行為消耗的具體電量叉信,這里只能顯示使用電池的頻率和時(shí)長亩冬,你可以看分時(shí)段的剩余電量來了解具體消耗了多少電量。
Job:后臺(tái)的工作,比如服務(wù)service的運(yùn)行硅急。
data_conn:數(shù)據(jù)連接方式覆享,上面的edge是說明采用的gprs的方式連接網(wǎng)絡(luò)的。此數(shù)據(jù)可以看出手機(jī)是使用2g营袜,3g撒顿,4g還是wifi進(jìn)行數(shù)據(jù)交換的。這一欄可以看出不同的連接方式對電量使用的影響荚板。
Status:電池狀態(tài)凤壁,有充電,放電跪另,未充電拧抖,已充滿,未知等不同狀態(tài)罚斗。 這一欄記錄了電池狀態(tài)的改變信息徙鱼。
phone_signal_strength:手機(jī)信號強(qiáng)弱。 這一欄記錄手機(jī)信號的強(qiáng)弱變化圖针姿,依次來判斷手機(jī)信號對電量的影響袱吆。
health:電池健康狀態(tài)的信息,這個(gè)信息一定程度上反映了這塊電池使用了多長時(shí)間距淫。
這一欄記錄電池狀態(tài)在何時(shí)發(fā)生改變绞绒,上面的圖中電池狀態(tài)一直處于good狀態(tài)。
plug:充電方式榕暇,usb或者插座蓬衡,以及顯示連接的時(shí)間。 這一欄顯示了不同的充電方式對電量使用的影響彤枢。
---------------------------------------------分割線---------------------------------------
我們可以這樣來獲取手機(jī)充電狀態(tài):
private boolean checkForPower() {
// It is very easy to subscribe to changes to the battery state, but you can get the current
// state by simply passing null in as your receiver. Nifty, isn't that?
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = this.registerReceiver(null, filter);
// There are currently three ways a device can be plugged in. We should check them all.
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_USB);
boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
boolean wirelessCharge = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
wirelessCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS);
}
return (usbCharge || acCharge || wirelessCharge);
}
BatteryManager.BATTERY_PLUGGED_AC這個(gè)屬性代表的是交流充電
BatteryManager.BATTERY_PLUGGED_USB 代表USB充電
BatteryManager.BATTERY_PLUGGED_WIRELESS代表無線充電
我們很多耗電的操作可以考慮放到充電的時(shí)候去執(zhí)行
好狰晚,下面我們來看看wake_lock:
wake_lock
系統(tǒng)為了節(jié)省電量,CPU在沒有任務(wù)忙的時(shí)候就會(huì)自動(dòng)進(jìn)入休眠缴啡。
有任務(wù)需要喚醒CPU高效執(zhí)行的時(shí)候壁晒,就會(huì)給CPU加wake_lock鎖。
保持常亮有這幾種方式:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)//保持常亮
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)//清理常亮
或者:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
///
</RelativeLayout>
保持CPU運(yùn)行
需要使用PowerManager這個(gè)系統(tǒng)服務(wù)的喚醒鎖(wake locks)特征來保持CPU處于喚醒狀態(tài)业栅。喚醒鎖允許程序控制宿主設(shè)備的電量狀態(tài)秒咐。創(chuàng)建和持有喚醒鎖對電池的續(xù)航有較大的影響,所以碘裕,除非是真的需要喚醒鎖完成盡可能短的時(shí)間在后臺(tái)完成的任務(wù)時(shí)才使用它携取。比如在Acitivity中就沒必要用了。
<uses-permission android:name="android.permission.WAKE_LOCK" />//權(quán)限
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");
wakeLock.acquire();
//wakeLock.release();//記得執(zhí)行完就釋放鎖帮孔,不然一直保持CPU喚醒也是十分耗電的
這樣可以完成保持CPU可以持續(xù)喚醒狀態(tài)但還有一種更好的方式雷滋,是使用WakefulBroadcastReceiver;
WakefulBroadcastReceiver是BroadcastReceiver的一種特例。它會(huì)為你的APP創(chuàng)建和管理一個(gè)PARTIAL_WAKE_LOCK 類型的WakeLock惊豺。WakefulBroadcastReceiver把工作交接給service(通常是IntentService)燎孟,并保證交接過程中設(shè)備不會(huì)進(jìn)入休眠狀態(tài)禽作。如果不持有WakeLock尸昧,設(shè)備很容易在任務(wù)未執(zhí)行完前休眠。最終結(jié)果是你的應(yīng)用不知道會(huì)在什么時(shí)候能把工作完成旷偿,相信這不是你想要的烹俗。
<receiver android:name=".MyWakefulReceiver"></receiver>
//使用startWakefulService()方法來啟動(dòng)服務(wù)和喚醒鎖
public class MyWakefulReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MyIntentService.class);
startWakefulService(context, service);
}
}
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
// 執(zhí)行耗時(shí)任務(wù)
...
// 結(jié)束任務(wù)時(shí)釋放喚醒鎖
MyWakefulReceiver.completeWakefulIntent(intent);
}
}
好,大概了解下WakefulBroadcastReceiver源碼萍程;
我們可以看到這兩個(gè)靜態(tài)方法幢妄,其實(shí)就是一個(gè)喚醒鎖的使用和釋放。
采用定時(shí)重復(fù)的Service開啟:
1茫负、利用Android自帶的定時(shí)器AlarmManager實(shí)現(xiàn)
Intent intent = new Intent(mContext, ServiceTest.class);
PendingIntent pi = PendingIntent.getService(mContext, 1, intent, 0);
AlarmManager alarm = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
if(alarm != null)
{
alarm.cancel(pi); // 鬧鐘在系統(tǒng)睡眠狀態(tài)下會(huì)喚醒系統(tǒng)并執(zhí)行提示功能
alarm.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, 2000, pi);// 確切的時(shí)間鐘
//alarm.setExact(…);
//alarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), pi);
}
AlarmManager為什么睡眠狀態(tài)下還能喚醒呢
首先Android手機(jī)有兩個(gè)處理器蕉鸳,一個(gè)叫Application Processor(AP),一個(gè)叫Baseband Processor(BP)忍法。AP是ARM架構(gòu)的處理器潮尝,用于運(yùn)行Linux+Android系統(tǒng);BP用于運(yùn)行實(shí)時(shí)操作系統(tǒng)(RTOS)饿序,通訊協(xié)議棧運(yùn)行于BP的RTOS之上勉失。非通話時(shí)間,BP的能耗基本上在5mA左右原探,而AP只要處于非休眠狀態(tài)乱凿,能耗至少在50mA以上,執(zhí)行圖形運(yùn)算時(shí)會(huì)更高咽弦。另外LCD工作時(shí)功耗在100mA左右徒蟆,WIFI也在100mA左右。一般手機(jī)待機(jī)時(shí)型型,AP段审、LCD、WIFI均進(jìn)入休眠狀態(tài)输莺,這時(shí)Android中應(yīng)用程序的代碼也會(huì)停止執(zhí)行戚哎。
Android為了確保應(yīng)用程序中關(guān)鍵代碼的正確執(zhí)行,提供了Wake Lock的API嫂用,使得應(yīng)用程序有權(quán)限通過代碼阻止AP進(jìn)入休眠狀態(tài)型凳。但如果不領(lǐng)會(huì)Android設(shè)計(jì)者的意圖而濫用Wake Lock API,為了自身程序在后臺(tái)的正常工作而長時(shí)間阻止AP進(jìn)入休眠狀態(tài)嘱函,就會(huì)成為待機(jī)電池殺手甘畅。比如前段時(shí)間的某應(yīng)用,比如現(xiàn)在仍然干著這事的某應(yīng)用。
那么Wake Lock API有啥用呢疏唾?比如心跳包從請求到應(yīng)答蓄氧,比如斷線重連重新登陸這些關(guān)鍵邏輯的執(zhí)行過程,就需要Wake Lock來保護(hù)槐脏。而一旦一個(gè)關(guān)鍵邏輯執(zhí)行成功喉童,就應(yīng)該立即釋放掉Wake Lock了。兩次心跳請求間隔5到10分鐘顿天,基本不會(huì)怎么耗電堂氯。除非網(wǎng)絡(luò)不穩(wěn)定,頻繁斷線重連牌废,那種情況辦法不多咽白。
AlarmManager 是Android 系統(tǒng)封裝的用于管理 RTC 的模塊,RTC (Real Time Clock) 是一個(gè)獨(dú)立的硬件時(shí)鐘鸟缕,可以在 CPU 休眠時(shí)正常運(yùn)行晶框,在預(yù)設(shè)的時(shí)間到達(dá)時(shí),通過中斷喚醒 CPU懂从。
1.關(guān)鍵邏輯的執(zhí)行過程授段,就需要Wake Lock來保護(hù)。如斷線重連重新登陸
2.休眠的情況下如何喚醒來執(zhí)行任務(wù)莫绣?用AlarmManager畴蒲。如推送消息的獲取
JobScheduler
先簡單介紹使用吧,首先要寫一個(gè)Service 繼承JobService,就需要實(shí)例化兩個(gè)方法onStartJob和onStopJob对室;
public class JobSchedulerService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
if (isNetworkConnected()) {
new SimpleDownloadTask() .execute(params);
return true;
} else {
Log.i(LOG_TAG, "No connection on job " + params.getJobId() + "; sad face");
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
private class SimpleDownloadTask extends AsyncTask<JobParameters, Void, String> {
protected JobParameters mJobParam;
@Override
protected String doInBackground(JobParameters... params) {
mJobParam = params[0];
try {
InputStream is = null;
int len = 50;
URL url = new URL("https://www.baidu.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000); //10sec
conn.setConnectTimeout(15000); //15sec
conn.setRequestMethod("GET");
conn.connect();
int response = conn.getResponseCode();
Log.d(LOG_TAG, "The response is: " + response);
is = conn.getInputStream();
Reader reader = null;
reader = new InputStreamReader(is, "UTF-8");
char[] buffer = new char[len];
reader.read(buffer);
return new String(buffer);
} catch (IOException e) {
return "Unable to retrieve web page.";
}
}
@Override
protected void onPostExecute(String result) {
jobFinished(mJobParam, false);
Log.i(LOG_TAG, result);
}
}
}
public void execute(View view) {
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo jobInfo = new JobInfo.Builder(i,serviceComponent)
.setMinimumLatency(5000)//5秒 最小延時(shí)模燥、
.setOverrideDeadline(60000)//maximum最多執(zhí)行時(shí)間
// .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)//免費(fèi)的網(wǎng)絡(luò)---wifi 藍(lán)牙 USB
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//任意網(wǎng)絡(luò)---
// 設(shè)置重試/退避策略,當(dāng)一個(gè)任務(wù)調(diào)度失敗的時(shí)候執(zhí)行什么樣的測量采取重試掩宜。
// initialBackoffMillis:第一次嘗試重試的等待時(shí)間間隔ms
// backoffPolicy:對應(yīng)的退避策略蔫骂。比如等待的間隔呈指數(shù)增長。
// .setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
.setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR)
// .setPeriodic (long intervalMillis)//設(shè)置執(zhí)行周期牺汤,每隔一段時(shí)間間隔任務(wù)最多可以執(zhí)行一次辽旋。
// .setPeriodic(long intervalMillis,long flexMillis)//在周期執(zhí)行的末端有一個(gè)flexMiliis長度的窗口期,任務(wù)就可以在這個(gè)窗口期執(zhí)行檐迟。
// .setPersisted(boolean isPersisted); //設(shè)置設(shè)備重啟后补胚,這個(gè)任務(wù)是否還要保留。需要權(quán)限:RECEIVE_BOOT_COMPLETED //ctrl+shift+y/u x
// .setRequiresCharging(boolean )//是否需要充電
// .setRequiresDeviceIdle(boolean)//是否需要等設(shè)備出于空閑狀態(tài)的時(shí)候
// .addTriggerContentUri(uri)//監(jiān)聽uri對應(yīng)的數(shù)據(jù)發(fā)生改變追迟,就會(huì)觸發(fā)任務(wù)的執(zhí)行溶其。
// .setTriggerContentMaxDelay(long duration)//設(shè)置Content發(fā)生變化一直到任務(wù)被執(zhí)行中間的最大延遲時(shí)間
// .setTriggerContentUpdateDelay(long durationMilimms)//設(shè)置Content發(fā)生變化一直到任務(wù)被執(zhí)行中間的延遲。如果在這個(gè)延遲時(shí)間內(nèi)content發(fā)生了改變敦间,延遲時(shí)間會(huì)重寫計(jì)算瓶逃。
.BUILD();
jobScheduler.schedule(jobInfo);
}
這段代碼中束铭,我們可以看到onStartJob中有個(gè)返回值,如果方法執(zhí)行結(jié)束厢绝,返回false契沫,否則,就返回true昔汉,調(diào)用jobFinished來告訴Sevice任務(wù)執(zhí)行完了.當(dāng)然懈万,這個(gè)JobScheduler是5.0以后,而5.0以前挤庇,使用的是GCM--Google Cloud Messaging钞速。
其實(shí)贷掖,現(xiàn)在我們?nèi)シ治鱿?JobScheduler,
首先由這樣幾個(gè)文件
JobScheduler.java
->JobSchedulerImpl.java
JobSchedulerService.java
-> JobSchedulerStub schedule()
IJobScheduler.aidl cancel() cancelAll() schedule() getAllPendingJobs()