
引言
1. Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存泄漏
2.Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存優(yōu)化分析工具
3.Android性能優(yōu)化篇之UI渲染性能優(yōu)化
4.Android性能優(yōu)化篇之計(jì)算性能優(yōu)化
5.Android性能優(yōu)化篇之電量?jī)?yōu)化(1)——電量消耗分析
6.Android性能優(yōu)化篇之電量?jī)?yōu)化(2)
7.Android性能優(yōu)化篇之網(wǎng)絡(luò)優(yōu)化
8.Android性能優(yōu)化篇之Bitmap優(yōu)化
9.Android性能優(yōu)化篇之圖片壓縮優(yōu)化
10.Android性能優(yōu)化篇之多線程并發(fā)優(yōu)化
11.Android性能優(yōu)化篇之?dāng)?shù)據(jù)傳輸效率優(yōu)化
12.Android性能優(yōu)化篇之程序啟動(dòng)時(shí)間性能優(yōu)化
13.Android性能優(yōu)化篇之安裝包性能優(yōu)化
14.Android性能優(yōu)化篇之服務(wù)優(yōu)化
介紹
這篇我們來(lái)分析電量消耗快的原因,電量?jī)?yōu)化的方案。講之前我們先來(lái)了解下如何保持設(shè)備喚醒狀態(tài)?
一.如何保持設(shè)備喚醒狀態(tài)?
當(dāng)Android設(shè)備空閑時(shí),屏幕會(huì)變暗,然后關(guān)閉屏幕,最后會(huì)停止CPU的運(yùn)行,這樣可以防止電池電量掉的快。在休眠過(guò)程中自定義的Timer、Handler、Thread、Service等都會(huì)暫停。
什么情況下需要喚醒設(shè)備?
對(duì)于一些帶通訊功能的應(yīng)用,通訊的心跳包會(huì)在熄屏不久后停止網(wǎng)絡(luò)訪問(wèn),所以需要定時(shí)喚醒cpu。
后臺(tái)關(guān)鍵邏輯代碼執(zhí)行時(shí),防止cpu休眠
后臺(tái)長(zhǎng)連接的狀態(tài)
后臺(tái)定時(shí)任務(wù)執(zhí)行
下面我們來(lái)看下常用的喚醒設(shè)備的方法有哪些
1.保持屏幕常亮
有兩種方式:
(1).activity中設(shè)置
//保持屏幕常亮
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//取消屏幕常亮
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
(2).配置文件中設(shè)置
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_screen_on"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
這個(gè)方法的好處是不像喚醒鎖(wake locks),需要一些特定的權(quán)限(permission)。并且能正確管理不同app之間的切換,不用擔(dān)心無(wú)用資源的釋放問(wèn)題。
2.保持CPU運(yùn)行(wake locks)
ake_lock鎖主要是相對(duì)系統(tǒng)的休眠而言的,意思就是我的程序給CPU加了這個(gè)鎖那系統(tǒng)就不會(huì)休眠了,這樣做的目的是為了全力配合我們程序的運(yùn)行。
需要使用PowerManager這個(gè)系統(tǒng)服務(wù)的喚醒鎖(wake locks)特征來(lái)保持CPU處于喚醒狀態(tài)。喚醒鎖允許程序控制宿主設(shè)備的電量狀態(tài)。創(chuàng)建和持有喚醒鎖對(duì)電池的續(xù)航有較大的影響,所以,除非是真的需要喚醒鎖完成盡可能短的時(shí)間在后臺(tái)完成的任務(wù)時(shí)才使用它。使用場(chǎng)景
在使用后臺(tái)服務(wù)在屏幕關(guān)閉情況下hold住CPU完成一些工作。
不使用wake locks,執(zhí)行長(zhǎng)時(shí)間任務(wù)可能導(dǎo)致的問(wèn)題?
如果不使用喚醒鎖來(lái)執(zhí)行后臺(tái)服務(wù),不能保證因CPU休眠未來(lái)的某個(gè)時(shí)刻任務(wù)會(huì)停止
喚醒鎖可劃分為并識(shí)別四種用戶喚醒鎖:

注意: android sdk 大于17 后,F(xiàn)ULL_WAKE_LOCK 將被棄用。 應(yīng)用應(yīng)使用 FLAG_KEEP_SCREEN_ON。
wake locks 使用步驟
(1).添加喚醒鎖權(quán)限
<uses-permission android:name="android.permission.WAKE_LOCK" />
(2).使用
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "myPartialWakeLock");
//喚醒
wakeLock.acquire();
//執(zhí)行任務(wù)
doJob();
//釋放鎖
if (wakeLock.isHeld()) {
wakeLock.release();
}
注意:在使用該類的時(shí)候,必須保證acquire和release是成對(duì)出現(xiàn)的。
我們也可以使用帶超時(shí)的acquire,防止沒有手動(dòng)釋放
//帶超時(shí)的喚醒鎖,超時(shí)后自動(dòng)釋放鎖
wakeLock.acquire(timeout);
WakefulBroadcastReceiver
官方推薦使用WakefulBroadcastReceiver。下面來(lái)看下定義和使用。
WakefulBroadcastReceiver是BroadcastReceiver的一種特例。它會(huì)為你的APP創(chuàng)建和管理一個(gè)PARTIAL_WAKE_LOCK 類型的WakeLock。WakefulBroadcastReceiver把工作交接給service(通常是IntentService),并保證交接過(guò)程中設(shè)備不會(huì)進(jìn)入休眠狀態(tài)。如果不持有WakeLock,設(shè)備很容易在任務(wù)未執(zhí)行完前休眠。最終結(jié)果是你的應(yīng)用不知道會(huì)在什么時(shí)候能把工作完成,相信這不是你想要的。WakefulBroadcastReceiver使用:
(1).注冊(cè)
<receiver android:name=".battery.MyWakefulReceiver"></receiver>
(2).使用
public class MyWakefulReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent mIntent = new Intent(context,MyService.class);
startWakefulService(context,mIntent);
}
}
public class MyService extends IntentService {
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public MyService(String name) {
super(name);
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
//執(zhí)行任務(wù)
doJob();
MyWakefulReceiver.completeWakefulIntent(intent);
}
3.AlarmManager 喚醒CPU
為什么AM能在cpu休眠時(shí),喚醒它呢?
首先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)限通過(guò)代碼阻止AP進(jìn)入休眠狀態(tài)。但如果不領(lǐng)會(huì)Android設(shè)計(jì)者的意圖而濫用Wake Lock API,為了自身程序在后臺(tái)的正常工作而長(zhǎng)時(shí)間阻止AP進(jìn)入休眠狀態(tài),就會(huì)成為待機(jī)電池殺手。
AlarmManager 是Android 系統(tǒng)封裝的用于管理 RTC 的模塊,RTC (Real Time Clock) 是一個(gè)獨(dú)立的硬件時(shí)鐘,可以在 CPU 休眠時(shí)正常運(yùn)行,在預(yù)設(shè)的時(shí)間到達(dá)時(shí),通過(guò)中斷喚醒 CPU。
使用:
public void btn_alarm(View view){
Intent mIntent = new Intent(view.getContext(),TestService.class);
PendingIntent pendingIntent = PendingIntent.getService(view.getContext(),mRequestCode,mIntent,mFlags);
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
if(alarmManager != null){
long mTriggerTime = System.currentTimeMillis() + 1000;
long mItervalTime = 2000;
alarmManager.cancel(pendingIntent);
//鬧鐘在系統(tǒng)睡眠狀態(tài)下會(huì)喚醒系統(tǒng)并執(zhí)行提示功能
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,mTriggerTime,mItervalTime,pendingIntent);
// alarmManager.setExact(AlarmManager.RTC_WAKEUP,mTriggerTime,pendingIntent);
// alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), pendingIntent);
}
}
該定時(shí)器可以啟動(dòng)Service服務(wù)、發(fā)送廣播、跳轉(zhuǎn)Activity,并且會(huì)在系統(tǒng)睡眠狀態(tài)下喚醒系統(tǒng)。所以該方法不用獲取電源鎖和釋放電源鎖。
注意:在19以上版本,setRepeating中設(shè)置的頻繁只是建議值(6.0 的源碼中最小值是60s),如果要精確一些的用setWindow或者setExact。
注意:由于手機(jī)廠商做了心跳對(duì)齊,所有的app后臺(tái)喚醒頻率不能太高,不然會(huì)無(wú)效。
總結(jié):
1.關(guān)鍵邏輯的執(zhí)行過(guò)程,就需要Wake Lock來(lái)保護(hù)。如斷線重連重新登陸
2.休眠的情況下如何喚醒來(lái)執(zhí)行任務(wù)?用AlarmManager。如推送消息的獲取
注意:如果請(qǐng)求網(wǎng)絡(luò)很差,會(huì)要很長(zhǎng)的時(shí)間,一般我們谷歌建議一定要設(shè)置請(qǐng)求超時(shí)時(shí)間。
二.電量?jī)?yōu)化的一些建議
1.充電時(shí)執(zhí)行任務(wù)
為了省電,有些工作可以放當(dāng)手機(jī)插上電源的時(shí)候去做。往往這樣的情況非常多。像這些不需要及時(shí)地和用戶交互的操作可以放到后面處理。
if (!checkForPower()) {
Toast.makeText(view.getContext(), "當(dāng)前非充電狀態(tài)", Toast.LENGTH_SHORT).show();
return;
}
/**
* 是否充電
* AC --- 交流電
* USB
* WireLess -- 無(wú)線充電
*
* @return
*/
private boolean checkForPower() {
IntentFilter mIntentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent intent = registerReceiver(null, mIntentFilter);
int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean isUsb = plugged == BatteryManager.BATTERY_PLUGGED_USB;
boolean isAc = plugged == BatteryManager.BATTERY_PLUGGED_AC;
boolean isWireless = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
//api >= 17
isWireless = plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
return (isUsb || isAc || isWireless);
}
2.連接Wifi后執(zhí)行任務(wù)
我們知道wifi網(wǎng)絡(luò)傳輸?shù)碾娏肯囊纫苿?dòng)網(wǎng)絡(luò)少很多,應(yīng)該盡量減少移動(dòng)網(wǎng)絡(luò)下的數(shù)據(jù)傳輸,多在WiFi環(huán)境下傳輸數(shù)據(jù),所以我們可以把一些不需要實(shí)時(shí)性的任務(wù)留到連接wifi后在執(zhí)行
3.wake_lock
系統(tǒng)為了節(jié)省電量,CPU在沒有任務(wù)忙的時(shí)候就會(huì)自動(dòng)進(jìn)入休眠。
有任務(wù)需要喚醒CPU高效執(zhí)行的時(shí)候,就會(huì)給CPU加wake_lock鎖。
但是根據(jù)我們上面的講解,使用wake_lock結(jié)束時(shí)需要釋放鎖,如果忘記釋放,會(huì)使得CPU一直執(zhí)行消耗電量,所以推薦使用帶超時(shí)的wake lock或者WakefulBroadcastReceiver
wakeLock.acquire(timeout);
4.大量高頻次的CPU喚醒及操作集中處理
我們希望把頻繁的間隔任務(wù)集中起來(lái)進(jìn)行批量執(zhí)行, 這正是JobScheduler API所做的事情。它會(huì)根據(jù)當(dāng)前的情況與任務(wù),組合出理想的喚醒時(shí)間,例如等到正在充電或者連接到WiFi的時(shí)候,或者集中任務(wù)一起執(zhí)行。我們可以通過(guò)這個(gè)API實(shí)現(xiàn)很多免費(fèi)的調(diào)度算法。
image3.png

JobScheduler
使用Job Scheduler,應(yīng)用需要做的事情就是判斷哪些任務(wù)是不緊急的,可以交給Job Scheduler來(lái)處理,Job Scheduler集中處理收到的任務(wù),選擇合適的時(shí)間,合適的網(wǎng)絡(luò),再一起進(jìn)行執(zhí)行。
使用(android api >= 21)
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class JobWakeUpService extends JobService{
private int mJobID = 100;
private JobScheduler mJobScheduler;
private long mIntervalMillis = 6000;
private long mMinLatencyMillis = 5000;
private long mMaxExecutionDelayMillis = 10000;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
JobInfo mJobInfo = new JobInfo.Builder(mJobID,new ComponentName(this,JobWakeUpService.class))
.setPeriodic(mIntervalMillis)//設(shè)備重啟,任務(wù)是否保留
.setMinimumLatency(mMinLatencyMillis)//最小延時(shí)
.setOverrideDeadline (mMaxExecutionDelayMillis)//最大執(zhí)行時(shí)間
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//網(wǎng)絡(luò)類型 NETWORK_TYPE_UNMETERED(wifi 藍(lán)牙)
.setRequiresCharging(true) //充電時(shí)執(zhí)行
//設(shè)置重試/退避策略 (重試時(shí)間,重試時(shí)間間隔)
.setBackoffCriteria(mInitialBackoffMillis,JobInfo.BACKOFF_POLICY_LINEAR)
.build();
mJobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
mJobScheduler.schedule(mJobInfo);
return START_STICKY;
}
@Override
public boolean onStartJob(JobParameters params) {
if(!isServiceWork(this,TestService.class.getName())){
startService(new Intent(this,TestService.class));
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
mJobScheduler.cancel(mJobID);
// mJobScheduler.cancelAll();
return false;
}
/**
* 查詢服務(wù)是否開啟
* @param context
* @param serviceName
* @return
*/
private boolean isServiceWork(Context context, String serviceName){
ActivityManager am= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(100);
if(runningServices == null){
return false;
}
for (ActivityManager.RunningServiceInfo service : runningServices) {
String className = service.service.getClassName();
if(className.equals(serviceName)){
return true;
}
}
return false;
}
5.定位
定位完成,及時(shí)關(guān)閉
如果需要實(shí)時(shí)定位,減少更新頻率
根據(jù)實(shí)際情況,選擇gsp定位還是網(wǎng)絡(luò)定位,降低電量消耗
6.網(wǎng)絡(luò)優(yōu)化
手機(jī)的通過(guò)內(nèi)置的射頻模塊和基站幾乎, 從而鏈接上網(wǎng)的, 而這個(gè)射頻模塊(radio)是非常耗電的.
為了控制這個(gè)射頻模塊的耗電, 硬件驅(qū)動(dòng)及Android RIL層做了很多處理. 例如可以單獨(dú)關(guān)閉radio(飛行模式), 間歇性假休眠radio(有數(shù)據(jù)發(fā)生時(shí)才上電, 保持一個(gè)頻率的與基站交互)等等.
具體的優(yōu)化請(qǐng)看關(guān)于網(wǎng)絡(luò)優(yōu)化的文章。
