做爲 Android Jetpack 中的新組件,WorkManager 負責用來管理後臺任務,它和一個異步任務以及 Service 有什麼區別呢?看完你就知道了。java
咱們先來看看 WorkManager 相關的幾個類:android
Worker 任務的執行者,是一個抽象類,須要繼承它實現要執行的任務。bash
WorkRequest 指定讓哪一個 Woker 執行任務,指定執行的環境,執行的順序等。 要使用它的子類 OneTimeWorkRequest 或 PeriodicWorkRequest。網絡
WorkManager 管理任務請求和任務隊列,發起的 WorkRequest 會進入它的任務隊列。異步
WorkStatus 包含有任務的狀態和任務的信息,以 LiveData 的形式提供給觀察者。ide
接下來是 WorkManager 的簡單使用。模塊化
WorkManager 的實現包括如下幾個步驟。gradle
在 build.gradle 添加以下依賴:ui
implementation "android.arch.work:work-runtime:$work_version"
implementation "android.arch.work:work-firebase:$work_version"
複製代碼
咱們定義 MainWorker 繼承 Worker,發現須要重寫 doWork 方法,而且須要返回任務的狀態 WorkerResult:this
class MainWorker : Worker() {
override fun doWork(): WorkerResult {
// 要執行的任務
return WorkerResult.SUCCESS
}
}
複製代碼
咱們暫時什麼都不作,直接返回任務執行完成 WorkerResult.SUCCESS。
在 MainActivity 中定義 WorkRequest:
val request = OneTimeWorkRequest.Builder(MainWorker::class.java).build()
複製代碼
OneTimeWorkRequest 意味着這個任務只需執行一遍。
要讓任務執行,須要將 WorkRequest 加入任務隊列:
WorkManager.getInstance().enqueue(request)
複製代碼
如今加入任務隊列後,任務會立刻獲得執行。但須要注意的是,這句代碼的做用是將任務加入任務隊列,而不是執行任務,至於區別後面會講到。
後臺任務少不了數據的交互,咱們看一下數據是如何傳入傳出的。
先是在 Activity 傳數據給 Worker ,咱們傳一個格式化過的時間過去:
val dateFormat = SimpleDateFormat("hh:mm:ss", Locale.getDefault())
val data = Data.Builder()
.putString("time", dateFormat.format(Date()))
.build()
val request = OneTimeWorkRequest.Builder(DemoWorker::class.java)
.setInputData(data)
.build()
複製代碼
使用 WorkRequest 的 setInputData 方法傳遞 Data,Data 的使用和 Bundle 差很少。
在 Worker 中,從 inputData 能夠取到數據,這裏取到後簡單打印一下:
class MainWorker : Worker() {
override fun doWork(): WorkerResult {
Log.d("WorkManager", inputData.getString("time",""))
return WorkerResult.SUCCESS
}
}
複製代碼
當任務處理完了,須要將處理結果返回。傳入的是 inputData,傳出就是 outputData:
class MainWorker : Worker() {
override fun doWork(): WorkerResult {
Log.d("MainWorker", inputData.getString("time",""))
outputData = Data.Builder()
.putString("name", "SouthernBox")
.build()
return WorkerResult.SUCCESS
}
}
複製代碼
每個 WorkRequest 都會有一個 id,經過 id 能夠獲取到對應任務的 WorkStatus,而且是以 LiveData 形式提供的:
WorkManager.getInstance()
.getStatusById(request.id)
.observe(this, Observer { workStatus ->
if (workStatus != null && workStatus.state.isFinished) {
Log.d("MainActivity", workStatus.outputData.getString("name", ""))
}
})
複製代碼
若是須要取消一個在隊列中的任務,也是經過 id 實現的:
WorkManager.getInstance().cancelWorkById(request.id)
複製代碼
這樣咱們就完成了一個最簡單的 WorkManager,執行一下就能夠看到打印的結果了。
到目前爲止都是基本操做,和一個普通的異步任務沒有太大區別,接下來咱們看看它不同的一些地方。
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 | 鏈接非漫遊網絡 |
咱們試一下效果,添加一個須要聯網的條件,Activity 代碼以下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val dateFormat = SimpleDateFormat("hh:mm:ss", Locale.getDefault())
val data = Data.Builder()
.putString("date", dateFormat.format(Date()))
.build()
val request = OneTimeWorkRequest
.Builder(MainWorker::class.java)
.setConstraints(constraints)
.setInputData(data)
.build()
WorkManager.getInstance().enqueue(request)
WorkManager.getInstance()
.getStatusById(request.id)
.observe(this, Observer<WorkStatus> { workStatus ->
if (workStatus != null && workStatus.state.isFinished) {
Log.d("MainActivity",
workStatus.outputData.getString("name", ""))
}
})
}
}
複製代碼
打開應用以前,先把網絡關閉,打開後發現 Worker 並無打印時間,這時候再把網連上,就會看到打印出時間了。
這也是爲何前面說 WorkManager.getInstance().enqueue(request) 是將任務加入任務隊列,並不表明立刻執行任務,由於任務可能須要等到知足環境條件的狀況纔會執行。
仍是同樣的代碼,咱們來作點不同的操做:
不出意外的話,這時候你會看到有兩個時間的打印,並且兩個時間還不同,這是爲何呢?
第一個時間是第一次運行後,加入了任務隊列,但尚未執行的任務。第二個則是本次執行的任務打印的。這說明了,就算進程被殺掉,任務仍是存在,甚至若是重啓手機,任務依然會在知足條件的狀況下獲得執行。
這是 WorkManager 的另外一個特色,一旦發起一個任務,任務是能夠保證必定會被執行的,就算退出應用,甚至重啓手機都阻止不了他。但可能因爲添加了環境約束等緣由,它執行的時間是不肯定的。
當應用正在運行時,它會在當前的進程中啓用一個子線程執行。應用沒有運行的狀況下啓用,它則會本身選擇一種合適的方式在後臺運行。具體是什麼方式和 Android 的版本和依賴環境有關:
前面說了 OneTimeWorkRequest 是指任務只須要執行一遍,而 PeriodicWorkRequest 則能夠發起一個屢次執行的定時任務:
val request = PeriodicWorkRequest
.Builder(MainWorker::class.java, 15, TimeUnit.MINUTES)
.setConstraints(constraints)
.setInputData(data)
.build()
複製代碼
這樣,發起的任務就會每隔 15 分鐘執行一次。除了須要傳入間隔時間,使用起來跟 OneTimeWorkRequest 是沒有區別的。
你可能會想更頻繁的去執行一個任務,好比幾秒鐘執行一遍,但很遺憾,最小時間間隔就是 15 分鐘,看一下源碼就知道了。
還有須要注意的是,定時任務並非說通過指定時間後它就立刻執行,而是通過這一段時間後,等到知足約束條件等狀況時,它才執行。
WorkManager 容許咱們按照必定的順序執行任務,好比我想 A、B、C 三個任務按前後順序執行:
能夠這樣寫,把它們組成一條任務鏈:
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue()
複製代碼
這樣的話,上一個任務的 outputData 會成爲下一個任務的 inputData。
再更復雜一點,我想 A 和 B 同時執行,它們都執行完以後,再執行 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,我想到的辦法是每次執行以前,根據標籤去取消已有的任務。
以上,就是本文對 WorkManager 的簡單介紹和用法講解。
這裏引入一個思考,既然 WorkManager 的生命力這麼強,還能夠實現定時任務,那能不能讓咱們的應用生命力也這麼強?換句話說,能不能用它來保活?
要是上面有細看的話,你應該已經發現這幾點了:
總之,用 WorkManager 保活是不可能了,這輩子都不可能保活了。
很明顯,WorkManager 區別於異步任務,它更像是一個 Service。基本上,WorkManager 能作的,Service 也能作,我並無想到有什麼狀況是非用 WorkManger 不可的。
但反觀 Service,氾濫的 Service 後臺任務多是引發 Android 系統卡頓的主要緣由,這幾年 Google 也對 Service 也作了一些限制。
Android 6.0 (API 23)
休眠模式:在關閉手機屏幕後,系統會禁止應用的網絡請求等功能。
Android 8.0(API 26)
在某些不被容許的狀況下,調用 startService 會拋異常。
但目前不少 APP 的 target API 還在 23 如下,由於不想處理運行時權限,更別說 API 26 了。基於此,2017 年年末,谷歌採起了少有的強硬措施。
2018 年 8 月起
全部新開發的應用,Target API 必須是 26 或以上。
2018 年 11 月起
全部已發佈的應用,Target API 必須更新到 26 或以上。
2019 年起
每次發佈新版本後,全部應用都必須在一年內將 Target API 更新到最新版本
雖然這些措施對國內的環境沒有辦法形成直接影響,但這也會成爲一種發展指導方向。
說了這麼多,我想表達的是,在不久的未來,在某些狀況下,Service 已經沒卵用了!
而 WorkManager 做爲一個更合理的後臺任務管理庫,在這種狀況下就是一個更好的選擇了。