AlarmManager實現精準定時任務
阿新 • • 發佈:2018-12-23
在專案中,有這麼一個功能點,app程序中,需要實現一個定時任務,只要裝置處於開機狀態,每隔半個小時,就需要定時向伺服器上傳一次位置資訊,並且只要是有網路和獲取到GPS訊號,程序不能死,如果程序死掉了,需要自動重啟。對該點進行細分梳理,包含如下幾個小功能點:
1.程序能夠實現開機啟動。
2.程序需要一直存活,並且能夠自動重啟。
3.需要定時(30分鐘)一次,向server端上報資訊。
針對以上三個功能點,第1和2點,實現起來,都不難,唯獨第三點,在實現時,一般情況,會考慮到多種方式實現。由於沒有自己查閱相關資料,在實現定時上傳功能時,分別使用了三種方案,導致多花了冤枉時間,吃力不討好,最終的結果是,定時功能不準,時間跨度越長,誤差越大。
方案一. 死迴圈中,使用sleep方法
for(;;)
Tread.sleep 30分鐘
方案二. timer機制
timerTask 30分鐘一次
最終方案三.AlarmManager進行精準定時
使用系統級AlarmManager,進行精準定時,與系統鬧鐘的類似功能點。
方案一和方案二的定時方案不準,主要原因是sleep和timerTask的內部執行緒執行的時間。在thread執行時,CPU才開始計算時間,當執行緒掛起,CPU沒有將時間片交給該執行緒,就沒有計算時間。實際使用中,app在後臺執行越長,誤差就越大。
由於之前沒有使用過AlarmManager功能,對其各個API不熟悉,並且存在如下的一些疑問:
question 1:程序死掉後,AlarmManager是否還有效果?是否需要使用自動重啟的service來重啟程序?
question 2:裝置reboot後,設定的AlarmManager是否還有效?
question 3:重複set相同的alarm ,是否會影響AlarmManager的定時機制?
文章提到,在裝置reboot後,為了確保AlarmManager的setInexactRepeating還有效,必須在裝置接收到ACTION_BOOT_COMPLETED事件時,重新進行set
alarm。
經過不斷的嘗試,在裝置上進行驗證,實現精準定時上傳的方案也就出來的。
1.註冊廣播,接收系統ACTION_BOOT_COMPLETED事件時,進行set alarm,進行精準定時任務
2.在alarm中啟動service,service中實現網路HTTP請求。
3.首次啟動後,要set alarm。
實現一個簡單的demo,執行效果如下:
點選“啟動定時器”,進行設定arlam,點選“關閉定時器”按鈕,取消定時任務。
程式碼如下:
MainActivity.java
執行後,針對question 1和question 3進行測試。從列印的log分析問題。執行步奏:在控制檯,使用adb kill命令殺死程序,觀察先前設定的AlarmManager是否還有效果?log如下:
程序PID“32267”和“1190”屬於同一應用,從log發現,程序被kill後,自動重啟程序,並且先前設定的alarm同樣有效。此log資訊,說明系統級的alarm,在程序kill後,會自動重啟,並且重啟後的定時任務,依舊有效。
在裝置reboot後,重新set alarm,alarm會執行。
以上內容,僅僅從表像來分析AlarmManager的執行機制,如若要深入分析其原理,還需要研讀AlarmManager的原始碼才行。然而,fucking the code,是一件很痛苦的事情。
在測試程式碼時,發現某些裝置上,AlarmManager執行效果沒有達到預期效果,10秒一次的定時任務,被搞成了5min+一次。例如雷布斯的小米3裝置,原始碼設定的10秒一次,米3上,卻是5分鐘一次定時任務。後來查詢資料,發現時由於Android碎片化的原因,各個廠家進行隨意定製,考慮節能省電,一旦系統休眠,不會頻繁喚醒系統造成的原因。相關連結如下:
http://www.miui.com/thread-1241523-1-1.html
上述功能點,不是很難,然而在真正開發過程中,做好這麼一件小事,還是很費神的,也遭受過一些莫名其妙的坑。
在如今的專案中,此項功能點使用得比較頻繁,一般app都會偶push功能,push的精準定時任務,實現的方式,也大同小異。
原始碼連結如下:
http://download.csdn.net/detail/coder80/8115751
github地址:
https://github.com/hero-peng/AlarmTimerTask
點選“啟動定時器”,進行設定arlam,點選“關閉定時器”按鈕,取消定時任務。
程式碼如下:
MainActivity.java
類ServiceUtil.java@Override public void onClick(View v) { // TODO Auto-generated method stub int id = v.getId(); switch(id){ case R.id.button1: ServiceUtil.invokeTimerPOIService(mContext); break; case R.id.button2: ServiceUtil.cancleAlarmManager(mContext); break; }
public static void invokeTimerPOIService(Context context){
Log.i("ServiceUtil-AlarmManager", "invokeTimerPOIService wac called.." );
PendingIntent alarmSender = null;
Intent startIntent = new Intent(context, UploadPOIService.class);
startIntent.setAction(Constants.POI_SERVICE_ACTION);
try {
alarmSender = PendingIntent.getService(context, 0, startIntent, PendingIntent.FLAG_UPDATE_CURRENT);
} catch (Exception e) {
Log.i("ServiceUtil-AlarmManager", "failed to start " + e.toString());
}
AlarmManager am = (AlarmManager) context.getSystemService(Activity.ALARM_SERVICE);
am.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), Constants.ELAPSED_TIME, alarmSender);
}
public static void cancleAlarmManager(Context context){
Log.i("ServiceUtil-AlarmManager", "cancleAlarmManager to start ");
Intent intent = new Intent(context,UploadPOIService.class);
intent.setAction(Constants.POI_SERVICE_ACTION);
PendingIntent pendingIntent=PendingIntent.getService(context, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarm=(AlarmManager)context.getSystemService(Activity.ALARM_SERVICE);
alarm.cancel(pendingIntent);
}
在類UploadPOIService,是一個service,使用Thread.sleep模擬網路HTTP請求。 @Override
public void run() {
// TODO Auto-generated method stub
try {
Log.i(TAG, "UploadPOIService beign to upload POI to server ");
Thread.sleep(5*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
stopSelf();
}
類BootBroadcastReceiver,註冊一個靜態Broadcast,接收系統級的ACTION_BOOT_COMPLETED事件,用於開機set alarm。 public void onReceive(Context context, Intent intent) {
mContext = context;
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
LogUtil.i("BootBroadcastReceiver", "BroadcastReceiver onReceive here,action = " + intent.getAction());
Handler handler = new Handler(Looper.getMainLooper());
//after reboot the device,about 2 minutes later,upload the POI info to server
handler.postDelayed(new Runnable() {
@Override
public void run() {
if(!ServiceUtil.isServiceRunning(mContext,Constants.POI_SERVICE)){
ServiceUtil.invokeTimerPOIService(mContext);
}
}
}, Constants.BROADCAST_ELAPSED_TIME_DELAY);
}
}
執行後,針對question 1和question 3進行測試。從列印的log分析問題。執行步奏:在控制檯,使用adb kill命令殺死程序,觀察先前設定的AlarmManager是否還有效果?log如下:
程序PID“32267”和“1190”屬於同一應用,從log發現,程序被kill後,自動重啟程序,並且先前設定的alarm同樣有效。此log資訊,說明系統級的alarm,在程序kill後,會自動重啟,並且重啟後的定時任務,依舊有效。
在裝置reboot後,重新set alarm,alarm會執行。
以上內容,僅僅從表像來分析AlarmManager的執行機制,如若要深入分析其原理,還需要研讀AlarmManager的原始碼才行。然而,fucking the code,是一件很痛苦的事情。
在測試程式碼時,發現某些裝置上,AlarmManager執行效果沒有達到預期效果,10秒一次的定時任務,被搞成了5min+一次。例如雷布斯的小米3裝置,原始碼設定的10秒一次,米3上,卻是5分鐘一次定時任務。後來查詢資料,發現時由於Android碎片化的原因,各個廠家進行隨意定製,考慮節能省電,一旦系統休眠,不會頻繁喚醒系統造成的原因。相關連結如下:
http://www.miui.com/thread-1241523-1-1.html
上述功能點,不是很難,然而在真正開發過程中,做好這麼一件小事,還是很費神的,也遭受過一些莫名其妙的坑。
在如今的專案中,此項功能點使用得比較頻繁,一般app都會偶push功能,push的精準定時任務,實現的方式,也大同小異。
原始碼連結如下:
http://download.csdn.net/detail/coder80/8115751
github地址:
https://github.com/hero-peng/AlarmTimerTask