1. 事前準備
本程式碼研究室將介紹 WorkManager,這是具有回溯相容性和彈性的簡易程式庫,適用於可延後執行的背景工作。WorkManager
是 Android 推薦的工作排程器,可用來處理可延後的工作,並確保工作順利執行。
必要條件
- 瞭解 StateFlow 和 ViewModel。如果您剛接觸這些課程,請參閱「Compose 中的 ViewModel 和狀態」程式碼研究室 (特別是 ViewModel 和「狀態」的章節),或是「使用 Room 讀取及更新資料」程式碼研究室 (特別是資料流和 StateFlow 的章節)。
- 具備存放區和插入依附元件的相關知識。如需複習內容,請參閱「新增存放區和手動 DI」。
- 瞭解如何在應用程式中實作協同程式。
課程內容
- 如何在專案中新增 WorkManager。
- 如何排定簡單的工作。
- 如何設定 worker 的輸入和輸出參數。
- 如何鏈結 worker。
執行步驟
- 修改範例應用程式以使用 WorkManager。
- 實作將圖片模糊處理的工作要求。
- 鏈結工作,以便實作一系列的工作。
- 將資料傳入和傳出已排定的工作。
軟硬體需求
- 最新的 Android Studio 穩定版
- 連上網際網路
2. 應用程式總覽
時至今日,智慧型手機的拍攝功能幾乎都「非常」強大。要針對某些神祕事物拍出模糊度可靠的相片,已不再是攝影師的專利。
在本程式碼研究室中,您將使用 Blur-O-Matic 應用程式對相片進行模糊處理,並將成品存檔。那是尼斯湖水怪還是玩具潛水艇?只要使用 Blur-O-Matic,沒有人能看得出來!
畫面中的圓形按鈕可讓您選擇圖片的模糊程度。按一下「Start」按鈕,即可將圖片模糊處理並儲存。
應用程式目前不會套用任何模糊效果,也不會儲存圖片的成品。
本程式碼研究室的重點包括在應用程式中加入 WorkManager、建立 worker 來清除為了模糊處理圖片而建立的暫存檔、對圖片進行模糊處理,以及儲存圖片的成品,讓您只要點選「See File」按鈕,就能查看成品。您也會瞭解如何監控背景工作的狀態,並據此更新應用程式的 UI。
3. 瞭解 Blur-O-Matic 範例應用程式
取得範例程式碼
如要開始使用,請先下載範例程式碼:
或者,您也可以複製 GitHub 存放區的程式碼:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout starter
您可以在這個 GitHub 存放區中瀏覽 Blur-o-matic 應用程式的程式碼。
執行範例程式碼
若要瞭解範例程式碼,請完成下列步驟:
- 在 Android Studio 中開啟有範例程式碼的專案。
- 在 Android 裝置或模擬器中執行應用程式。
畫面中有圓形按鈕可讓您選取圖片的模糊程度。只要按一下「Start」按鈕,應用程式就會將圖片模糊處理並儲存。
目前應用程式不會在您點選「Start」按鈕時套用任何模糊效果。
範例程式碼逐步操作說明
您可以透過這個工作熟悉專案結構。下列清單提供專案中重要檔案和資料夾的逐步操作說明。
WorkerUtils
:之後要用來顯示Notifications
和程式碼,以便將點陣圖儲存至檔案的便利方法。BlurViewModel
:這個檢視模型會儲存應用程式的狀態,並與存放區互動。WorkManagerBluromaticRepository
:透過 WorkManager 啟動背景工作的類別。Constants
:靜態類別,其中包含本程式碼研究室中使用的部分常數。BluromaticScreen
: 包含 UI 的可組合函式,並與BlurViewModel
互動。可組合函式會顯示圖片,並且包含用來選取所需模糊處理程度的圓形按鈕。
4. 什麼是 WorkManager?
WorkManager 屬於 Android Jetpack 的一部分,也是一種架構元件,用於處理需結合「機會式執行」和「保證執行」的背景工作。機會式執行表示 WorkManager 會盡快執行背景工作。保證執行是指 WorkManager 會確保工作在不同情況下都能執行,即便您離開應用程式也一樣。
WorkManager 是非常靈活的程式庫,具有許多其他優勢。以下列出其中幾項優點:
- 支援非同步的一次性工作和週期性工作。
- 支援限制條件,如網路狀況、儲存空間和充電狀態等。
- 複雜的工作要求鏈結,例如同時處理不同工作要求。
- 一項工作要求產生的輸出內容,可做為下一個工作要求的輸入內容。
- 將 API 級別相容性支援至 API 級別 14 以上 (請參見附註)。
- 不論是否使用 Google Play 服務都能運作。
- 遵循系統健康狀態最佳做法。
- 支援在應用程式的 UI 中輕鬆顯示工作要求的狀態。
5. 使用 WorkManager 的時機
WorkManager 程式庫非常適合協助您完成各項必要工作。不論應用程式是否持續運作,只要把工作排入佇列就一定會執行。即使應用程式關閉,或使用者返回主畫面,工作照舊會執行。
以下列舉一些適合使用 WorkManager 的工作範例:
- 定期查詢最新的新聞報導。
- 為圖片套用篩選器,然後儲存圖片。
- 定期將本機資料同步至網路。
WorkManager 是在主執行緒外執行工作的一種方式,但並非所有在主執行緒外執行的工作類型都適用。協同程式是另一種執行方式,先前的程式碼研究室曾討論過相關內容。
如要進一步瞭解 WorkManager 的使用時機,請參閱背景工作指南。
6. 將 WorkManager 新增至應用程式
WorkManager
需要下列 Gradle 依附元件。建構檔案中已包含這項元件:
app/build.gradle.kts
dependencies {
// WorkManager dependency
implementation("androidx.work:work-runtime-ktx:2.8.1")
}
您必須在應用程式中使用最新版 work-runtime-ktx
的穩定版。
如果您變更了版本,請務必按一下「Sync Now」,將專案與更新過的 Gradle 檔案保持同步。
7. WorkManager 基本概念
您必須瞭解以下幾個 WorkManager 類別:
Worker
/CoroutineWorker
:worker 是一種在背景執行緒上同步執行工作的類別。由於我們希望進行非同步工作,因此可以使用 CoroutineWorker,它能與 Kotlin 協同程式互通。在此應用程式中,您會從 CoroutineWorker 類別擴充並覆寫doWork()
方法。這個方法是用來放置您要在背景執行的實際工作程式碼。WorkRequest
:此類別代表執行某些工作的要求。WorkRequest
可讓您定義 worker 要執行一次還是定期執行。您也可以對WorkRequest
設定限制條件,要求工作必須先滿足特定條件才能執行。例如,在開始執行要求的工作前,裝置要先充電。您會在建立WorkRequest
的過程中傳入CoroutineWorker
。WorkManager
:此類別會確實排定WorkRequest
並將其執行。它會以在系統資源上分散負載的方式排定WorkRequest
,同時遵循您指定的限制條件。
在這個範例中,您可以定義新的 BlurWorker
類別,其中包含將圖片模糊處理的程式碼。當您點選「Start」按鈕時,WorkManager 會建立 WorkRequest
物件並排入佇列。
8. 建立 BlurWorker
在這個步驟中,您可以從 res/drawable
資料夾內取得名為 android_cupcake.png
的圖片,並在背景對該圖片執行幾項函式。這些函式會對圖片進行模糊處理。
- 在 Android 專案窗格中的
com.example.bluromatic.workers
套件上按一下滑鼠右鍵,然後依序選取「New」->「Kotlin Class/File」。 - 將新的 Kotlin 類別命名為
BlurWorker
。使用必要的建構函式參數從CoroutineWorker
擴充該類別。
workers/BlurWorker.kt
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import android.content.Context
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
}
BlurWorker
類別會擴充 CoroutineWorker
類別,而非較通用的 Worker
類別。doWork()
的 CoroutineWorker
類別實作是一項暫停函式,因此可執行 Worker
無法執行的非同步程式碼。如 WorkManager 中的執行緒指南中所述,「建議 Kotlin 使用者採用 CoroutineWorker 做為實作方式」。
此時,Android Studio 會在 class BlurWorker
下方繪製一條紅色的波浪線,表示發生錯誤。
如果將游標懸停在文字 class BlurWorker
上,IDE 會顯示彈出式視窗,其中含有與錯誤相關的額外資訊。
錯誤訊息指出您並未按規定覆寫 doWork()
方法。
請在 doWork()
方法中編寫程式碼,將杯子蛋糕的圖片模糊處理。
請按照下列步驟修正錯誤並實作 doWork()
方法:
- 按一下「BlurWorker」文字,將游標移到類別代碼中。
- 在 Android Studio 選單中,依序選取「Code」>「Override Methods...」
- 在「Override Members」彈出式視窗中,選取
doWork()
。 - 按一下「OK」。
- 在類別宣告之前,建立名為
TAG
的變數,並指派其值為BlurWorker
。請注意,這個變數與doWork()
方法無關,但會在之後呼叫Log()
時用到。
workers/BlurWorker.kt
private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
...
- 如要更準確瞭解工作執行時間,您需要使用
WorkerUtil
的makeStatusNotification()
函式。此函式可讓您輕鬆在畫面頂端顯示通知橫幅。
在 doWork()
方法中,使用 makeStatusNotification()
函式顯示狀態通知,並告知使用者已啟動模糊處理 worker,正在將圖片模糊處理。
workers/BlurWorker.kt
import com.example.bluromatic.R
...
override suspend fun doWork(): Result {
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
...
- 新增
return try...catch
程式碼區塊,這是實際執行圖片模糊處理作業的位置。
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
} catch (throwable: Throwable) {
}
...
- 在
try
區塊中呼叫Result.success()
。 - 在
catch
區塊中呼叫Result.failure()
。
workers/BlurWorker.kt
...
makeStatusNotification(
applicationContext.resources.getString(R.string.blurring_image),
applicationContext
)
return try {
Result.success()
} catch (throwable: Throwable) {
Result.failure()
}
...
- 在
try
區塊中,建立名為picture
的新變數並填入點陣圖,該圖片是藉由呼叫BitmapFactory.decodeResource
()
方法,並傳入應用程式的資源套件以及杯子蛋糕圖片的資源 ID 所傳回。
workers/BlurWorker.kt
...
return try {
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
Result.success()
...
- 透過呼叫
blurBitmap()
函式並為blurLevel
參數傳入picture
變數和1
的值 (一個),將點陣圖模糊處理。 - 將結果儲存在名為
output
的新變數中。
workers/BlurWorker.kt
...
val picture = BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.android_cupcake
)
val output = blurBitmap(picture, 1)
Result.success()
...
- 建立新的變數
outputUri
,並呼叫writeBitmapToFile()
函式來填入該變數。 - 在對
writeBitmapToFile()
的呼叫中,傳遞做為引數的應用程式結構定義output
變數。
workers/BlurWorker.kt
...
val output = blurBitmap(picture, 1)
// Write bitmap to a temp file
val outputUri = writeBitmapToFile(applicationContext, output)
Result.success()
...
- 新增程式碼,向使用者顯示含有
outputUri
變數的通知訊息。
workers/BlurWorker.kt
...
val outputUri = writeBitmapToFile(applicationContext, output)
makeStatusNotification(
"Output is $outputUri",
applicationContext
)
Result.success()
...
- 在
catch
區塊中,記錄錯誤訊息,指出嘗試模糊處理圖片時發生錯誤。呼叫Log.e()
會傳遞先前定義的TAG
變數、相對應的訊息,以及擲回的例外狀況。
workers/BlurWorker.kt
...
} catch (throwable: Throwable) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_applying_blur),
throwable
)
Result.failure()
}
...
根據預設,CoroutineWorker,
會以 Dispatchers.Default
執行,但可藉由呼叫 withContext()
並傳入所需的調度工具來進行變更。
- 建立
withContext()
區塊。 - 在對
withContext()
的呼叫中傳遞Dispatchers.IO
,由於 lambda 函式可能會封鎖 IO 作業,因此會在特殊的執行緒集區中執行。 - 將先前編寫的
return try...catch
程式碼移入這個區塊。
...
return withContext(Dispatchers.IO) {
return try {
// ...
} catch (throwable: Throwable) {
// ...
}
}
...
Android Studio 會顯示以下錯誤,因為您無法從 lambda 函式內呼叫 return
。
只要新增標籤即可修正這項錯誤,如彈出式視窗中所示。
...
//return try {
return@withContext try {
...
由於此工作站執行速度非常快,因此建議您在程式碼中新增延遲,用來模擬執行速度較慢的工作。
- 在
withContext()
lambda 中,新增對delay()
公用程式函式的呼叫並傳入DELAY_TIME_MILLIS
常數。此呼叫為本程式碼研究室專用,用來在通知訊息之間提供延遲。
import com.example.bluromatic.DELAY_TIME_MILLIS
import kotlinx.coroutines.delay
...
return withContext(Dispatchers.IO) {
// This is an utility function added to emulate slower work.
delay(DELAY_TIME_MILLIS)
val picture = BitmapFactory.decodeResource(
...
9. 更新 WorkManagerBluromaticRepository
存放區會處理所有與 WorkManager 的互動。此結構遵循關注點分離的設計原則,也是我們建議的 Android 架構模式。
- 在
data/WorkManagerBluromaticRepository.kt
檔案的WorkManagerBluromaticRepository
類別中,建立名為workManager
的私有變數,並藉由呼叫WorkManager.getInstance(context)
將WorkManager
例項儲存在其中。
data/WorkManagerBluromaticRepository.kt
import androidx.work.WorkManager
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
// New code
private val workManager = WorkManager.getInstance(context)
...
在 WorkManager 中建立 WorkRequest 並將其排入佇列
很好,現在我們來發送 WorkRequest
,並要求 WorkManager 執行!WorkRequest
分為兩種類型:
OneTimeWorkRequest
:只執行一次的WorkRequest
。PeriodicWorkRequest
:在週期中重複執行的WorkRequest
。
您想要在點選「Start」按鈕之後,只對圖片進行一次模糊處理。
此工作會在 applyBlur()
方法中執行,點選「Start」按鈕時就會呼叫該方法。
以下是在 applyBlur()
方法中完成的步驟。
- 透過為模糊處理 worker 建立
OneTimeWorkRequest
,並從 WorkManager KTX 呼叫OneTimeWorkRequestBuilder
擴充功能函式,填入名為blurBuilder
的新變數。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
}
- 透過對
workManager
物件呼叫enqueue()
方法來啟動工作。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.BlurWorker
import androidx.work.OneTimeWorkRequestBuilder
...
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// Start the work
workManager.enqueue(blurBuilder.build())
}
- 執行應用程式,然後在您點選「Start」按鈕時查看通知。
目前不論您選擇哪一個選項,圖片的模糊程度都相同。在後續步驟中,模糊程度會根據所選選項而有所差異。
如要確認圖片是否已成功模糊處理,請在 Android Studio 中開啟「Device Explorer」:
接著,依序前往「data」>「data」>「com.example.bluromatic」>「files」>「blur_filter_outputs」「<URI>」,確認杯子蛋糕的圖片已模糊處理:
10. 輸入資料和輸出資料
對資源目錄中的圖片素材進行模糊處理是不錯的做法,但為了讓 Blur-O-Matic 真正成為革命性的圖片編輯應用程式,您必須讓使用者能對畫面上顯示的圖片進行模糊處理,然後顯示經過模糊處理的圖片成品。
為此,我們要提供杯子蛋糕圖片的 URI 做為 WorkRequest
的輸入內容,然後使用 WorkRequest
的輸出內容來顯示最終的模糊圖片。
輸入和輸出內容會透過 Data
物件傳入及傳出 worker。Data
物件是鍵/值組合的輕量級容器,用來儲存可透過 WorkRequest
傳入或傳出 worker 的少量資料。
在下一個步驟中,您會建立輸入資料物件,將 URI 傳遞至 BlurWorker
。
建立輸入資料物件
- 在
data/WorkManagerBluromaticRepository.kt
檔案的WorkManagerBluromaticRepository
類別中,建立名為imageUri
的新私有變數。 - 藉由呼叫結構定義方法
getImageUri()
,以圖片 URI 填入變數。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.getImageUri
...
class WorkManagerBluromaticRepository(context: Context) : BluromaticRepository {
private var imageUri: Uri = context.getImageUri() // <- Add this
private val workManager = WorkManager.getInstance(context)
...
應用程式程式碼包含用來建立輸入資料物件的 createInputDataForWorkRequest()
輔助函式。
data/WorkManagerBluromaticRepository.kt
// For reference - already exists in the app
private fun createInputDataForWorkRequest(blurLevel: Int, imageUri: Uri): Data {
val builder = Data.Builder()
builder.putString(KEY_IMAGE_URI, imageUri.toString()).putInt(BLUR_LEVEL, blurLevel)
return builder.build()
}
首先,輔助函式會建立 Data.Builder
物件,然後將 imageUri
和 blurLevel
做為鍵/值組合放入其中。接著,輔助函式會在呼叫 return builder.build()
物件時建立並傳回 Data 物件。
- 如要為 WorkRequest 設定輸入資料物件,請呼叫
blurBuilder.setInputData()
方法。只要呼叫createInputDataForWorkRequest()
輔助函式做為引數,即可透過單一步驟建立和傳遞資料物件。如要呼叫createInputDataForWorkRequest()
函式,請傳入blurLevel
變數和imageUri
變數。
data/WorkManagerBluromaticRepository.kt
override fun applyBlur(blurLevel: Int) {
// Create WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
// New code for input data object
blurBuilder.setInputData(createInputDataForWorkRequest(blurLevel, imageUri))
workManager.enqueue(blurBuilder.build())
}
存取輸入資料物件
現在,讓我們更新 BlurWorker
類別中的 doWork()
方法,以取得輸入資料物件傳入的 URI 和模糊處理程度。如未提供 blurLevel
的值,則預設值為 1
。
在 doWork()
方法中:
- 建立名為
resourceUri
的新變數並填入變數,只要呼叫inputData.getString()
,並傳入建立輸入資料物件時用來做為鍵的常數KEY_IMAGE_URI
,即可填入變數。
val resourceUri = inputData.getString(KEY_IMAGE_URI)
- 建立名為
blurLevel
的新變數。如要填入變數,請呼叫inputData.getInt()
,並傳入建立資料物件時用來做為鍵的常數BLUR_LEVEL
。如果尚未建立這組鍵/值組合,請提供預設值1
(一個)。
workers/BlurWorker.kt
import com.example.bluromatic.KEY_BLUR_LEVEL
import com.example.bluromatic.KEY_IMAGE_URI
...
override fun doWork(): Result {
// ADD THESE LINES
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val blurLevel = inputData.getInt(KEY_BLUR_LEVEL, 1)
// ... rest of doWork()
}
現在,我們來使用 URI 對畫面上的杯子蛋糕圖片進行模糊處理。
- 檢查
resourceUri
變數是否已填入。如未填入,程式碼應該會擲回例外狀況。以下程式碼使用了require()
陳述式,如果第一個引數評估結果為否 (false),就會擲回IllegalArgumentException
。
workers/BlurWorker.kt
return@withContext try {
// NEW code
require(!resourceUri.isNullOrBlank()) {
val errorMessage =
applicationContext.resources.getString(R.string.invalid_input_uri)
Log.e(TAG, errorMessage)
errorMessage
}
由於圖片來源是以 URI 的形式傳入,因此我們需要 ContentResolver 物件,用來讀取該 URI 指向的內容。
- 將
contentResolver
物件新增至applicationContext
值。
workers/BlurWorker.kt
...
require(!resourceUri.isNullOrBlank()) {
// ...
}
val resolver = applicationContext.contentResolver
...
- 由於目前圖片來源是透過 URI 傳遞,因此請使用
BitmapFactory.decodeStream()
建立點陣圖物件,不要使用BitmapFactory.decodeResource()
。
workers/BlurWorker.kt
import android.net.Uri
...
// val picture = BitmapFactory.decodeResource(
// applicationContext.resources,
// R.drawable.android_cupcake
// )
val resolver = applicationContext.contentResolver
val picture = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
- 將呼叫中的
blurLevel
變數傳遞至blurBitmap()
函式。
workers/BlurWorker.kt
//val output = blurBitmap(picture, 1)
val output = blurBitmap(picture, blurLevel)
建立輸出資料物件
您已完成這個 worker,現在可以在 Result.success()
中將輸出 URI 做為輸出資料物件傳回。如果提供輸出 URI 做為輸出資料物件,其他 worker 就可以輕鬆地對該 worker 進行後續作業。在下一節建立 worker 鏈結時,這個方法會很有用。
如要這樣做,請按照下列步驟進行:
- 在
Result.success()
程式碼之前,建立名為outputData
的新變數。 - 如要填入這個變數,請呼叫
workDataOf()
函式,並使用常數KEY_IMAGE_URI
做為鍵,變數outputUri
做為值。workDataOf()
函式會根據傳入的鍵/值組合建立 Data 物件。
workers/BlurWorker.kt
import androidx.work.workDataOf
// ...
val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())
- 更新
Result.success()
程式碼,將這個新的 Data 物件做為引數。
workers/BlurWorker.kt
//Result.success()
Result.success(outputData)
- 移除顯示通知的程式碼,因為輸出 Data 物件現在使用的是 URI,因此不再需要該程式碼。
workers/BlurWorker.kt
// REMOVE the following notification code
//makeStatusNotification(
// "Output is $outputUri",
// applicationContext
//)
執行應用程式
此時,執行應用程式時,應用程式應該能進行編譯。您可以透過 Device Explorer 查看經過模糊處理的圖片,但還無法在螢幕上查看。
請注意,您可能需要同步處理才能看到圖片:
真厲害!您已成功使用 WorkManager
對輸入圖片進行模糊處理!
11. 鏈結工作
您目前正在進行的是將圖片模糊處理的工作。這項工作是最適合進行的第一步,但應用程式仍然缺少一些核心功能:
- 應用程式不會清除暫存檔案。
- 應用程式實際上並未將圖片儲存至永久檔案。
- 應用程式會一律對圖片套用相同的模糊處理程度。
您可以使用 WorkManager 工作鏈結新增這項功能。WorkManager 可讓您建立個別 WorkerRequest
,可以依序執行,也可以同時執行。
在本節中,您會建立一個工作鏈結,如下所示:
方塊代表 WorkRequest
。
鏈結的另一個功能是可以接受輸入內容及產生輸出內容。一個 WorkRequest
的輸出內容會成為鏈結中下一個 WorkRequest
的輸入內容。
您已經使用 CoroutineWorker
對圖片進行模糊處理,但還需要 CoroutineWorker
來清除暫存檔案,並使用 CoroutineWorker
永久保存圖片。
建立 CleanupWorker
CleanupWorker
會刪除暫存檔案 (如果有的話)。
- 在 Android 專案窗格中的
com.example.bluromatic.workers
套件上按一下滑鼠右鍵,然後依序選取「New」->「Kotlin Class/File」。 - 將新的 Kotlin 類別命名為
CleanupWorker
。 - 複製 CleanupWorker.kt 的程式碼,如以下程式碼範例所示。
由於檔案操控不屬於本程式碼研究室的課程範圍,您可以為 CleanupWorker
複製下列程式碼。
workers/CleanupWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.OUTPUT_PATH
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.File
/**
* Cleans up temporary files generated during blurring process
*/
private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result {
/** Makes a notification when the work starts and slows down the work so that it's easier
* to see each WorkRequest start, even on emulated devices
*/
makeStatusNotification(
applicationContext.resources.getString(R.string.cleaning_up_files),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
return@withContext try {
val outputDirectory = File(applicationContext.filesDir, OUTPUT_PATH)
if (outputDirectory.exists()) {
val entries = outputDirectory.listFiles()
if (entries != null) {
for (entry in entries) {
val name = entry.name
if (name.isNotEmpty() && name.endsWith(".png")) {
val deleted = entry.delete()
Log.i(TAG, "Deleted $name - $deleted")
}
}
}
}
Result.success()
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_cleaning_file),
exception
)
Result.failure()
}
}
}
}
建立 SaveImageToFileWorker
SaveImageToFileWorker
類別會將暫存檔案儲存至永久檔案。
SaveImageToFileWorker
會接受輸入和輸出內容。輸入內容是暫時經過模糊處理圖片的 URI 的 String
,透過鍵 KEY_IMAGE_URI
進行儲存。輸出結果是已儲存的模糊圖片 URI 的 String
,透過鍵 KEY_IMAGE_URI
進行儲存。
- 在 Android 專案窗格中的
com.example.bluromatic.workers
套件上按一下滑鼠右鍵,然後依序選取「New」->「Kotlin Class/File」。 - 將新的 Kotlin 類別命名為
SaveImageToFileWorker
。 - 複製 SaveImageToFileWorker.kt 程式碼,如下列範例程式碼所示。
由於檔案操控不屬於本程式碼研究室的課程範圍,您可以為 SaveImageToFileWorker
複製下列程式碼。在提供的程式碼中,請注意如何透過鍵 KEY_IMAGE_URI
擷取及儲存 resourceUri
和 output
值。這項程序與您先前為輸入和輸出資料物件編寫的程式碼非常類似。
workers/SaveImageToFileWorker.kt
package com.example.bluromatic.workers
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.bluromatic.DELAY_TIME_MILLIS
import com.example.bluromatic.KEY_IMAGE_URI
import com.example.bluromatic.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.Date
/**
* Saves the image to a permanent file
*/
private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
private val title = "Blurred Image"
private val dateFormatter = SimpleDateFormat(
"yyyy.MM.dd 'at' HH:mm:ss z",
Locale.getDefault()
)
override suspend fun doWork(): Result {
// Makes a notification when the work starts and slows down the work so that
// it's easier to see each WorkRequest start, even on emulated devices
makeStatusNotification(
applicationContext.resources.getString(R.string.saving_image),
applicationContext
)
return withContext(Dispatchers.IO) {
delay(DELAY_TIME_MILLIS)
val resolver = applicationContext.contentResolver
return@withContext try {
val resourceUri = inputData.getString(KEY_IMAGE_URI)
val bitmap = BitmapFactory.decodeStream(
resolver.openInputStream(Uri.parse(resourceUri))
)
val imageUrl = MediaStore.Images.Media.insertImage(
resolver, bitmap, title, dateFormatter.format(Date())
)
if (!imageUrl.isNullOrEmpty()) {
val output = workDataOf(KEY_IMAGE_URI to imageUrl)
Result.success(output)
} else {
Log.e(
TAG,
applicationContext.resources.getString(R.string.writing_to_mediaStore_failed)
)
Result.failure()
}
} catch (exception: Exception) {
Log.e(
TAG,
applicationContext.resources.getString(R.string.error_saving_image),
exception
)
Result.failure()
}
}
}
}
建立工作鏈結
目前,程式碼只會建立及執行單一 WorkRequest
。
在這個步驟中,您必須修改程式碼來建立並執行 WorkRequest 鏈結,而非只執行一個模糊處理圖片要求。
在 WorkRequests 鏈結中,您的第一個工作要求是清理暫存檔案。
- 請呼叫
workManager.beginWith()
,不要呼叫OneTimeWorkRequestBuilder
。
呼叫 beginWith()
方法會傳回 WorkContinuation
物件,並為含有第一個工作請求的 WorkRequest
鏈結建立起始點。
data/WorkManagerBluromaticRepository.kt
import androidx.work.OneTimeWorkRequest
import com.example.bluromatic.workers.CleanupWorker
// ...
override fun applyBlur(blurLevel: Int) {
// Add WorkRequest to Cleanup temporary images
var continuation = workManager.beginWith(OneTimeWorkRequest.from(CleanupWorker::class.java))
// Add WorkRequest to blur the image
val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()
...
只要呼叫 then()
方法並傳入 WorkRequest
物件,即可新增至此工作要求鏈結。
- 移除對
workManager.enqueue(blurBuilder.build())
的呼叫,這個呼叫只會將一個工作要求加入佇列。 - 呼叫
.then()
方法,將下一個工作要求新增至鏈結中。
data/WorkManagerBluromaticRepository.kt
...
//workManager.enqueue(blurBuilder.build())
// Add the blur work request to the chain
continuation = continuation.then(blurBuilder.build())
...
- 建立儲存圖片的工作要求,並將要求新增至鏈結中。
data/WorkManagerBluromaticRepository.kt
import com.example.bluromatic.workers.SaveImageToFileWorker
...
continuation = continuation.then(blurBuilder.build())
// Add WorkRequest to save the image to the filesystem
val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
.build()
continuation = continuation.then(save)
...
- 如要啟動工作,請在接續物件上呼叫
enqueue()
方法。
data/WorkManagerBluromaticRepository.kt
...
continuation = continuation.then(save)
// Start the work
continuation.enqueue()
...
這個程式碼會產生並執行以下的 WorkRequest 鏈結:CleanupWorker
WorkRequest
,後面接著 BlurWorker
WorkRequest
和 SaveImageToFileWorker
WorkRequest
。
- 執行應用程式。
您現在可以按一下「Start」,並在不同 worker 執行時看到通知。您仍然能在 Device Explorer 看到經過模糊處理的圖片。在接下來的章節中,您將新增另一個按鈕,方便使用者在裝置上查看經過模糊處理的圖片。
請注意,在下列螢幕截圖中,通知訊息會顯示目前正在執行的 worker。
請注意,輸出資料夾中會有多張經過模糊處理的圖片,包括還在進行模糊處理的圖片,以及依據您選擇的模糊程度呈現的圖片成品。
太棒了!現在您可以清除暫存檔案、將圖片模糊處理並且儲存圖片!
12. 取得解決方案程式碼
完成程式碼研究室後,如要下載當中用到的程式碼,您可以使用這些指令:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-workmanager.git $ cd basic-android-kotlin-compose-training-workmanager $ git checkout intermediate
另外,您也可以下載存放區為 ZIP 檔案,然後解壓縮並在 Android Studio 中開啟。
如要查看本程式碼研究室的解決方案程式碼,請前往 GitHub。
13. 結論
恭喜!您已完成 Blur-O-Matic 應用程式,並且在過程中瞭解如何進行以下操作:
- 將 WorkManager 新增至專案
- 排定
OneTimeWorkRequest
- 輸入和輸出參數
- 將工作的
WorkRequest
鏈結在一起
WorkManager 還支援更多本程式碼研究室未涵蓋的功能,包括重複性工作、測試支援資料庫、同時執行不同工作要求,以及合併輸入內容。
詳情請參閱使用 WorkManager 安排工作說明文件。