Android開發之定時任務(AlarmManager、WorkManager)

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個步驟

  1. 定義工作,創建Worker實現類
  2. 創建工作請求,WorkRequest
  3. 將 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,使用更加簡單,適用於更加複雜的場景,兼容性更好
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章