Android 程序的定時任務主要有AlarmManager、WorkManager兩種。
一、AlarmManager
AlarmManager,又稱鬧鐘,可以設置一次性任務,週期重複任務,定時重複任務。
AlarmManager 通過 PendingIntent 傳遞要執行的任務程序,可以是廣播、跳轉頁面、後臺服務、前臺服務等。
1、PendingIntent介紹
本節參考文章:https://zhuanlan.zhihu.com/p/544564416
PendingIntent 是一種延遲的 Intent,表示一種延遲執行的意圖操作。
PendingIntent 一種是支持授權其他應用以當前應用的身份執行包裝的 Intent 操作的系統特性。
從結構上來說,PendingIntent 是 Intent 的包裝類
使用代碼示例:
Intent intent = new Intent(this, MyIntentService.class);
PendingIntent serviceIntent = PendingIntent.getService(this, requestCode, intent,PendingIntent.FLAG_UPDATE_CURRENT| PendingIntentPendingIntent.FLAG_IMMUTABLE);
PendingIntent通過如下方法獲取各種場景實例:
- PendingIntent.getBroadcast(),廣播,類似 Context.sendBroadcast(),Intent對應的class必須是BroadcastReceiver子類
- PendingIntent.getActivity(),跳轉活動頁面,類似 Context.startActivity(Intent),Intent對應的class必須是一個Activity
- PendingIntent.getService(),後臺服務,類似 Context.startService(),Intent對應的class必須是Service子類
- PendingIntent.getForegroundService(),前臺服務,類似 Context.startForegroundService()
簡單說明下創建 PendingIntent 的 4 個參數:
1、context: 當前應用的上下文,PendingIntent 將從中抽取授權信息;
2、requestCode: PendingIntent 的請求碼,與 Intent 的請求碼類似;
3、intent: 最終的意圖操作;
4、flag: 控制標記位。
創建 PendingIntent 時有一個容易犯錯的地方需要注意:重複調用 PendingIntent.getActivity() 等創建方法不一定會返回新的對象,系統會基於兩個要素判斷是否需要返回相同的對象
- 要素 1 - requestCode: 不同的 requestCode 會被認爲不同的 PendingIntent 意圖;
- 要素 2 - Intent: 不同的 Intent 會被認爲不同的 PendingIntent 意圖,但並不是 Intent 中所有的參數都會參與計算,而是僅包含 Intent.filterEquals() 方法考慮的參數,即:action、data、type、identity、class 和 categories,但不包括 extras。
PendingIntent 標記位
- FLAG_IMMUTABLE: 不可變標記位,將約束外部應用消費 PendingIntent 修改其中的 Intent;
- FLAG_MUTABLE: 可變標記位,不約束外部應用消費 PendingIntent 修改其中的 Intent;
- FLAG_UPDATE_CURRENT: 更新標記位 1,如果系統中已經存在相同的 PendingIntent,那麼將保留原有 PendingIntent 對象,而更新其中的 Intent。即使不可變 PendingIntent,依然可以在當前應用更新;
- FLAG_CANCEL_CURRENT: 更新標記位 2,如果系統中已經存在相同的 PendingIntent,那麼將先取消原有的 PendingIntent,並重新創建新的 PendingIntent。
- FLAG_NO_CREATE: 更新標記位 3,如果系統中已經存在相同的 PendingIntent,那麼不會重新創建,而是直接返回 null;
- FLAG_ONE_SHOT: 一次有效標記位,PendingIntent 被消費後不支持重複消費,即只能使用一次。
2、鬧鐘任務設置
1、鬧鐘執行方法設置
1)一次性執行
alarmMgr.set(@AlarmType int type, long triggerAtMillis, PendingIntent operation)
2)重複執行
setRepeating()和setInexactRepeating()都可以設置重複任務,官方推薦使用setInexactRepeating()
alarmMgr.setInexactRepeating(@AlarmType int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)
type,鬧鐘類型
triggerAtMillis,首次觸發時間,毫秒數
intervalMillis,每次執行時間間隔,毫秒數
2、鬧鐘類型說明:
- ELAPSED_REALTIME - 基於自設備啓動以來所經過的時間觸發待定 intent,但不會喚醒設備。經過的時間包括設備處於休眠狀態期間的任何時間。
- ELAPSED_REALTIME_WAKEUP - 喚醒設備,並在自設備啓動以來特定時間過去之後觸發待定 Intent。
- RTC - 在指定的時間觸發待定 Intent,但不會喚醒設備。
- RTC_WAKEUP - 喚醒設備以在指定的時間觸發待定 Intent。
3、AlarmManager代碼示例
1、一次性任務,1分鐘後執行
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT| PendingIntent.FLAG_IMMUTABLE);
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 60 * 1000, alarmIntent);
2、重複任務,30分鐘後執行,每間隔30分鐘執行1次,注意:最短間隔時間是1分鐘
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, MyIntentService.class);
alarmIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT| PendingIntent.FLAG_IMMUTABLE);
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);
3、在下午 2:00 左右喚醒設備並觸發鬧鐘,並在每天的同一時間重複一次
// 設置鬧鐘在下午2:00執行
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 14);
// 設置每隔一天執行一次
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
AlarmManager.INTERVAL_DAY, alarmIntent);
4、在上午 8:30 準時喚醒設備並觸發鬧鐘,此後每 20 分鐘觸發一次
private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
// 設置鬧鐘在上午8:30 執行
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);
// 設置鬧鐘每隔20分鐘觸發一次
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
1000 * 60 * 20, alarmIntent);
4、取消Alarm任務
使用PendingIntent.FLAG_NO_CREATE
獲取已存在的PendingIntent
,然後執行cancel()
方法
Intent intent = new Intent(this, MyIntentService.class);
AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_NO_CREATE|PendingIntent.FLAG_IMMUTABLE);
if (pendingIntent != null && alarmManager != null) {
Log.i("cancelAlarm", "cancelAlarm: " + pendingIntent);
pendingIntent.cancel();
alarmManager.cancel(pendingIntent);
} else {
Log.i("cancelAlarm", "not found Alarm !");
}
5、在設備重啓時啓動鬧鐘
1)添加設備權限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
2)實現 BroadcastReceiver 以接收廣播,判斷設備啓動事件
public class SampleBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
// 判斷設備啓動事件
}
}
}
3)配置 Intent 過濾器過濾器,添加android.intent.action.BOOT_COMPLETED
<receiver android:name=".SampleBootReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
二、使用 WorkManager 調度任務
本節參考官方文檔:https://developer.android.google.cn/topic/libraries/architecture/workmanager/basics
WorkManager 是適合用於持久性工作的推薦解決方案。如果工作始終要通過應用重啓和系統重新啓動來調度,便是持久性的工作。由於大多數後臺處理操作都是通過持久性工作完成的,因此 WorkManager 是適用於後臺處理操作的主要推薦 API。
- 立即,一次性,OneTimeWorkRequest 和 Worker。如需處理加急工作,請對 OneTimeWorkRequest 調用 setExpedited()。
- 長期運行,一次性或定期,任意 WorkRequest 或 Worker。在工作器中調用 setForeground() 來處理通知。
- 可延期,一次性或定期,PeriodicWorkRequest 和 Worker。
WorkManager 適用於需要可靠運行的工作,即使用戶導航離開屏幕、退出應用或重啓設備也不影響工作的執行。例如:
- 向後端服務發送日誌或分析數據。
- 定期將應用數據與服務器同步。
WorkManager 不適用於那些可在應用進程結束時安全終止的進程內後臺工作。它也並非對所有需要立即執行的工作都適用的通用解決方案。
WorkManager 的使用簡單描述3個步驟
- 定義工作,創建Worker實現類
- 創建工作請求,WorkRequest
- 將 WorkRequest 提交給系統
1、引入依賴包
將以下依賴項添加到應用的build.gradle
文件中
dependencies {
def work_version = "2.7.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"
// optional - GCMNetworkManager support
implementation "androidx.work:work-gcm:$work_version"
// optional - Test helpers
androidTestImplementation "androidx.work:work-testing:$work_version"
// optional - Multiprocess support
implementation "androidx.work:work-multiprocess:$work_version"
}
2、定義工作
工作使用 Worker 類定義。doWork() 方法在 WorkManager 提供的後臺線程上異步運行。
如需爲 WorkManager 創建一些要運行的工作,請擴展 Worker 類並替換 doWork() 方法。例如,如需創建上傳圖像的 Worker,您可以執行以下操作:
public class UploadWorker extends Worker {
public UploadWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {
super(context, params);
}
@Override
public Result doWork() {
// 獲取傳入的參數
String name = getInputData().getString("name");
Log.i("Worker", "uploadImages: name: "+ name);
uploadImages();
return Result.success();
}
private void uploadImages() {
Log.i("Worker", "uploadImages: test2");
}
}
從 doWork() 返回的 Result 會通知 WorkManager 服務工作是否成功,以及工作失敗時是否應重試工作。
- Result.success():工作成功完成。
- Result.failure():工作失敗。
- Result.retry():工作失敗,應根據其重試政策在其他時間嘗試。
3、創建 WorkRequest
定義工作後,必須使用 WorkManager 服務進行調度該工作才能運行。對於如何調度工作,WorkManager 提供了很大的靈活性。您可以將其安排爲在某段時間內定期運行,也可以將其安排爲僅運行一次。
不論您選擇以何種方式調度工作,請始終使用 WorkRequest。Worker 定義工作單元,WorkRequest(及其子類)則定義工作運行方式和時間。
- OneTimeWorkRequest,一次性任務
- PeriodicWorkRequest,週期任務
1)OneTimeWorkRequest 示例
WorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(UploadWorker.class)
.build();
2)PeriodicWorkRequest 示例
PeriodicWorkRequest 最短執行週期是15分鐘,如果設置的循環週期小於15分鐘也會被設置爲15分鐘
最好給WorkRequest設置Tag值,以便在啓動時,刪除舊的執行任務,防止重複執行
PeriodicWorkRequest uploadWorkRequest =
new PeriodicWorkRequest.Builder(UploadWorker.class, 15, TimeUnit.MINUTES)
// Constraints
.setInitialDelay(10, TimeUnit.SECONDS)
.addTag("task")
.setInputData(new Data.Builder()
.putString("name","Hello")
.build())
.build();
4、將 WorkRequest 提交給系統
最後,您需要使用 enqueue() 方法將 WorkRequest 提交到 WorkManager。
WorkManager
.getInstance(this)
.enqueue(uploadWorkRequest);
如果要要取消任務可使用方法
// 取消所有任務
WorkManager.getInstance(this).cancelAllWork();
// 取消指定任務
WorkManager.getInstance(this).cancelAllWorkByTag("task");
其他配置
1、執行加急工作
您可以控制當應用達到其執行配額時加急工作會發生什麼情況。如需繼續,您可以傳遞 setExpedited():
- OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST,這會導致作業作爲普通工作請求運行。上述代碼段演示了此操作。
- OutOfQuotaPolicy.DROP_WORK_REQUEST,這會在配額不足時導致請求取消。
OneTimeWorkRequest request = new OneTimeWorkRequestBuilder<T>()
.setInputData(inputData)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build();
2、工作約束
約束可確保將工作延遲到滿足最佳條件時運行
- NetworkType 約束運行工作所需的網絡類型。例如 Wi-Fi (UNMETERED)。
- BatteryNotLow 如果設置爲 true,那麼當設備處於“電量不足模式”時,工作不會運行。
- RequiresCharging 如果設置爲 true,那麼工作只能在設備充電時運行。
- DeviceIdle 如果設置爲 true,則要求用戶的設備必須處於空閒狀態,才能運行工作。在運行批量操作時,此約束會非常有用;若是不用此約束,批量操作可能會降低用戶設備上正在積極運行的其他應用的性能。
- StorageNotLow 如果設置爲 true,那麼當用戶設備上的存儲空間不足時,工作不會運行。
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build();
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setConstraints(constraints)
.build();
3、重試和退避政策
如果您需要讓 WorkManager 重試工作,可以從工作器返回 Result.retry()。然後,系統將根據退避延遲時間和退避政策重新調度工作。
- 退避延遲時間指定了首次嘗試後重試工作前的最短等待時間。此值不能超過 10 秒(或 MIN_BACKOFF_MILLIS)。
- 退避政策定義了在後續重試過程中,退避延遲時間隨時間以怎樣的方式增長。WorkManager 支持 2 個退避政策,即 LINEAR(線性) 和 EXPONENTIAL(指數)。
每個工作請求都有退避政策和退避延遲時間。默認政策是 EXPONENTIAL,延遲時間爲 10 秒,但您可以在工作請求配置中替換此設置。
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build();
4、鏈接工作,執行多個工作
您可以使用 WorkManager 創建工作鏈並將其加入隊列。工作鏈用於指定多個依存任務並定義這些任務的運行順序。
如需創建工作鏈,您可以使用 WorkManager.beginWith(OneTimeWorkRequest) 或 WorkManager.beginWith(List),這會返回 WorkContinuation 實例
WorkManager.getInstance(myContext)
// Candidates to run in parallel
.beginWith(Arrays.asList(plantName1, plantName2, plantName3))
// Dependent work (only runs after all previous work in chain)
.then(cache)
.then(upload)
// Call enqueue to kick things off
.enqueue();
總結
- AlarmManager,會使設備從低電耗模式中喚醒。因此,它在電源和資源管理方面來講並不高效。AlarmManager 僅適合用於精確鬧鐘或通知(例如日曆活動)場景,而不適用於後臺工作。
- WorkManager,使用更加簡單,適用於更加複雜的場景,兼容性更好