Jetpack學習(六)WorkManager

導入

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狀態,然后可能會根據工作的結果轉為 SUCCEEDEDFAILED 狀態;或者,如果結果是 retry,它可能會回到 ENQUEUED 狀態。在此過程中,隨時都可以取消工作,取消后工作將進入 CANCELLED 狀態。

image.png

SUCCEEDEDFAILEDCANCELLED 均表示此工作的終止狀態。如果您的工作處于上述任何狀態,WorkInfo.State.isFinished() 都將返回 true。

定期工作狀態

成功和失敗狀態僅適用于一次性工作和鏈式工作。定期工作只有一個終止狀態 CANCELLED。這是因為定期工作永遠不會結束。每次運行后,無論結果如何,系統都會重新對其進行調度。

image.png

管理工作

唯一工作

有的工作只需要開啟一次,需要避免重復啟動,可以使用唯一工作。

唯一工作既可用于一次性工作,也可用于定期工作。可以通過調用以下方法之一創建唯一工作序列,具體取決于是調度重復工作還是一次性工作。

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:將新工作附加到現有工作的末尾。此政策將導致您的新工作鏈接到現有工作,在現有工作完成后運行。

現有工作將成為新工作的先決條件。如果現有工作變為 CANCELLEDFAILED 狀態,新工作也會變為 CANCELLEDFAILED。如果您希望無論現有工作的狀態如何都運行新工作,請改用 APPEND_OR_REPLACE

APPEND_OR_REPLACE函數類似于 APPEND,不過它并不依賴于先決條件工作狀態。即使現有工作變為 CANCELLEDFAILED 狀態,新工作仍會運行。

對于定期工作,需要提供一個 ExistingPeriodicWorkPolicy,它支持 REPLACEKEEP 這兩個選項。這些選項的功能與其對應的 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)或更高版本為目標平臺,且包含需要訪問相機或麥克風的長時間運行的工作器,請分別聲明 cameramicrophone 前臺服務類型。有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
        )

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容