版權聲明:本文為部落客原創文章,轉載請标明出處。 https://blog.csdn.net/chaoyu168/article/details/53740938
Android中的定時任務一般有兩種實作方式,一種是使用 Java API 裡提供的 Timer 類,
一種是使用 Android的 Alarm機制。 這兩種方式在多數情況下都能實作類似的效果, 但 Timer
有一個明顯的短闆,它并不太适用于那些需要長期在背景運作的定時任務。我們都知道,為
了能讓電池更加耐用,每種手機都會有自己的休眠政策,Android手機就會在長時間不操作
的情況下自動讓 CPU 進入到睡眠狀态,這就有可能導緻 Timer中的定時任務無法正常運作。
而 Alarm 機制則不存在這種情況,它具有喚醒 CPU 的功能,即可以保證每次需要執行定時
任務的時候 CPU都能正常工作。 需要注意, 這裡喚醒 CPU和喚醒螢幕完全不是同一個概念,
千萬不要産生混淆。
那麼首先我們來看一下 Alarm 機制的用法吧,其實并不複雜,主要就是借助了
AlarmManager類來實作的。這個類和 NotificationManager有點類似,都是通過調用 Context的
getSystemService()方法來擷取執行個體的, 隻是這裡需要傳入的參數是 Context.ALARM_SERVICE。
是以,擷取一個 AlarmManager的執行個體就可以寫成:
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
接下來調用 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 同樣表示讓定時任務的觸發時間從
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()方法就可以得到執行。
了解了 set()方法的每個參數之後,你應該能想到,設定一個任務在 10 秒鐘後執行還可
以寫成:
long triggerAtTime = System.currentTimeMillis() + 10 * 1000;
manager.set(AlarmManager.RTC_WAKEUP, triggerAtTime, pendingIntent);
好了,現在你已經掌握 Alarm機制的基本用法,下面我們就來建立一個可以長期在背景
執行定時任務的服務。 建立一個 ServiceBestPractice 項目, 然後新增一個 LongRunningService
類,代碼如下所示:
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中的列印日志.
可以看到,LongRunningService 果然如我們所願地運作着,每隔一小時都會列印一條日
志。這樣,當你真正需要去執行某個定時任務的時候,隻需要将列印日志替換成具體的任務
邏輯就行了。
另外需要注意的是,從 Android 4.4 版本開始,Alarm 任務的觸發時間将會變得不準确,
有可能會延遲一段時間後任務才能得到執行。這并不是個 bug,而是系統在耗電性方面進行
的優化。系統會自動檢測目前有多少 Alarm任務存在,然後将觸發時間将近的幾個任務放在
一起執行,這就可以大幅度地減少 CPU被喚醒的次數,進而有效延長電池的使用時間。
當然,如果你要求 Alarm任務的執行時間必須準備無誤,Android仍然提供了解決方案。
使用 AlarmManager的 setExact()方法來替代 set()方法,就可以保證任務準時執行了。