1. 程式人生 > >Google推薦的後臺任務解決方案

Google推薦的後臺任務解決方案

640?wx_fmt=png


今日科技快訊


10月30日22點整,蘋果以“There's more in the making”為主題的釋出會在紐約布魯克林音樂學院正式舉行。在這次釋出會中,蘋果終於對其2個許久未更新的產品線MacBook Air與Mac mini進行了更新,除此之外,還發布了“世界上最好的AR裝置”——新款iPad Pro。


作者簡介


本篇轉自 Dynamic_2018 的部落格,主要講解了關於 WorkManager的相關知識,一起來看看!希望大家喜歡。

Dynamic_2018 的部落格地址:

https://www.jianshu.com/u/432048b89bd0


前言


先看一下google推薦的後臺任務的解決方案

640?wx_fmt=other

image.png

如圖後臺任務可以從三個維度去考慮:

  • 是否需要立即執行

  • 是否需要執行完畢

  • 是否需要監聽系統的狀態 如網路狀態等

然後給出了對應的具體的推薦方案

執行緒池、廣播、前臺任務這裡暫時不提,主要看一下新出的workmanager,從圖中可以看出workmanager的使用條件任務可以適當延遲的,即task沒有鬧鐘那種強時效性。

workmanager是今年google io提出來的,目前版本還是1.0.0-alpha01,看見alpha就有點慌,後面果然出現bug了,這個後面再細說。

背景

為什麼要使用workmanager,是alrammanager不好用嗎?

其實這兩者的作用並不完全一樣;alarmmanager適用於類似鬧鐘那樣必須準時喚醒的任務,不管是否處於doze(低耗電和應用待機模式);不過在6.0後的doze裡面,alarmmanager需要呼叫setAndAllowWhileIdle()和setExactAndAllowWhileIdle()。

如果必須要準時執行的任務比如股票每日開市提醒只能強行喚醒系統,但是也有的是不是要求時間這麼準確的,比如每隔2天彈一個notification提示開啟app啥的,這種實效性不那麼高的就可以換成jobservice。


正文


簡單介紹一下doze

相關文件地址如下所示:

https://developer.android.google.cn/training/monitoring-device-state/doze-standby

640?wx_fmt=other

image.png

這個是android6.0後出來的,6.0+的裝置上都有這個,目的是為了省電。簡單的說就是手機系統發現一段時間使用者都沒有在用手機了,就會進入省電模式,期間不會處理alarm、網路狀態等,等省電了一段時間後再給一小段時間讓所有應用統一處理alram等task,然後又進入省電狀態。如果中途一直沒有亮屏、充電等表示使用者開始用手機了的操作,doze的省電週期會越來越長,如上圖。

老實說,按照國人的習慣,白天手機想進入doze,基本上上不可能的。沒有誰會好幾個小時不碰手機吧。所以基本上都是晚上進入doze,這也說明了為啥早上亮屏的瞬間手機好多app會在同一時間彈出notification(亮屏就退出了doze,被延遲的alram馬上開始工作)。

那麼使用jobservice的優勢在哪兒,我為什麼不全部用alarmManager?

使用jobservice的優勢就是doze下,不會喚醒系統,耗電量會減小。耗電量減小到一定程度,才能達到googleplay的推薦要求;在國內,兩種型別差不多的app,使用者通過電量消耗統計軟體發現耗電量低的那個app,應該會對它更有好感吧(解除安裝也是先解除安裝競爭對手的app 哈哈)。

從jobservice 到android-job 到workmanager

前面說的時效性不是很強的任務可以用jobservice(JobScheduler),但是這個是android5.0+才出來的,要相容5.0之前的,額外再用alramager;而且國外的還可以使用googleservice(Firebase JobDispatcher)實現。這就需要開發者自己來相容;

後面發現印象筆記的android-job已經實現了類似的功能,地址如下所示:

https://github.com/evernote/android-job

  • androidjob的相關api

任務排程型別

  • 普通任務
    setExecutionWindow(start,end)

  • 特定時間執行的任務
    setExact(time)

  • 週期任務
    setPeriodic(long intervalMs, long flexMs)

任務的執行時間範圍public Builder setExecutionWindow(long startInMs, long endInMs)

  • mStartMs
    Earliest point from which your task is eligible to run.

  • endInMs
    Latest point at which your task must be run. eligible to run.
    這兩個是時間視窗的起始時間,意思是System.currentTimeMillis() + startInMs到System.currentTimeMillis() + endInMs之間執行,endInMs對應後面的deadline。
    在setPeriodic(long)和setExact(long)時不起作用

和週期任務相關的引數Builder setPeriodic(long intervalMs, long flexMs)

  • long mIntervalMs
    這個是週期執行有關的引數,每隔多久執行一次,最低是15分鐘。

  • long mFlexMs
    這個是週期執行有關的引數,離週期末尾多久這個job應該被執行,最低5分鐘。

JobManager.instance().getConfig().setAllowSmallerIntervalsForMarshmallow(true); // Don't use this in production

debug模式下設定這個,可以在api<24的機子上週期減小到60s,倒數減小到30s;正式環境,最低週期15分鐘,倒數5分鐘。

和任務執行失敗,重新排程有關的引數public Builder setBackoffCriteria(long backoffMs, @NonNull BackoffPolicy backoffPolicy)

  • backoffMs
    非週期性的任務,執行失敗後被重新排程需要等待的時間,配合著下面的策略使用。
    預設開始30s,逐漸增加最多5小時

  • backoffPolicy
    列舉,只有線性和指數增長兩種方式。

一些二級條件吧,如果沒有強制要求,會在deadline時忽略這些條件執行job

  • boolean mRequiresCharging
    是否需要插入裝置(是指充電吧)
    預設false,但是如果之前的endInMS到了&&mRequirementsEnforced=false,就會忽略這個條件執行job

  • boolean mRequiresDeviceIdle
    是否需要等到裝置空閒。同上面一樣,當deadline到來&&沒有mRequirementsEnforced=false時,就會忽略這個條件執行Job

  • boolean mRequiresBatteryNotLow
    是否禁止低電量。同上,deadline&&有mRequirementsEnforced=false會忽略。

  • boolean mRequiresStorageNotLow
    是否禁止低儲存空間。這個只有在android o才有效果

  • NetworkType mNetworkType
    需要等待的網路狀態,是netWorkType的列舉,有好幾種,類似wify 、流量等,預設是不需要關心網路狀態。
    同上條件。

  • boolean mRequirementsEnforced
    這個就是前面提到的那個引數,如果設為false,當job到deadline還沒執行的時候,就可以搶救一下。

  • 傳參相關

  • PersistableBundleCompat mExtras
    帶的額外引數,必須是這種型別,類似bundle。可以在onRunJob方法那裡通過傳過去的param取出來。

可以說android-job很良心了,暴露的api簡單易用,功能強大。

  • workmanager的使用

本來已經使用android-job了,但是後面在github文件的後面看到了官方的說明:由於google出了workmanager,Android-job後面可能不再更新。然後又換到workmanager。

使用起來還是很方便的,workmanager.enque(workRequest)就可以了,看起來類似google之前出的volley的風格,不知道是不是同一批程式設計師開發的哈哈。

public class DemoWorker extends Worker {
  public static final String TAG = "work_demo_tag";
  @NonNull @Override public WorkerResult doWork() {
    Data data = getInputData();
    int requestCode = data.getInt(AlarmMgr.ALARM_REQUEST_CODE, -1);
    String strData = data.getString(AlarmMgr.ALARM_NOTIFICATION_DATA, "");
    boolean bNotiClick = data.getBoolean(AlarmMgr.ALARM_NOTIFICATION_CLICK, false);
    Context context = getApplicationContext();
  //your job to do

  //可以再這裡呼叫下一個一次性任務,形成周期迴圈。
 //現在預設執行成功,有需要再新增retry back-off相關邏輯
    return WorkerResult.SUCCESS;
  }
/**
   * @param requestCode requestCode
   * @param strData strData
   * @param bNotiClick bNotiClick
   */

  public static void schedule(final int requestCode, String strData, boolean bNotiClick) {
    WorkManager manager = WorkManager.getInstance();
   //下一次任務開始時,取消上一次相關tag型別的任務(避免網路狀態不太好反覆觸發網路切換廣播的情況)
   //這裡如果queue裡面存在100個以上job會crash,所以需要處理一下
    manager.cancelAllWorkByTag(String.valueOf(requestCode));
    OneTimeWorkRequest oneTimeWorkRequest =
        new OneTimeWorkRequest.Builder(AlarmReceiverWorker.class)
            .addTag(String.valueOf(requestCode))
            .setInputData(new Data.Builder()
                .putInt(AlarmMgr.ALARM_REQUEST_CODE, requestCode)
                .putString(AlarmMgr.ALARM_NOTIFICATION_DATA, strData)
                .putBoolean(AlarmMgr.ALARM_NOTIFICATION_CLICK, bNotiClick)
                .build())
            .build();
    manager.enqueue(oneTimeWorkRequest);
  }
}

需要注意的是佇列裡面任務(還在等待排程 未執行的那種)不能超過100個,不然會crash,這是workmanager程式碼的限制

workRequest子類有oneTime和periodic,對應一次性任務和週期性任務;因為基本上同樣是對jobservice操作,所以方法很類似android-job,列一下常見用法。

多工排程順序

WorkManager.getInstance()
    // First, run all the A tasks (in parallel):
    .beginWith(workA1, workA2, workA3)
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in any order):
    .then(workC1, workC2)
    .enqueue();

設定任務執行的約束條件

// Create a Constraints that defines when the task should run
Constraints myConstraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
    // Many other constraints are available, see the
    // Constraints.Builder reference
     .build();

// ...then create a OneTimeWorkRequest that uses those constraints
OneTimeWorkRequest compressionWork =
                new OneTimeWorkRequest.Builder(CompressWorker.class)
     .setConstraints(myConstraints)
     .build();

取消任務除了前面提到的cancleByTag,也可以用uniquework

OneTimeWorkRequest oneTimeWorkRequest =
          new OneTimeWorkRequest.Builder(AlarmReceiverWorker.class).build();
      WorkManager.getInstance()
          .beginUniqueWork("downloadQueue", ExistingWorkPolicy.REPLACE, oneTimeWorkRequest);

說說坑吧

workermanager的週期任務,有時不能取消掉之前放到佇列的任務。通過cancleByTag也不行,這個在stackoverflow和github上面的issue也看到有人提過,而且確實回覆是個bug,應該是alpha版還是有些問題的。

既然週期任務有坑,我們也可以採用一次性任務開啟下一個一次性任務,像連結串列那樣。而且可以避開週期任務裡面原始碼對週期設定最小15分鐘的限制,不過一般也沒那麼流氓要隔幾分鐘就喚醒吧。

但是使用一次性任務迴圈觸發,發現在小米上測試,開啟app退到後臺時,alarmmanager android-job workmanager正常,殺掉app,workmanager就不行了;使小米達到doze的條件,再亮屏,anroid-job也不行了。在小米上暫時只有alramanager適用(如果有哪位朋友發現小米上用workmanager有方法可以避開這個坑,請分享一下)。

還有就是前面提到的,要避開queue裡面出現100個待排程的job的case。


結語


最後的做法定時任務使用alrammanager,等收到alram廣播後交給worker處理;網路狀態監聽任務直接再接收到廣播後交給worker處理。保證定時,但是收到廣播放到佇列裡面不像之前那樣接收到廣播就序列馬上執行了,等系統決定統一處理。


歡迎長按下圖 -> 識別圖中二維碼

或者 掃一掃 關注我的公眾號

640.png?

640?wx_fmt=jpeg