5月8號, I/O大會上又推出了兩個新的Architeture Component庫: Navigation與WorkManager. 這裏就先介紹一下WorkManager.java
其實就是"管理一些要在後臺工做的任務, -- 即便你的應用沒啓動也能保證任務能被執行".android
: 其實這個想法很對. WorkManager在底層也是看你是什麼版原本選究竟是JobScheduler, AlamarManager來作. JobScheduler是Android 5.x纔有的. 而AlarmManager一直存在. 因此WorkManager在底層, 會根據你的設備狀況, 選用JobScheduler, Firebase的JobDispatcher, 或是AlarmManagerbash
: 這一點就要特別說明一下了. 這三個和WorkManager並非替代的關係. 這三個工具, 能幫助你在應用中開後臺線程幹活, 可是應用一被殺或被關閉, 這些工具就幹不了活了. 而WorkManager不是, 它在應用被殺, 甚至設備重啓後仍能保證你安排給他的任務能獲得執行.app
其實Google本身也說了:"WorkManager並非爲了那種在應用內的後臺線程而設計出來的. 這種需求你應該使用ThreadPool"ide
仍是show me the code吧.工具
app/build.gradle中加入gradle
[kotlin]ui
implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha01"
this
[java]spa
implementation "android.arch.work:work-runtime:1.0.0-alpha01"
以我在2012年作過的一個項目爲例, 當時我在作一個電商項目. 咱們有一個需求是要定時推給用戶一些咱們推薦的單品, 可是當時集團尚未push service組件呢, 因此咱們當時爲了及時上線, 選用的策略是"pull策略". 客戶端定時去後臺拉取, 看有沒有新的推薦.
這時咱們要分兩步走. 第一步是肯定要幹什麼活(去後臺pull推薦信息); 第二步是讓這個活入隊列.
代碼上咱們也分兩步
這裏要新建個Worker的子類, 重寫它的doWork()方法.
class PullWorker : Worker() {
override fun doWork(): WorkerResult {
// 模擬設置頁面中的"是否接受推送"是否被勾選
val isOkay = this.inputData.getBoolean("key_accept_bg_work", false)
if(isOkay) {
Thread.sleep(5000) //模擬長時間工做
val pulledResult = startPull()
val output = Data.Builder().putString("key_pulled_result", pulledResult).build()
outputData = output
return WorkerResult.SUCCESS
} else {
return WorkerResult.FAILURE
}
}
fun startPull() : String{
return "szw [worker] pull messages from backend"
}
}
複製代碼
class PullEngine {
fun schedulePull(){
//java就請用PeriodicWorkRequest.Builder類
val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS)
.setInputData(
Data.Builder()
.putBoolean("key_accept_bg_work", true)
.build()
)
.build()
WorkManager.getInstance().enqueue(pullRequest)
}
}
複製代碼
1.幹活的是Worker類. 咱們通常是新建個Worker的子類, 並重寫doWork()方法. 可是, doWork()方法是沒有參數的. 咱們有時有參數的需求,怎麼辦? 這時就要用上Worker.getInputData()方法了.
2.同理, doWork()方法是返回void的. 你要是有結果想傳出去, 就能夠用Worker.setOutputData()
3.上面的兩個方法所獲得/設置的數據類型都是Data. 這個Data很相似咱們Android中的Bundle, 也有putInt(key, value), getString(key, defaultValue)這樣的方法.
通常Data的生成, 是用Data.Builder類. 如:
val output = Data.Builder().putInt(key, 23).build()
複製代碼
4.上面講了WorkRequest其實就是入列的一個實體, 它包裝了Worker在內. 但咱們通常不直接使用WorkReqeust類, 可能是用它的子類: OneTimeWorkRequest, 或是PeriodWorkReqeust.
由於咱們的pull需求是天天都要去拉一次, 因此這裏咱們沒有用OneTimeWorkRequest, 而是構建了一個24小時就重複幹活的PeriodicWorkReqeust.
WorkManager提供了一個接口讓咱們拿到結果, 這個東東就是WorkStatus. 你能夠由id獲得你想要的那個任務的WorkStatus. 這個WorkStatus其實就是知道這任務沒有完成, 有什麼返回值.
由於先後臺要解耦合的緣由, 因此這個工做實際上是由LiveData來完成的. 既然有LiveData, 那咱們確定要有一個LifecycleOwner了(通常是咱們的AppcompatActivity).
來看個例子. 以上面的pull例子爲例, 若咱們拉到告終果, 就顯示一個notification (這裏爲簡便, 是收到結果後就打印一下日誌).
[PullEngine.kt]
class PullEngine {
fun schedulePull(){
val pullRequest = PeriodicWorkRequestBuilder<PullWorker>(24, TimeUnit.HOURS).build()
WorkManager.getInstance().enqueue(pullRequest)
// 下面兩行是新加的, 用來存任務的ID
val pullRequestID = pullRequest.id
MockedSp.pullId = pullRequestID.toString() // 模擬存在SharedPreference中
}
}
複製代碼
[PullActivity.kt]
class PullActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// UUID實現了Serializable接口. 也能由toString(), fromString()與String互轉
val uuid = UUID.fromString(MockedSp.pullId)
WorkManager.getInstance().getStatusById(uuid)
.observe(this, Observer<WorkStatus> { status ->
if (status != null){
val pulledResult = status.outputData.getString("key_pulled_result", "")
println("szw Activity getResultFromBackend : $pulledResult")
}
})
}
}
複製代碼
注意, observe()方法是用來監聽嘛. 它的參數分別是: observer(LifecycleOwner, Observer<WorkStatus>)
入參: WorkRequest.Builder.setInputData()
Worker類: 能夠getIntpuData(), 以及setOutputData()
返回值: 由LiveData監聽, 能夠獲得WorkStatus. 而WorkStatus就有getOutputDat()方法
只是注意,這裏說的inputData, outputDat, 都不是普通的int, string. 而是Data類.
: 好問題. 但嚴格來講, 這個其實不是WorkManager的問題, 而是LiveData的問題. LiveData本身自己就是和Activity的生命週期綁定的. 你不用說應用被殺了, 就是你退出了這個註冊的Activity, 你都收不到LiveData的通知. 因此說你的應用被殺, 任務又執行完了時, 是沒有UI通知的, 更不會強行啓動你的啓動. (這有點流氓~)
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue()
複製代碼
這樣就會按workA, workB, workC的順序來執行. workA執行完了, 纔會接着執行workB.
WorkManager甚至還能執行:
A --> B
--> E
C --> D
複製代碼
這樣的形式, 即A執行完了才執行了B, C執行完才執行D. B,D都執行完了才執行E.
WorkManager能夠用beginUniqueWork()
來執行惟一工做隊列("unique work sequence"). 如有任務有重複時, 怎麼辦?
這個主要是一個ExistingWorkPolicy類. 這個類也是WorkManager包中的類. 它實際上是一個Enum. 其值有:
整體來講, WorkManager並非要取代線程池/AsyncTask/RxJava. 反而是有點AlarmManager來作定時任務的意思. 即保證你給它的任務能完成, 即便你的應用都沒有被打開, 或是設備重啓後也能讓你的任務被執行.
WorkManager在設計上設計得比較好. 沒有把worker, 任務混爲一談, 而是把它們解耦成Worker, WorkRequest. 這樣分層就清晰多了, 也好擴展. (如之後再有一個什麼WorkRequest的子類出來)
最後, WorkManager的入參出參設計得不錯. WorkReqeust負責放入參數, Worker處理並放置返回值, 最後WorkStaus中取出返回值, 並由LiveData來通知監聽者.
至於鏈式執行, 惟一工做隊列這些特性在你有相似的需求時, 也能幫助到你.