導入
def work_version = "2.6.0"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
使用
class TestWorker(appContext: Context, workerParams: WorkerParameters): Worker(appContext,workerParams) {
override fun doWork(): Result {
Log.d("haha",Thread.currentThread().name)
return Result.success()
}
}
執行一次的任務
val testWorkerRequest1 = OneTimeWorkRequestBuilder<TestWorker>()
// Additional configuration
.build()
val testWorkerRequest2 = OneTimeWorkRequest.from(TestWorker::class.java)
WorkManager.getInstance(requireContext()).enqueue(testWorkerRequest1)
}
執行定期的任務
定期任務最低定義間隔為15分鐘
val workRequest =
PeriodicWorkRequestBuilder<TestWorker>(1, TimeUnit.HOURS)
// Additional configuration
build()
WorkManager.getInstance(requireContext()).enqueue(workRequest)
工作約束
約束將工作延遲到滿足最佳條件時運行。
NetworkType
約束運行工作所需的[網絡類型]。例如 Wi-Fi UNMETERED
BatteryNotLow
如果設置為 true,那么當設備處于“電量不足模式”時,工作不會運行。
RequiresCharging
如果設置為 true,那么工作只能在設備充電時運行。
DeviceIdle
如果設置為 true,則要求用戶的設備必須處于空閑狀態,才能運行工作。如果要運行批量操作,否則可能會降低用戶設備上正在積極運行的其他應用的性能,建議使用此約束。
StorageNotLow
如果設置為 true,那么當用戶設備上的存儲空間不足時,工作不會運行。
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build()
val workerRequest: WorkRequest =
OneTimeWorkRequestBuilder<TestWorker>()
.setConstraints(constraints)
.build()
WorkManager.getInstance(requireContext()).enqueue(workerRequest)
重試和退避政策
class TestWorkerRetry (appContext: Context, workerParams: WorkerParameters): Worker(appContext,workerParams) {
override fun doWork(): Result {
Log.d("haha",Thread.currentThread().name)
return Result.retry()
}
}
val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build()
如果doWork返回retry(),BackoffPolicy.LINEAR會在設置的延時時間后重試,每次增加延時時間,比如設置的延時時間為10秒,如果一直返回retry(),就會在20秒、30秒、40秒依次增加設置的延時時間重試。BackoffPolicy.EXPONENTIAL,每次會以指數級增加下一次重試的時間,比如設置的延時時間為10秒,那么重試時長序列將接近 20、40、80 秒,以此類推。
標記工作
每個工作請求都有一個唯一標識符,該標識符可用于在以后標識該工作,以便取消工作或觀察其進度。
如果有一組在邏輯上相關的工作,對這些工作項進行標記可能也會很有幫助。通過標記,您一起處理一組工作請求。
例如,WorkManager.cancelAllWorkByTag(String)
會取消帶有特定標記的所有工作請求,WorkManager.getWorkInfosByTag(String)
會返回一個 WorkInfo 對象列表,該列表可用于確定當前工作狀態。
val workerRequest: WorkRequest =
OneTimeWorkRequestBuilder<TestWorker>()
.addTag("test")
.build()
傳入數據
val workerRequest: WorkRequest =
OneTimeWorkRequestBuilder<TestWorker>()
.setInputData(workDataOf("key" to "I'm value"))
.build()
class TestWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
val value = inputData.getString("key")
Log.d("haha", value!!)
return Result.success()
}
}
工作狀態
一次性的工作狀態
對于 one-time
工作請求,工作的初始狀態為 ENQUEUED
在 ENQUEUED
狀態下,您的工作會在滿足其 Constraints
和初始延遲計時要求后立即運行。接下來,該工作會轉為 RUNNING
狀態,然后可能會根據工作的結果轉為 SUCCEEDED
、FAILED
狀態;或者,如果結果是 retry
,它可能會回到 ENQUEUED
狀態。在此過程中,隨時都可以取消工作,取消后工作將進入 CANCELLED
狀態。
SUCCEEDED
、FAILED
和 CANCELLED
均表示此工作的終止狀態。如果您的工作處于上述任何狀態,WorkInfo.State.isFinished()
都將返回 true。
定期工作狀態
成功和失敗狀態僅適用于一次性工作和鏈式工作。定期工作只有一個終止狀態 CANCELLED
。這是因為定期工作永遠不會結束。每次運行后,無論結果如何,系統都會重新對其進行調度。
管理工作
唯一工作
有的工作只需要開啟一次,需要避免重復啟動,可以使用唯一工作。
唯一工作既可用于一次性工作,也可用于定期工作。可以通過調用以下方法之一創建唯一工作序列,具體取決于是調度重復工作還是一次性工作。
WorkManager.enqueueUniqueWork()
WorkManager.enqueueUniquePeriodicWork()`
val testWorkerRequest =
PeriodicWorkRequestBuilder<TestWorker>(900000, TimeUnit.SECONDS).build()
WorkManager.getInstance(requireContext()).enqueueUniquePeriodicWork(
"testwork",
ExistingPeriodicWorkPolicy.KEEP,
testWorkerRequest
解決沖突政策
ExistingWorkPolicy
它支持用于處理沖突的 4 個選項。
REPLACE
:用新工作替換現有工作。此選項將取消現有工作。
KEEP
:保留現有工作,并忽略新工作。
APPEND
:將新工作附加到現有工作的末尾。此政策將導致您的新工作鏈接到現有工作,在現有工作完成后運行。
現有工作將成為新工作的先決條件。如果現有工作變為 CANCELLED
或 FAILED
狀態,新工作也會變為 CANCELLED
或 FAILED
。如果您希望無論現有工作的狀態如何都運行新工作,請改用 APPEND_OR_REPLACE
。
APPEND_OR_REPLACE
函數類似于 APPEND
,不過它并不依賴于先決條件工作狀態。即使現有工作變為 CANCELLED
或 FAILED
狀態,新工作仍會運行。
對于定期工作,需要提供一個 ExistingPeriodicWorkPolicy
,它支持 REPLACE
和 KEEP
這兩個選項。這些選項的功能與其對應的 ExistingWorkPolicy 功能相同。
觀察工作
// by id
workManager.getWorkInfoByIdLiveData(syncWorker.id) // LiveData<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync") // LiveData<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag") // LiveData<List<WorkInfo>>
val testWorkerRequest = OneTimeWorkRequestBuilder<TestWorker>().build()
WorkManager.getInstance(requireContext()).enqueue(testWorkerRequest)
val listener = WorkManager.getInstance(requireContext())
.getWorkInfoByIdLiveData(testWorkerRequest.id)
listener.observe(viewLifecycleOwner, object : Observer<WorkInfo> {
override fun onChanged(t: WorkInfo?) {
Log.d("haha", t!!.state.name)
}
})
高級用法
自定義初始化
在manifest里面移除默認的WorkManagerInitializer,在Application中實現Configuration.Provider,可以對WorkManager進行配置,比如設置線程池
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- If you are using androidx.startup to initialize other components -->
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
class MyApplication : Application(), Configuration.Provider {
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder().setExecutor(Executors.newSingleThreadExecutor()).build()
}
}
在Worker處理線程
WorkManager 會自動在后臺線程中調用 Worker.doWork()
,可以初始化的時候給WorkManager配置線程池或者不配置,系統會默認給一個線程池。Worker.doWork()
是同步調用,內部調用方法需要調用同步方法。如果需要在內部調用異步回調操作的方法,需要使用ListenableWorker
使用CoroutineWorker
class TestWorker(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
Log.d("haha", "start" + Thread.currentThread().name)
delay(5000)
Log.d("haha", "worker" + Thread.currentThread().name)
return Result.success()
}
}
在ListenableWorker處理
需要處理基于回調的異步操作。在這種情況下,不能只依靠 Worker
來完成操作,因為它無法以阻塞方式完成這項工作。WorkManager 通過 ListenableWorker
支持該用
使用councurrent-futures
包含到 gradle 文件中并使用 CallbackToFutureAdapter
。
implementation "androidx.concurrent:concurrent-futures-ktx:1.1.0"
import android.content.Context
import android.util.Log
import androidx.concurrent.futures.CallbackToFutureAdapter
import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
import com.google.common.util.concurrent.ListenableFuture
import okhttp3.*
import java.io.IOException
class CallbackWorker(
context: Context,
params: WorkerParameters
) : ListenableWorker(context, params) {
override fun startWork(): ListenableFuture<Result> {
return CallbackToFutureAdapter.getFuture { completer ->
val callback = object : Callback {
var successes = 0
override fun onFailure(call: Call, e: IOException) {
Log.d("onFailure", e.message + "")
e.printStackTrace()
completer.setException(e)
}
override fun onResponse(call: Call, response: Response) {
successes++
Log.d("onResponse", successes.toString() + " " + response.body?.string())
if (successes == 100) {
completer.set(Result.success())
}
}
}
repeat(100) {
val client = OkHttpClient();
val request: Request = Request.Builder()
.url("https://www.baidu.com")
.get()
.build()
val call = client.newCall(request)
call.enqueue(callback)
}
callback
}
}
}
val testWorkerRequest =
OneTimeWorkRequestBuilder<CallbackWorker>().build()
WorkManager.getInstance(requireContext()).enqueue(testWorkerRequest)
WorkManager.getInstance(requireContext()).getWorkInfoByIdLiveData(testWorkerRequest.id)
.observe(viewLifecycleOwner, object : Observer<WorkInfo> {
override fun onChanged(t: WorkInfo?) {
Log.d("haha", t!!.state.name)
}
})
支持長時間運行的工作器
WorkManager 可以向操作系統提供一個信號,指示在此項工作執行期間應盡可能讓進程保持活躍狀態。這些工作器可以運行超過 10 分鐘。這一新功能的示例用例包括批量上傳或下載(不可分塊)、在本地進行的機器學習模型處理,或者對應用的用戶很重要的任務。
ListenableWorker
現在支持 setForegroundAsync()
API,而 CoroutineWorker
則支持掛起 setForeground()
API。這些 API 允許開發者指定此 WorkRequest
是“重要的”(從用戶的角度來看)或“長時間運行的”任務。
import android.annotation.TargetApi
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.work.*
import kotlinx.coroutines.delay
import com.example.myapplication.R
class TestWorker(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result {
repeat(100) {
delay(1000)
Log.d(
"haha", "測試長時間任務" + it
)
setForeground(createForegroundInfo(it.toString()))
}
return Result.success()
}
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = "123"
val title = "測試長時間任務"
val cancel = "取消長時間任務"
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
// Create a Notification channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(id, title, NotificationManager.IMPORTANCE_HIGH)
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentText(progress)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(123, notification)
}
@TargetApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String, importance: Int) {
val channel = NotificationChannel(channelId, channelName, importance)
val manager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
如果應用以 Android 10(API 級別 29)或更高版本為目標平臺,且包含需要位置信息訪問權限的長時間運行的工作器,指明該工作器使用 location
的前臺服務類型。此外,如果您的應用以 Android 11(API 級別 30)或更高版本為目標平臺,且包含需要訪問相機或麥克風的長時間運行的工作器,請分別聲明 camera
或 microphone
前臺服務類型。有2種方式申明
在應用的清單中聲明工作器的前臺服務類型。
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="location|microphone"
tools:node="merge" />
在運行時指定前臺服務類型
return ForegroundInfo(
123,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
)