1. 程式人生 > >android Service重啟問題,結合AlarmManager實現定時任務

android Service重啟問題,結合AlarmManager實現定時任務

         當啟動service進行後臺任務的時候,我們一般的 做法是啟動一個執行緒,然後通過sleep方法來控制進行定時的任務,如輪詢操作,訊息推送。這種service的資源是很容易被回收的,雖然service的優先順序很高,但是還沒有前臺的activity的優先極高,所以一旦資源被回收,service會停止執行。         service被回收是我們不能控制的,但是我們可以控制service的重啟活動。在service的onStartCommand 方法中可以返回一個引數來控制重啟活動         1、Service.START_STICKY,就會在資源被回收(用手機加速程式加速)或者程式異常(異常結束程式,測試的時候手動丟擲一個異常)的時候重新呼叫oncreate、onStartCommand來啟動服務。但是不能連續重啟兩次。像音樂播放器,這種需要持續執行的,斷開後可以馬上重啟,但是傳送過來的intent為null。             這種模式通常用於處理自身狀態的service,以及需要通過startService和stopService顯示的啟動和終止的Service,如音樂播放器的service         2、START_NOT_STICKY就不會重新啟動服務。比如說更新操作,他會考慮到資源競爭,比較謹慎,不會自動啟動service。此處onstartcommand方法中的intent是本次傳送過來的intent。當被異常終止後,只有重新startService 呼叫,這個servie才會啟動。             這種模式比較謹慎,當停止後,它會在下一個排程中嘗試重新啟動,不會再資源競爭的時候重啟,對於處理特殊請求,尤其諸如更新操作或者網路輪詢這樣的定期處理。         3、START_REDELIVER_INTENT不會馬上重啟service。需要在下次呼叫啟動這個service的時候會獲得上次傳入的intent,然後執行兩次onStartCommand方法。只執行一次oncreat方法。也就是說可以把上次沒有完成的工作這一次也完成(傳送過來的intent是保留的上一次的intent和本次傳送的intent)。一般用於不是無限迴圈的任務。         這種模式是要確保service中的命令得以完成。--例如在時效性比較重要的時候 注意,在處理完成後,每種模式都要求使用stopService和stopSelf顯式的停止service。 從以上的引數中可以看出,service的重啟操作不能只依靠引數返回值來實現,能夠重啟的只有Service.START_STICKY,但是這種方法只能重啟一次,再次斷開的時候就不能實現重連操作了。所以必須使用AlarmManager的定時任務來實現網路的輪詢等定時任務。可以看到使用START_NOT_STICKY的返回值是最適合網路輪詢操作的。 AlarmManager的工作原理。alarmmanager會定時的發出一條廣播,然後在自己的專案裡面註冊這個廣播,重寫onReceive方法,在這個方法裡面啟動一個service,然後在service裡面進行網路的訪問操作,當獲取到新訊息的時候進行推送,同時再設定一個alarmmanager進行下一次的輪詢,當本次輪詢結束的時候可以stopself結束改service。這樣即使這一次的輪詢失敗了,也不會影響到下一次的輪詢。這樣就能保證推送任務不會中斷。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d("LongRunningService", "executed at " + new Date().
toString());
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000; // 這是一小時的毫秒數
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}
}

我們在 onStartCommand()方法裡開啟了一個子執行緒, 然後在子執行緒裡就可以執行具體的邏輯操作了。這裡簡單起見,只是列印了一下當前的時間。 建立執行緒之後的程式碼就是我們剛剛講解的 Alarm 機制的用法了,先是獲取到了AlarmManager 的例項,然後定義任務的觸發時間為一小時後,再使用 PendingIntent 指定處理定時任務的廣播接收器為 AlarmReceiver,最後呼叫 set()方法完成設定。 顯然,AlarmReceiver目前還不存在呢,所以下一步就是要新建一個 AlarmReceiver類, 並讓它繼承自 BroadcastReceiver,程式碼如下所示: 我們在 onStartCommand()方法裡開啟了一個子執行緒, 然後在子執行緒裡就可以執行具體的邏輯操作了。這裡簡單起見,只是列印了一下當前的時間。
建立執行緒之後的程式碼就是我們剛剛講解的 Alarm 機制的用法了,先是獲取到了AlarmManager 的例項,然後定義任務的觸發時間為一小時後,再使用 PendingIntent 指定處理定時任務的廣播接收器為 AlarmReceiver,最後呼叫 set()方法完成設定。
顯然,AlarmReceiver目前還不存在呢,所以下一步就是要新建一個 AlarmReceiver類,
並讓它繼承自 BroadcastReceiver,程式碼如下所示:
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}

onReceive()方法裡的程式碼非常簡單,就是構建出了一個 Intent 物件,然後去啟動LongRunningService 這個服務。那麼這裡為什麼要這樣寫呢?其實在不知不覺中,這就已經將一個長期在後臺定時執行的服務完成了。因為一旦啟動 LongRunningService,就會在onStartCommand()方法裡設定一個定時任務,這樣一小時後 AlarmReceiver 的 onReceive()方法就將得到執行,然後我們在這裡再次啟動 LongRunningService,這樣就形成了一個永久的迴圈,保證 LongRunningService 可以每隔一小時就會啟動一次,一個長期在後臺定時執行的服務自然也就完成了。         另外需要注意的是,從 Android 4.4 版本開始,Alarm 任務的觸發時間將會變得不準確,有可能會延遲一段時間後任務才能得到執行。這並不是個 bug,而是系統在耗電性方面進行的優化。系統會自動檢測目前有多少 Alarm任務存在,然後將觸發時間將近的幾個任務放在一起執行,這就可以大幅度地減少 CPU被喚醒的次數,從而有效延長電池的使用時間。當然,如果你要求 Alarm任務的執行時間必須準備無誤,Android仍然提供瞭解決方案。使用 AlarmManager的 setExact()方法來替代 set()方法,就可以保證任務準時執行了。