1. 程式人生 > >AlarmManager實現精準定時任務

AlarmManager實現精準定時任務

       在專案中,有這麼一個功能點,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
	@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;
		}
  類ServiceUtil.java
 
    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 1question 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