近期Google的Architecture Component 庫 又迎來了兩個新的組件:Navigation 、WorkManager
WorkManager定義
管理一些要在后台工作的任務 ,在沒有啟動app的情況下保證任務能被執行。
ps:這里有沒有想起一些其他概念!!比如 守護進程!!互相調用進程!!!虛擬進程!!!
問題一:有些人提出為什么不用JobScheduler 和AlarmManager?
JobScheduler 是Android 5.x +才有的新方法,而AlarmManager是早就存在的方法。所以WorkManager會根據設備情況來選用JobScheduler 還是Firebase的JobDispatcher(后續學習一些)或是AlarmManager。
問題二: 為什么不用AsyncTask,ThreadPool,RxJava?
這三個線程管理工具和WorkManager 並不是提供關系或是說功能並不一樣。前三者可以幫你在應用中開后台線程干活,但是app一旦被kill掉或是shutdown,這三個線程管理工具也就跟着stop工作了。而WorkManager就是為了彌補這方面的缺陷,在app被kill掉后甚至設備重啟后仍能保證繼續進行需要的任務。
ps:其實Google自己也說了:"WorkManager並不是為了那種在應用內的后台線程而設計出來的. 這種需求你應該使用
ThreadPool"
WorkManager構建
app/build.gradle
kotlin:
implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha01"
Android Java
implementation "android.arch.work:work-runtime:1.0.0-alpha01"
WorkManager 實例
講述一個小的定時推送的小案例
工作步驟
- 后台pull數據到前台 (確定要干什么活)
- 將前台的數據加入活動隊列已顯示 (讓這個活入隊列)
(1) Worker是干活的主體方法,它只管輪到了它時要做的工作,不考慮其他問題
構建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”
}
}
(2)將Worker包裝成一個WorkRequest ,並加入隊列
WorkRequest 一些新屬性:
- ID(一般是一個UUID, 以保證唯一性),
- 何時執行,
- 有沒有限制(如只有在充電並連網時才執行此任務),
- 執行鏈 (當某任務執行完了, 才能輪到我執行)
WorkManager就負責把WorkRequest入列
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.
進階
1. 想拿到結果
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>)
2. 總結入參/返回值
入參: WorkRequest.Builder.setInputData()
Worker類: 可以getIntpuData(), 以及setOutputData()
返回值: 由LiveData監聽, 可以得到WorkStatus. 而WorkStatus就有getOutputDat()方法
只是注意,這里說的inputData, outputDat, 都不是普通的int, string. 而是Data類.
3. 如果任務執行完了, 應用卻沒被啟動怎么辦? 會強行啟動應用來顯示UI變化嗎?
: 好問題. 但嚴格來說, 這個其實不是WorkManager的問題, 而是LiveData的問題. LiveData自己本身就是和Activity的生命周期綁定的. 你不用說應用被殺了, 就是你退出了這個注冊的Activity, 你都收不到LiveData的通知. 所以說你的應用被殺, 任務又執行完了時, 是沒有UI通知的, 更不會強行啟動你的啟動. (這有點流氓~)
4. 任務鏈
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.
5. 插入任務時, 已經有相同的任務時, 怎么辦?
WorkManager可以用beginUniqueWork()來執行唯一工作隊列("unique work sequence"). 若有任務有重復時, 怎么辦?
這個主要是一個ExistingWorkPolicy類. 這個類也是WorkManager包中的類. 它其實是一個Enum. 其值有:
- REPLACE: 用新任務來取代已經存在的任務
- KEEP: 保留已經存在的任務. 忽視新任務
- APPEND: 新任務入列. 新舊任務都存在於隊列中.
總結
總體來說, WorkManager並不是要取代線程池/AsyncTask/RxJava. 反而是有點AlarmManager來做定時任務的意思. 即保證你給它的任務能完成, 即使你的應用都沒有被打開, 或是設備重啟后也能讓你的任務被執行.
WorkManager在設計上設計得比較好. 沒有把worker, 任務混為一談, 而是把它們解耦成Worker, WorkRequest. 這樣分層就清晰多了, 也好擴展. (如以后再有一個什么WorkRequest的子類出來)
最后, WorkManager的入參出參設計得不錯. WorkReqeust負責放入參數, Worker處理並放置返回值, 最后WorkStaus中取出返回值, 並由LiveData來通知監聽者.
至於鏈式執行, 唯一工作隊列這些特性在你有類似的需求時, 也能幫助到你.
系列文章列表:
轉自: