【Android JetPack系列】WorkManager

1、前言

做用WorkManager 負責用來管理後臺任務。java

區別: 它和一個異步任務以及 Service 有什麼區別呢?看完你就知道了android

相關類網絡

  • Worker:任務的執行者,是一個抽象類,須要繼承它實現要執行的任務。異步

  • WorkRequest:指定讓哪一個 Woker 執行任務,指定執行的環境,執行的順序等。要使用它的子類 OneTimeWorkRequestPeriodicWorkRequestide

  • WorkManager:管理任務請求和任務隊列,發起的 WorkRequest 會進入它的任務隊列。模塊化

  • WorkStatus:包含有任務的狀態和任務的信息,以 LiveData 的形式提供給觀察者。gradle

2、集成

build.gradle 中配置:ui

implementation "android.arch.work:work-runtime:$work_version"
implementation "android.arch.work:work-firebase:$work_version"

gradle.properties中配置:this

work_version=1.0.0-alpha10

3、基本使用

一、使用步驟

第一步:定義 worker線程

咱們定義 MainWorker 繼承 Worker,發現須要重寫 doWork 方法,而且須要返回任務的狀態 WorkerResult

class MainWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    override fun doWork(): Result {
      // todo  要執行的任務
       return Result.SUCCESS
    }
}

暫時什麼都不作,直接返回任務執行完成 Result.SUCCESS

第二步:定義 WorkRequest

MainActivity 中定義 WorkRequest

val request =OneTimeWorkRequest.Builder(MainWork::class.java)
                 .build()

OneTimeWorkRequest 意味着這個任務只需執行一遍。

第三步:加入任務隊列

要讓任務執行,須要將 WorkRequest 加入任務隊列:

WorkManager.getInstance().enqueue(request)

如今加入任務隊列後,任務會立刻獲得執行。 但須要注意的是,這句代碼的做用是將任務加入任務隊列,而不是執行任務。由於任務可能須要等到知足環境條件的狀況纔會執行

二、數據交互

後臺任務少不了數據的交互,咱們看一下數據是如何傳入傳出的。

2.一、input

  • 構造 Data對象:Data 的使用和 Bundle 差很少。
val dateFormat = SimpleDateFormat("hh:mm:ss", Locale.getDefault())
      
// Data 對象       
 val data = Data.Builder() 
             .putString("time",dateFormat.format(Date())) 
             .build()
  • 使用 WorkRequestsetInputData 方法傳遞 Data
val request = OneTimeWorkRequest.Builder(MainWork::class.java)
                  .setInputData(data) // 傳遞data
                  .build()
  • 最後,在 Worker 中,從 inputData 能夠取到數據:
class MainWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    override fun doWork(): Result {
        // 獲取傳遞的數據,inputdata 實際上是 getInputData()
        val time = inputData.getString("time")
        return Result.SUCCESS
    }
}

2.二、output

當任務處理完了,須要將處理結果返回。

  • 構造Data並賦值給 outputData
class MainWork(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
    override fun doWork(): Result {
        val time = inputData.getString("time")

        // 傳出
        outputData = Data.Builder()
            .putString("name","Hello Worker!")
            .build()



       return Result.SUCCESS
    }
}
  • 取出 Worker 傳遞出來的數據

每個 WorkRequest 都會有一個 id,經過 id 能夠獲取到對應任務的 WorkStatus,而且是以 LiveData 形式提供的:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val dateFormat = SimpleDateFormat("hh:mm:ss", Locale.getDefault())
        val data = Data.Builder().putString("time", dateFormat.format(Date())).build()


        val request =
            OneTimeWorkRequest.Builder(MainWork::class.java)
                .setInputData(data)
                .build()

        WorkManager.getInstance().enqueue(request)

        WorkManager.getInstance()
            // 經過 id
            .getStatusByIdLiveData(request.id)
            .observe(this, Observer {
                if (it != null && it.state.isFinished) {

                    // 取出 outputData
                    val name = it.outputData.getString("name")
                    Log.e("eeee==>>",name)
                }
            })

    }
}

三、取消任務

若是須要取消一個在隊列中的任務,也是經過 id 實現的:

WorkManager.getInstance().cancelWorkById(request.id)

4、WorkRequest詳解

一、環境約束

WorkManager 容許咱們指定任務執行的環境,好比網絡已鏈接、電量充足時等,在知足條件的狀況下任務纔會執行。

val constraints = Constraints.Builder()  
 .setRequiredNetworkType(NetworkType.CONNECTED) // 網絡狀態
 .setRequiresBatteryNotLow(true) // 不在電量不足時執行 
 .setRequiresCharging(true) // 在充電時執行 
 .setRequiresStorageNotLow(true) // 不在存儲容量不足時執行
 .setRequiresDeviceIdle(true) // 在待機狀態下執行,須要 API 23 
 .build() 

val request = OneTimeWorkRequest.
                   Builder(MainWorker::class.java) 
                   .setConstraints(constraints)
                   .build()

網絡狀態可選值以下:

狀態 說明
NOT_REQUIRED 沒有要求
CONNECTED 網絡鏈接
UNMETERED 鏈接無限流量的網絡
METERED 鏈接按流量計費的網絡
NOT_ROAMING 鏈接非漫遊網絡

舉例:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val dateFormat = SimpleDateFormat("hh:mm:ss", Locale.getDefault())
        val data = Data.Builder().putString("time", dateFormat.format(Date())).build()

        val constraints =
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()

        val request =
            OneTimeWorkRequest.Builder(MainWork::class.java)
                // 添加約束條件
                .setConstraints(constraints)
                .setInputData(data)
                .build()

        WorkManager.getInstance().enqueue(request)

        WorkManager.getInstance()
            // 經過 id
            .getStatusByIdLiveData(request.id)
            .observe(this, Observer {
                if (it != null && it.state.isFinished) {
                    val name = it.outputData.getString("name")
                    Log.e("eeee==>>", name)
                }
            })

    }
}

觀察,當有網絡的時候,纔會打印 E/eeee==>>: Hello Worker!

二、直接子類

2.一、OneTimeWorkRequest

任務只須要執行一遍

2.二、PeriodicWorkRequest

能夠發起一個屢次執行的定時任務。

如:

val request = PeriodicWorkRequest
    .Builder(MainWorker::class.java, 15, TimeUnit.MINUTES)
    .setConstraints(constraints)
    .setInputData(data)
    .build()

這樣,發起的任務就會每隔 15 分鐘執行一次。

除了須要傳入間隔時間,使用起來跟 OneTimeWorkRequest 是沒有區別的。

你可能會想更頻繁的去執行一個任務,好比幾秒鐘執行一遍,但很遺憾,最小時間間隔就是 15 分鐘,看一下源碼就知道了。

還有須要注意的是,定時任務並非說通過指定時間後它就立刻執行,而是通過這一段時間後,等到知足約束條件等狀況時,它才執行

5、其它特色

一、強大的生命力

先看代碼:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val dateFormat = SimpleDateFormat("hh:mm:ss", Locale.getDefault())
        val data = Data.Builder().putString("time", dateFormat.format(Date())).build()

        val constraints =
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()

        val request =
            OneTimeWorkRequest.Builder(MainWork::class.java)
                // 添加約束條件
                .setConstraints(constraints)
                .setInputData(data)
                .build()

        WorkManager.getInstance().enqueue(request)

        WorkManager.getInstance()
            // 經過 id
            .getStatusByIdLiveData(request.id)
            .observe(this, Observer {
                if (it != null && it.state.isFinished) {
                    val name = it.outputData.getString("name")
                    Log.e("eeee==>>", name)
                }
            })

    }
}

操做步驟: 斷網 -> 將進程殺掉 ->聯網 ->再次運行

不出意外的話,這時候你會看到有兩個時間的打印,並且兩個時間還不同,這是爲何呢?

  • 第一個時間是第一次運行後,加入了任務隊列,但尚未執行的任務。
  • 第二個則是本次執行的任務打印的。

這說明了,就算進程被殺掉,任務仍是存在,甚至若是重啓手機,任務依然會在知足條件的狀況下獲得執行。

這是 WorkManager 的另外一個特色:

一旦發起一個任務,任務是能夠保證必定會被執行的,就算退出應用,甚至重啓手機都阻止不了他。但可能因爲添加了環境約束等緣由,它執行的時間是不肯定的。

當應用正在運行時,它會在當前的進程中啓用一個子線程執行。應用沒有運行的狀況下啓用,它則會本身選擇一種合適的方式在後臺運行。具體是什麼方式和 Android 的版本和依賴環境有關:

二、任務鏈

WorkManager 容許咱們按照必定的順序執行任務,好比我想 ABC 三個任務按前後順序執行:

能夠這樣寫,把它們組成一條任務鏈:

WorkManager.getInstance()
      .beginWith(workA)
      .then(workB)
      .then(workC)
      .enqueue()

這樣的話,上一個任務的 outputData 會成爲下一個任務的 inputData

再更復雜一點,我想 AB 同時執行,它們都執行完以後,再執行 C

也是能夠實現的:

WorkManager.getInstance()
    .beginWith(workA,workB)
    .then(workC)
    .enqueue()

再更更復雜一點,若是我想這樣:

這樣就須要先把 A、B 和 C、D 分別組成一條任務鏈,再進行聯結:

val chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)
val chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD)
val chain3 = WorkContinuation
    .combine(chain1, chain2)
    .then(workE)
chain3.enqueue()

再更更更復雜一點,若是我把定時任務放進去會怎樣? 很差意思,鏈式任務只支持 OneTimeWorkRequest

使用任務鏈,咱們能夠將各類任務進行模塊化。一樣的,任務鏈不保證每一個任務執行的時間,可是保證它們執行的前後順序

任務惟一性

不少狀況下,咱們但願在任務隊列裏,同一個任務只存在一個,避免任務的重複執行,這時候能夠用到 beginUniqueWork 這個方法:

WorkManager.getInstance()
   .beginUniqueWork("unique", ExistingWorkPolicy.REPLACE, request)
   .enqueue()

須要傳入一個任務的標籤,和重複任務的執行方式,可取值以下:

狀態 說明
REPLACE 刪除已有的任務,添加現有的任務
KEEP 什麼都不作,不添加新任務,讓已有的繼續執行
APPEND 加入已有任務的任務鏈最末端。追加,舊任務執行以後再執行新的任務。

但這種方式也是隻支持 OneTimeWorkRequest。若是是 PeriodicWorkRequest,我想到的辦法是每次執行以前,根據標籤去取消已有的任務。

相關文章
相關標籤/搜索