android: 後臺執行的定時任務

Android 中的定時任務通常有兩種實現方式,一種是使用 Java API 裏提供的 Timer 類, 一種是使用 Android 的 Alarm 機制。這兩種方式在多數狀況下都能實現相似的效果,但 Timer 有一個明顯的短板,它並不太適用於那些須要長期在後臺運行的定時任務。咱們都知道,爲 了能讓電池更加耐用,每種手機都會有本身的休眠策略,Android 手機就會在長時間不操做 的狀況下自動讓 CPU 進入到睡眠狀態,這就有可能致使 Timer 中的定時任務沒法正常運行。 而 Alarm 機制則不存在這種狀況,它具備喚醒 CPU 的功能,便可以保證每次須要執行定時 任務的時候 CPU 都能正常工做。須要注意,這裏喚醒 CPU 和喚醒屏幕徹底不是同一個概念, 千萬不要產生混淆。android

那麼首先咱們來看一下 Alarm 機制的用法吧,其實並不複雜,主要就是藉助了 AlarmManager 類來實現的。這個類和 NotificationManager 有點相似,都是經過調用 Context 的 getSystemService()方法來獲取實例的,只是這裏須要傳入的參數是 Context.ALARM_SERVICE。 所以,獲取一個 AlarmManager 的實例就能夠寫成:app

 AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);ide

接下來調用 AlarmManager 的 set()方法就能夠設置一個定時任務了,好比說想要設定一 個任務在 10 秒鐘後執行,就能夠寫成:優化

 long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000; manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent); 上面的兩行代碼你不必定能看得明白,由於 set()方法中須要傳入的三個參數稍微有點複雜,下面咱們就來仔細地分析一下。第一個參數是一個整型參數,用於指定 AlarmManager 的工做類型,有四種值可選,分別是 ELAPSED_REALTIME、ELAPSED_REALTIME_WAKEUP、 RTC 和 RTC_WAKEUP。其中 ELAPSED_REALTIME 表示讓定時任務的觸發時間從系統開 機開始算起,但不會喚醒 CPU。ELAPSED_REALTIME_WAKEUP 一樣表示讓定時任務的觸 發時間從系統開機開始算起,但會喚醒 CPU。RTC 表示讓定時任務的觸發時間從 1970 年 1 月 1 日 0 點開始算起,但不會喚醒 CPU。RTC_WAKEUP 一樣表示讓定時任務的觸發時間從this

1970 年 1 月 1 日 0 點開始算起,但會喚醒 CPU。使用 SystemClock.elapsedRealtime()方法可 以獲取到系統開機至今所經歷時間的毫秒數,使用 System.currentTimeMillis()方法能夠獲取 到 1970 年 1 月 1 日 0 點至今所經歷時間的毫秒數。線程

而後看一下第二個參數,這個參數就好理解多了,就是定時任務觸發的時間,以毫秒爲 單位。若是第一個參數使用的是 ELAPSED_REALTIME 或 ELAPSED_REALTIME_WAKEUP, 則這裏傳入開機至今的時間再加上延遲執行的時間。若是第一個參數使用的是 RTC 或 RTC_WAKEUP,則這裏傳入 1970 年 1 月 1 日 0 點至今的時間再加上延遲執行的時間。第三個參數是一個 PendingIntent,對於它你應該已經不會陌生了吧。這裏咱們通常會調日誌

用 getBroadcast()方法來獲取一個可以執行廣播的 PendingIntent。這樣當定時任務被觸發的時 候,廣播接收器的 onReceive()方法就能夠獲得執行。xml

瞭解了 set()方法的每一個參數以後,你應該能想到,設定一個任務在 10 秒鐘後執行還可 以寫成:對象

 

long triggerAtTime = System.currentTimeMillis() + 10 * 1000; manager.set(AlarmManager.RTC_WAKEUP, triggerAtTime, pendingIntent); 好了,如今你已經掌握 Alarm 機制的基本用法,下面咱們就來建立一個能夠長期在後臺執行定時任務的服務。建立一個 ServiceBestPractice 項目,而後新增一個 LongRunningServiceblog

類,代碼以下所示:

 

public class LongRunningService extends Service {

 

 

@Override

public IBinder onBind(Intent intent) {

return null;

}

 

 

@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,代碼以下所示:

 

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 能夠每隔一小時就會啓動一次,一個長期在後臺定時運行的 服務天然也就完成了。

接下來的任務也很明確了,就是咱們須要在打開程序的時候啓動一次 LongRunningService, 以後 LongRunningService 就能夠一直運行了。修改 MainActivity 中的代碼,以下所示:

 

public class MainActivity extends Activity {

 

 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

Intent intent = new Intent(this, LongRunningService.class);

startService(intent);

}

 

 

}

最後別忘了,咱們所用到的服務和廣播接收器都要在 AndroidManifest.xml 中註冊才行, 代碼以下所示:

 

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.servicebestpractice"

android:versionCode="1" android:versionName="1.0" >

……

<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" >

<activity android:name="com.example.servicebestpractice.MainActivity" android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<service android:name=".LongRunningService" >

</service>

<receiver android:name=".AlarmReceiver" >

</receiver>

</application>

</manifest>

如今就能夠來運行一下程序了。雖然你不會在界面上看到任何有用的信息,但實際上 LongRunningService 已經在後臺悄悄地運行起來了。爲了可以驗證一下運行結果,我將手機 閒置了幾個小時,而後觀察 LogCat 中的打印日誌,如圖 9.15 所示。

 

圖   9.15

能夠看到,LongRunningService 果真如咱們所願地運行着,每隔一小時都會打印一條日誌。這樣,當你真正須要去執行某個定時任務的時候,只須要將打印日誌替換成具體的任務 邏輯就好了。

另外須要注意的是,從 Android 4.4 版本開始,Alarm 任務的觸發時間將會變得不許確, 有可能會延遲一段時間後任務才能獲得執行。這並非個 bug,而是系統在耗電性方面進行 的優化。系統會自動檢測目前有多少 Alarm 任務存在,而後將觸發時間將近的幾個任務放在 一塊兒執行,這就能夠大幅度地減小 CPU 被喚醒的次數,從而有效延長電池的使用時間。

固然,若是你要求 Alarm 任務的執行時間必須準備無誤,Android 仍然提供瞭解決方案。 使用 AlarmManager 的 setExact()方法來替代 set()方法,就能夠保證任務準時執行了。

相關文章
相關標籤/搜索