基於V2.0版本的battery historian請先看 battery historian安裝與使用
#####(1).橫座標 橫座標就是一個時間範圍,我們們的例子中統計的資料是以重置為起點,獲取bugreport內容時刻為終點。我們一共採集了多長時間的資料
#####(2).縱座標 關鍵的資料點我們用表格來彙總一下。
引數名 | 作用 |
---|---|
CPU running | CPU的執行狀態,是否被喚醒。如果把滑鼠放到上面去,還能看到更多的資訊,如CPU喚醒的原因。 |
Screen | 亮屏狀態,可以看到圖表中該項著色有間隔,這是因為實驗期間我關閉過螢幕,每關閉一次螢幕,著色就被打斷。 |
Top app | 當前最上層的app |
Mobile network type | 網路型別,其中需要注意的是,“免費網路可能包括wifi、藍芽網路共享、USB網路共享” |
Mobile radio active | 移動蜂窩訊號 BP側耗電,通常是指SIM卡,資料連結。該欄過多著色,間隔多。表示功耗也會高。 |
WiFi supplicant | wifi是否開啟 |
WiFi signal strength | wifi強度 |
Wifi Running | wifi連線情況下的耗電情況 |
Audio | 音訊是否開啟 |
Battery Level | 電量 |
Plugged | 是否正在充電,以及滑鼠放在上面的時候可以看到充電型別,包括AC(充電器)、USB、其它(例如無線充電) |
Battery Level | 開始測試時的電量,之前抓取的圖可以看到電量是100,滿電狀態。 |
Top app | 前臺應用,如果要分析應用的耗電情況,那麼在測試期間,就該保證應用一直處於前臺。 |
Userspace wakelock | 記錄wake_lock模組的工作時間 |
ps:系統為了節省電量,CPU在沒有任務忙的時候就會自動進入休眠。有任務需要喚醒CPU高效執行的時候,就會給CPU加wake_lock鎖。
####電量優化建議:
當Android裝置空閒時,螢幕會變暗,然後關閉螢幕,最後會停止CPU的執行,這樣可以防止電池電量掉的快。在休眠過程中自定義的Timer、Handler、Thread、Service等都會暫停。但有些時候我們需要改變Android系統預設的這種狀態:比如玩遊戲時我們需要保持螢幕常亮,比如一些下載操作不需要螢幕常亮但需要CPU一直執行直到任務完成。從而防止因為喚醒的瞬間而耗更多的電。
####1、判斷充電狀態
這裡我們就需要思考,根據我們自己的業務,那些為了省電,可以放當手機插上電源的時候去做。 往往這樣的情況非常多。像這些不需要及時地和使用者互動的操作可以放到後面處理。 比如:360手機助手,當充上電的時候,才會自動清理手機垃圾,自動備份上傳圖片、聯絡人等到雲端;再比如我們自己的APP,其中有一塊業務是相簿備份,這個時候有一個選項控制讓使用者選擇是否在低於15%的電量時還繼續進行備份,從而避免當使用者手機低電量時,任然繼續進行耗電操作。
我們可以通過下面的程式碼來獲取手機的當前充電狀態:
// 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);
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
if (acCharge) {
Log.v(LOG_TAG,“The phone is charging!”);
}
複製程式碼
private boolean checkForPower() {
//獲取電池的充電狀態(註冊一個廣播)
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent res = this.registerReceiver(null, filter);
//通過使用BatteryManager的引數資訊判斷充電狀態
if (res != null) {
int chargePlug = res.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usb = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;//usb充電
boolean ac = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;//交流電
//無線充電,這個需要API>=17
boolean wireless = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
wireless = chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
return (usb || ac || wireless);
} else {
return false;
}
}
複製程式碼
呼叫示例
private void applyFilter() {
//是否在充電
if(!checkForPower()){
mPowerMsg.setText("請充上電,再處理!");
return;
}
mCheyennePic.setImageResource(R.drawable.pink_cheyenne);
mPowerMsg.setText(R.string.photo_filter);
}
複製程式碼
####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)
複製程式碼
#####android:keepScreenOn = ” true “的作用和FLAG_KEEP_SCREEN_ON一樣。使用程式碼的好處是你允許你在需要的地方關閉螢幕。
注意:一般不需要人為的去掉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被手機裡面很多其他的軟體一直在喚醒狀態)。 其中,喚醒鎖有下面幾種型別:
######wake_lock兩種鎖(從釋放、使用的角度來看的話):計數鎖和非計數鎖(鎖了很多次,只需要release一次就可以解除了)
#####請注意,自 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。一個WakeBroadcastReceiver接收到廣播後將工作傳遞給Service(一個典型的IntentService),直到確保裝置沒有休眠。如果你在交接工作給服務的時候沒有保持喚醒鎖,在工作還沒完成之前就允許裝置休眠的話,將會出現一些你不願意看到的情況。
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"));
複製程式碼
#####分析WakefulBroadcastReceiver 原始碼(檢視原始碼記得引入v4包)
public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks
= new SparseArray<PowerManager.WakeLock>();
private static int mNextId = 1;
/**
* Do a {@link android.content.Context#startService(android.content.Intent)
* Context.startService}, but holding a wake lock while the service starts.
* This will modify the Intent to hold an extra identifying the wake lock;
* when the service receives it in {@link android.app.Service#onStartCommand
* Service.onStartCommand}, it should pass back the Intent it receives there to
* {@link #completeWakefulIntent(android.content.Intent)} in order to release
* the wake lock.
*
* @param context The Context in which it operate.
* @param intent The Intent with which to start the service, as per
* {@link android.content.Context#startService(android.content.Intent)
* Context.startService}.
*/
public static ComponentName startWakefulService(Context context, Intent intent) {
synchronized (mActiveWakeLocks) {
int id = mNextId;
mNextId++;
if (mNextId <= 0) {
mNextId = 1;
}
intent.putExtra(EXTRA_WAKE_LOCK_ID, id);
ComponentName comp = context.startService(intent);
if (comp == null) {
return null;
}
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"wake:" + comp.flattenToShortString());
wl.setReferenceCounted(false);
wl.acquire(60*1000);
mActiveWakeLocks.put(id, wl);
return comp;
}
}
/**
* Finish the execution from a previous {@link #startWakefulService}. Any wake lock
* that was being held will now be released.
*
* @param intent The Intent as originally generated by {@link #startWakefulService}.
* @return Returns true if the intent is associated with a wake lock that is
* now released; returns false if there was no wake lock specified for it.
*/
public static boolean completeWakefulIntent(Intent intent) {
final int id = intent.getIntExtra(EXTRA_WAKE_LOCK_ID, 0);
if (id == 0) {
return false;
}
synchronized (mActiveWakeLocks) {
PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
if (wl != null) {
wl.release();
mActiveWakeLocks.remove(id);
return true;
}
// We return true whether or not we actually found the wake lock
// the return code is defined to indicate whether the Intent contained
// an identifier for a wake lock that it was supposed to match.
// We just log a warning here if there is no wake lock found, which could
// happen for example if this function is called twice on the same
// intent or the process is killed and restarted before processing the intent.
Log.w("WakefulBroadcastReceiver", "No active wake lock id #" + id);
return true;
}
}
}
複製程式碼
######我們發現WakefulBroadcastReceiver 本質上還是基於 PowerManager.WakeLock ######注意: ######1.注意新增許可權 ######2.注意服務與廣播的註冊 ######3.使用廣播來設計,就是為了解耦
網上採集的一些問題坑點及解決如下: ######1.向伺服器輪詢的程式碼不執行。 曾經做一個應用,利用Timer和TimerTask,來設定對伺服器進行定時的輪詢,但是發現機器在某段時間後,輪詢就不再進行了。查了很久才發 現是休眠造成的。後來解決的辦法是,利用系統的AlarmService來執行輪詢。因為雖然系統讓機器休眠,節省電量,但並不是完全的關機,系統有一部 分優先順序很高的程式還是在執行的,比如鬧鐘,利用AlarmService可以定時啟動自己的程式,讓cpu啟動,執行完畢再休眠。 #####解決:利用系統的AlarmService來替代Timer和TimerTask 執行輪詢 ######2.後臺長連線斷開。 最近遇到的問題。利用Socket長連線實現QQ類似的聊天功能,發現在螢幕熄滅一段時間後,Socket就被斷開。螢幕開啟的時候需進行重連,但 每次看Log的時候又發現網路是連結的,後來才發現是cpu休眠導致連結被斷開,當你插上資料線看log的時候,網路cpu恢復,一看網路確實是連結的, 坑。最後使用了PARTIAL_WAKE_LOCK,保持CPU不休眠。 #####解決:使用了PARTIAL_WAKE_LOCK,保持CPU不休眠。 ######3.除錯時是不會休眠的。 讓我非常鬱悶的是,在除錯2的時候,就發現,有時Socket會斷開,有時不會斷開,後來才搞明白,因為我有時是插著資料線進行除錯,有時拔掉資料線,這 時Android的休眠狀態是不一樣的。而且不同的機器也有不同的表現,比如有的機器,插著資料線就會充電,有的不會,有的機器的設定的充電時螢幕不變暗 等等,把自己都搞暈了。其實搞明白這個休眠機制,一切都好說了。
######通過Wakelock Detector(WLD)軟體可以看到手機中的Wakelock:
####3.3、大量高頻次的CPU喚醒及操作使用JobScheduler/GCM 自 Android 5.0 釋出以來,JobScheduler 已成為執行後臺工作的首選方式,其工作方式有利於使用者。應用可以在安排作業的同時允許系統基於記憶體、電源和連線情況進行優化。JobSchedule的宗旨就是把一些不是特別緊急的任務放到更合適的時機批量處理。這樣做有兩個好處:
避免頻繁的喚醒硬體模組,造成不必要的電量消耗。 避免在不合適的時間(例如低電量情況下、弱網路或者行動網路情況下的)執行過多的任務消耗電量;
JobScheduler的簡單使用,首先自定義一個Service類,繼承自JobService
public class JobSchedulerService extends JobService{
private String TAG = JobSchedulerService.class.getSimpleName();
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d(TAG, "onStartJob:" + jobParameters.getJobId());
if(true) {
// JobService在主執行緒執行,如果我們這裡需要處理比較耗時的業務邏輯需單獨開啟一條子執行緒來處理並返回true,
// 當給定的任務完成時通過呼叫jobFinished(JobParameters params, boolean needsRescheduled)告知系統。
//假設開啟一個執行緒去下載檔案
new DownloadTask().execute(jobParameters);
return true;
}else {
//如果只是在本方法內執行一些簡單的邏輯話返回false就可以了
return false;
}
}
/**
* 比如我們的服務設定的約束條件為在WIFI狀態下執行,結果在任務執行的過程中WIFI斷開了系統
* 就會通過回掉onStopJob()來通知我們停止執行,正常的情況下不會回掉此方法
*
* @param jobParameters
* @return
*/
@Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d(TAG, "onStopJob:" + jobParameters.getJobId());
//如果需要服務在設定的約定條件再次滿足時再次執行服務請返回true,反之false
return true;
}
class DownloadTask extends AsyncTask<JobParameters, Object, Object> {
JobParameters mJobParameters;
@Override
protected Object doInBackground(JobParameters... jobParameterses) {
mJobParameters = jobParameterses[0];
//比如說我們這裡處理一個下載任務
//或是處理一些比較複雜的運算邏輯
//...
try {
Thread.sleep(30*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
//如果在onStartJob()中返回true的話,處理完成邏輯後一定要執行jobFinished()告知系統已完成,
//如果需要重新安排服務請true,反之false
jobFinished(mJobParameters, false);
}
}
}
複製程式碼
記得在Manifest檔案內配置Service
<service android:name=".JobSchedulerService" android:permission="android.permission.BIND_JOB_SERVICE"/>
複製程式碼
建立工作計劃
public class MainActivity extends Activity{
private JobScheduler mJobScheduler;
private final int JOB_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mai_layout);
mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE );
//通過JobInfo.Builder來設定觸發服務的約束條件,最少設定一個條件
JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_ID, new ComponentName(this, JobSchedulerService.class));
//迴圈觸發,設定任務每三秒定期執行一次
jobBuilder.setPeriodic(3000);
//單次定時觸發,設定為三秒以後去觸發。這是與setPeriodic(long time)不相容的,
// 並且如果同時使用這兩個函式將會導致丟擲異常。
jobBuilder.setMinimumLatency(3000);
//在約定的時間內設定的條件都沒有被觸發時三秒以後開始觸發。類似於setMinimumLatency(long time),
// 這個函式是與 setPeriodic(long time) 互相排斥的,並且如果同時使用這兩個函式,將會導致丟擲異常。
jobBuilder.setOverrideDeadline(3000);
//在裝置重新啟動後設定的觸發條件是否還有效
jobBuilder.setPersisted(false);
// 只有在裝置處於一種特定的網路狀態時,它才觸發。
// JobInfo.NETWORK_TYPE_NONE,無論是否有網路均可觸發,這個是預設值;
// JobInfo.NETWORK_TYPE_ANY,有網路連線時就觸發;
// JobInfo.NETWORK_TYPE_UNMETERED,非蜂窩網路中觸發;
// JobInfo.NETWORK_TYPE_NOT_ROAMING,非漫遊網路時才可觸發;
jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
/**
設定重試/退避策略,當一個任務排程失敗的時候執行什麼樣的測量採取重試。
initialBackoffMillis:第一次嘗試重試的等待時間間隔ms
*backoffPolicy:對應的退避策略。比如等待的間隔呈指數增長。
*/
// jobBuilder .setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
jobBuilder .setBackoffCriteria(JobInfo.MAX_BACKOFF_DELAY_MILLIS, JobInfo.BACKOFF_POLICY_LINEAR)
//設定手機充電狀態下觸發
jobBuilder.setRequiresCharging(true);
//設定手機處於空閒狀態時觸發
jobBuilder.setRequiresDeviceIdle(true);
//得到JobInfo物件
JobInfo jobInfo = jobBuilder.build();
//設定開始安排任務,它將返回一個狀態碼
//JobScheduler.RESULT_SUCCESS,成功
//JobScheduler.RESULT_FAILURE,失敗
if (mJobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
//安排任務失敗
}
//停止指定JobId的工作服務
mJobScheduler.cancel(JOB_ID);
//停止全部的工作服務
mJobScheduler.cancelAll();
}
複製程式碼
量高頻次的CPU喚醒及操作,我們最好把這些操作集中處理。我們可以採取一些演算法來解決。 可以借鑑谷歌的精髓,JobScheduler/GCM。
####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。(極光推送就是利用這個來做的。)
####總結: ######1.關鍵邏輯的執行過程,就需要Wake Lock來保護。如斷線重連重新登陸 ######2.休眠的情況下如何喚醒來執行任務?用AlarmManager。如推送訊息的獲取
其他參考資料: alarmManager在手機休眠時無法喚醒Service的問題?( 為了對付的頻繁喚醒的”流氓”app,有的廠家都開發了心跳對齊。) https://www.zhihu.com/question/36421849 微信 Android 版 6.2 為什麼設定了大量長時間的隨機喚醒鎖? https://www.zhihu.com/question/31136645