2、屏幕保持常亮
爲了防止屏幕喚醒一瞬間耗電過多,有一些應用,比如遊戲、支付頁面,需要保持屏幕常亮來節省電量:
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
也可以在佈局文件裏面使用,但是沒有那麼靈活:
android:keepScreenOn=“true”
注意:一般不需要人爲的去掉FLAG_KEEP_SCREEN_ON的flag,windowManager會管理好程序進入後臺回到前臺的的操作。如果確實需要手動清掉常亮的flag,使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
3.1、使用wake_lock
系統爲了節省電量,CPU在沒有任務忙的時候就會自動進入休眠。有任務需要喚醒CPU高效執行的時候,就會給CPU加wake_lock鎖。wake_lock鎖主要是相對系統的休眠而言的,意思就是我的程序給CPU加了這個鎖那系統就不會休眠了,這樣做的目的是爲了全力配合我們程序的運行。有的情況如果不這麼做就會出現一些問題,比如微信等及時通訊的心跳包會在熄屏不久後停止網絡訪問等問題。所以微信裏面是有大量使用到了wake_lock鎖。
PowerManager這個系統服務的喚醒鎖(wake locks)特徵來保持CPU處於喚醒狀態。喚醒鎖允許程序控制宿主設備的電量狀態。創建和持有喚醒鎖對電池的續航有較大的影響,所以,除非是真的需要喚醒鎖完成儘可能短的時間在後臺完成的任務時才使用它。比如在Acitivity中就沒必要用了。一種典型的代表就是在屏幕關閉以後,後臺服務繼續保持CPU運行。
如果不使用喚醒鎖來執行後臺服務,不能保證因CPU休眠未來的某個時刻任務會停止,這不是我們想要的。(有的人可能認爲以前寫的後臺服務就沒掉過鏈子呀運行得挺好的,1.可能是你的任務時間比較短;2.可能CPU被手機裏面很多其他的軟件一直在喚醒狀態)。
其中,喚醒鎖有下面幾種類型:
喚醒鎖的類型.png
wake_lock兩種鎖(從釋放、使用的角度來看的話):
一種計數鎖
非計數鎖(鎖了很多次,只需要release一次就可以解除了)
Tips:請注意,自 API 等級17開始,FULL_WAKE_LOCK將被棄用,應使用FLAG_KEEP_SCREEN_ON代替。
綜上所述,爲了防止CPU喚醒一瞬間耗電過多,在執行關鍵代碼的時候,爲了防止CPU睡眠,需要使用喚醒鎖來節省電量:
//創建喚醒鎖
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "partial_lock");
//獲取喚醒鎖
wakeLock.acquire();
//一些關鍵的代碼
//釋放喚醒鎖
wakeLock.release();
需要添加權限:
<uses-permission android:name="android.permission.WAKE_LOCK"/>
Tips:獲取與釋放喚醒鎖需要成對出現
Tips:有一些意外的情況,比如小米手機是做了同步心跳包(心跳對齊)(如果超過了這個同步的頻率就會被屏蔽掉或者降頻),所有的app後臺喚醒頻率不能太高,這時候就需要降頻,比如每隔2S中去請求。
3.2、使用WakefulBroadcastReceiver
上面提到,典型的使用場景就是後臺服務需要保持CPU保持運行,推薦的方式是使用WakefulBroadcastReceiver:使用廣播和Service(典型的IntentService)結合的方式可以讓你很好地管理後臺服務的生命週期。
WakefulBroadcastReceiver是BroadcastReceiver的一種特例。它會爲你的APP創建和管理一個PARTIAL_WAKE_LOCK類型的WakeLock。WakefulBroadcastReceiver把工作交接給service(通常是IntentService),並保證交接過程中設備不會進入休眠狀態。如果不持有WakeLock,設備很容易在任務未執行完前休眠。最終結果是你的應用不知道會在什麼時候能把工作完成,相信這不是你想要的。
例子:
服務:
public class MyIntentService extends IntentService {
public MyIntentService(String name) {
super(name);
}
public MyIntentService() {
super(MyIntentService.class.getSimpleName());
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent != null) {
//獲取參數
Bundle extras = intent.getExtras();
//執行一些需要CPU保持喚醒的代碼
//執行結束,釋放喚醒鎖
MyWakefulReceiver.completeWakefulIntent(intent);
}
}
}
廣播接收者:
public class MyWakefulReceiver extends WakefulBroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MyIntentService.class);
startWakefulService(context, service);
}
}
需要使用服務的時候,像一般的方式一樣即可:
Intent intent = new Intent(this, MyIntentService.class);
//傳遞參數
intent.setData(Uri.parse(“xxx”));
Tips:注意添加權限
Tips:注意服務與廣播的註冊
Tips:使用廣播來設計,就是爲了解耦
3.3、大量高頻次的CPU喚醒及操作使用JobScheduler/GCM
大量高頻次的CPU喚醒及操作,我們可以採取一些算法來解決,把這些操作安排在一個時間點集中處理,而不是分開處理(這樣就可以防止了喚醒的耗電)。
我們可以使用谷歌提供的JobScheduler或者GCM來實現這樣的功能。
下面舉一個頻繁請求網絡的例子:
這是一個請求網絡的服務:
public class MyJobService extends JobService {
private static final String TAG = "MyJobService";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "MyJobService created");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "MyJobService destroyed");
}
/**
* 開啓耗時操作
* @param params
* @return
*/
@Override
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "onStartJob:" + params.getJobId());
if (isNetworkConnected()) {
new SimpleDownloadTask() .execute(params);
return true;
} else {
Log.i(TAG, "No connection:" + params.getJobId());
}
return false;
}
/**
* jobFinish調用之前會回調
* @param params
* @return
*/
@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "onStopJob:" + params.getJobId());
return false;
}
private boolean isNetworkConnected() {
ConnectivityManager connectivityManager =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
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(TAG, "The response is: " + response);
is = conn.getInputStream();
Reader reader = new InputStreamReader(is, "UTF-8");
char[] buffer = new char[len];
reader.read(buffer);
return new String(buffer);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onPostExecute(String result) {
//結束任務
jobFinished(mJobParam, false);
Log.i(TAG, result);
}
}
}
下面通過循環來模擬頻繁調用:
ComponentName serviceComponent = new ComponentName(this,MyJobService.class);
//頻繁地喚醒
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
for (int i = 0; i < 500; i++) {
JobInfo jobInfo = new JobInfo.Builder(i,serviceComponent)
.setMinimumLatency(5000)//最小延時5秒
.setOverrideDeadline(60000)//最多執行時間60秒
//.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)//免費的網絡---wifi 藍牙 USB
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)//任意網絡---wifi
.build();
jobScheduler.schedule(jobInfo);
}
4、使用AlarmManager來喚醒
當機器一段時間不操作以後,就會進入睡眠狀態。向服務器的輪詢就會停止、長連接就會斷開,爲了防止這樣的情況,就可以使用AlarmManager:
Intent intent = new Intent(this, TestService.class);
PendingIntent pi = PendingIntent.getService(this, 0, intent, 0);
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
am.cancel(pi);
//鬧鐘在系統睡眠狀態下會喚醒系統並執行提示功能
//模糊時間,在API-19中以及以前,setRepeating都是不準確的
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, 2000, pi);
//準確時間,但是需要在API-17之後使用
am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, pi);
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, pi);
該定時器可以啓動Service服務、發送廣播、跳轉Activity,並且會在系統睡眠狀態下喚醒系統。所以該方法不用獲取電源鎖和釋放電源鎖。
關於AlarmManager的更多信息,請參考其他文章。
在19以上版本,setRepeating中設置的頻率只是建議值(6.0 的源碼中最小值是60s),如果要精確一些的用setWindow或者setExact。
5、其他優化
當然,電量優化是包括很多方面的,例如:
渲染優化
定位策略優化
網絡優化,例如網絡緩存處理,請求方式、次數優化、設置超時時間等等
代碼執行效率優化
防止內存泄漏
等等,電量優化無處不在。
深化
首先Android手機有兩個處理器,一個叫Application Processor(AP),一個叫Baseband Processor(BP)。AP是ARM架構的處理器,用於運行Linux+Android系統;BP用於運行實時操作系統(RTOS),通訊協議棧運行於BP的RTOS之上。非通話時間,BP的能耗基本上在5mA左右,而AP只要處於非休眠狀態,能耗至少在50mA以上,執行圖形運算時會更高。另外LCD工作時功耗在100mA左右,WIFI也在100mA左右。一般手機待機時,AP、LCD、WIFI均進入休眠狀態,這時Android中應用程序的代碼也會停止執行。
Android爲了確保應用程序中關鍵代碼的正確執行,提供了Wake Lock的API,使得應用程序有權限通過代碼阻止AP進入休眠狀態。但如果不領會Android設計者的意圖而濫用Wake Lock API,爲了自身程序在後臺的正常工作而長時間阻止AP進入休眠狀態,就會成爲待機電池殺手。比如前段時間的某應用,比如現在仍然幹着這事的某應用。
AlarmManager 是Android 系統封裝的用於管理 RTC 的模塊,RTC (Real Time Clock) 是一個獨立的硬件時鐘,可以在 CPU 休眠時正常運行,在預設的時間到達時,通過中斷喚醒 CPU。(極光推送就是利用這個來做的。)
總結:
關鍵邏輯的執行過程,就需要Wake Lock來保護。如斷線重連重新登陸
休眠的情況下如何喚醒來執行任務?用AlarmManager。如推送消息的獲取
最後,通過Wakelock Detector(WLD)軟件可以看到手機中的Wakelock: