WorkManager

  • 投稿者:
  • 投稿カテゴリー:その他

WorkManagerとは

Android Jetpack の一部
バックグラウンド処理のためのアーキテクチャコンポーネント

環境

Projectのbuild.gradleにWorkManagerのバージョンを定義
buildscript {

versions.work = “2.5.0”

}

appのbuild.gradleにWorkManagerを追記
dependencies {

implementation “androidx.work:work-runtime-ktx:$versions.work”
}

サンプル

ファイル構成

root
┣workers
┃┣BlurWorker.kt // ぼかし処理(workerクラス)
┃┣CleanupWorker.kt // 一時ファイル削除処理(workerクラス)
┃┣SaveImageToFileWorker.kt // ファイル保存処理(workerクラス)
┃┗WorkerUtilis.kt // 共通処理
┣BlurActivity.kt // メインアクティビティ
┣BlurViewModel.kt // ビューモデル
┗Constants.kt // 定数

概要

イメージファイルにぼかし処理を行う。
処理は「一時ファイル削除」→「ぼかし処理を行い一時ファイルに保存」→「ファイル保存」の順
WorkManagerを用いてバックグラウンドで実行
制約として一連の処理は2重起動しない、ファイル保存時はバッテリー充電中のみ。
処理状況のステータスに応じて画面表示
処理完了後は出力イメージの参照ボタンを表示

Workerクラス

BlurWorker
WorkManagerクラスとしてWorkerクラスの子クラスを作成
doWorkメソッドにバックグラウンドで実行する処理を記載する
画像にぼかしを入れる処理
Result.success()で以降の処理の入力Dataを返す

/**
 * WorkManagerクラスとしてWorkerクラスの子クラスを作成
 * doWorkメソッドにバックグラウンドで実行する処理を記載する
 * 画像にぼかしを入れる処理
 * Result.success()で以降の処理の入力Dataを返す
 */
import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.text.TextUtils
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.example.background.KEY_IMAGE_URI

private const val TAG = "BlurWorker"
class BlurWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {

    // WorkManagerで実行する処理内容を記載
    override fun doWork(): Result {
        // WorkerオブジェクトからContextを取得
        val appContext = applicationContext

        // 対象のイメージファイルを取得
        // 1回目はapplyBlurで設定したDataオブジェクト
        // 2回以降はこのfunで設定したDataオブジェクト
        val resourceUri = inputData.getString(KEY_IMAGE_URI)

        // 処理状況を表示
        makeStatusNotification("Blurring image", appContext)

        // 動作確認のための一時停止
        sleep()

        return try {
            if (TextUtils.isEmpty(resourceUri)) {
                Log.e(TAG, "Invalid input uri")
                throw IllegalArgumentException("Invalid input uri")
            }

            // 入力イメージを開く
            val resolver = appContext.contentResolver
            val picture = BitmapFactory.decodeStream(
                    resolver.openInputStream(Uri.parse(resourceUri)))

            // ぼかしの入ったイメージに変換
            val output = blurBitmap(picture, appContext)

            // 出力イメージを一時ファイルに保存
            val outputUri = writeBitmapToFile(appContext, output)

            // 出力UriをDataオブジェクトにセット(以降の処理の入力Dataとして利用する)
            val outputData = workDataOf(KEY_IMAGE_URI to outputUri.toString())

            // outputDataをsuccessで返却
            Result.success(outputData)

        } catch (throwable: Throwable) {
            Log.e(TAG, "Error applying blur")
            throwable.printStackTrace()
            Result.failure()
        }
    }
}

CleanupWorker
WorkManagerクラスとしてWorkerクラスの子クラスを作成
doWorkメソッドにバックグラウンドで実行する処理を記載する
一時ファイルを削除する処理

import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.OUTPUT_PATH
import java.io.File

private const val TAG = "CleanupWorker"
class CleanupWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {

    override fun doWork(): Result {

        // 処理状況の表示(ContextはWorkerオブジェクトからを取得)
        makeStatusNotification("Cleaning up old temporary files", applicationContext)

        // 動作確認のための一時停止
        sleep()

        return try {
            // 出力ディレクトリの取得(ContextはWorkerオブジェクトからを取得)
            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) {
            exception.printStackTrace()
            Result.failure()
        }
    }
}

SaveImageToFileWorker
WorkManagerクラスとしてWorkerクラスの子クラスを作成
doWorkメソッドにバックグラウンドで実行する処理を記載する
イメージファイルとして画像を保存する処理
Result.success()でWorkManagerにDataオブジェクトを返す

/**
 * WorkManagerクラスとしてWorkerクラスの子クラスを作成
 * doWorkメソッドにバックグラウンドで実行する処理を記載する
 * イメージファイルとして画像を保存する処理
 * Result.success()でWorkManagerにDataオブジェクトを返す
 */

import android.content.Context
import android.graphics.BitmapFactory
import android.net.Uri
import android.provider.MediaStore
import android.util.Log
import androidx.work.workDataOf
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.example.background.KEY_IMAGE_URI
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

private const val TAG = "SaveImageToFileWorker"
class SaveImageToFileWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {

    private val Title = "Blurred Image"
    private val dateFormatter = SimpleDateFormat(
            "yyyy.MM.dd 'at' HH:mm:ss z",
            Locale.getDefault()
    )

    override fun doWork(): Result {

        // 処理状況の表示(ContextはWorkerオブジェクトからを取得)
        makeStatusNotification("Saving image", applicationContext)

        // 動作確認のための一時停止
        sleep()

        val resolver = applicationContext.contentResolver

        return try {
            // DataオブジェクトからURIを取得
            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, "Writing to MediaStore failed")
                Result.failure()
            }
        } catch (exception: Exception) {
            exception.printStackTrace()
            Result.failure()
        }
    }
}

レイアウトxml

1画面のみ
goButtonでワークマネージャーの処理を開始する
ファイル保存処理の状況に応じてボタンやProgressBarの表示切替

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/image_view"
            android:layout_width="match_parent"
            android:layout_height="400dp"
            android:layout_weight="1"
            android:src="@drawable/test"
            android:contentDescription="@string/description_image"
            android:scaleType="fitCenter" />

        <TextView
            android:id="@+id/filters_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/blur_title"
            android:textAppearance="@style/TextAppearance.AppCompat.Large" />


        <RadioGroup
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:id="@+id/radio_blur_group"
            android:checkedButton="@+id/radio_blur_lv_1">
            <RadioButton android:id="@+id/radio_blur_lv_1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/blur_lv_1" />
            <RadioButton android:id="@+id/radio_blur_lv_2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/blur_lv_2" />
            <RadioButton android:id="@+id/radio_blur_lv_3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/blur_lv_3" />
        </RadioGroup>

        <RadioGroup
            android:id="@+id/destinations"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

        </RadioGroup>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:orientation="horizontal">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
                <Button
                    android:id="@+id/cancel_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/cancel_work"
                    android:visibility="gone"
                    />

                <ProgressBar
                    android:id="@+id/progress_bar"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:indeterminate="true"
                    android:visibility="gone"
                    android:layout_gravity="center_horizontal"
                    />
            </LinearLayout>

            <Button
                android:id="@+id/go_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/go"
                />

            <Button
                android:id="@+id/see_file_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/see_file"
                android:visibility="gone"
                tools:visibility="visible" />

        </LinearLayout>

    </LinearLayout>

</ScrollView>

アクティビティ

1画面のみ
goButtonでワークマネージャーの処理を開始する
ファイル保存処理の状況に応じてボタンやProgressBarの表示切替

/**
 * アプリで唯一の画面
 * goButtonでワークマネージャーの処理を開始する
 * 処理状況に応じて画面表示
 */
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.work.WorkInfo
import com.example.background.databinding.ActivityBlurBinding

class BlurActivity : AppCompatActivity() {

    private val viewModel: BlurViewModel by viewModels { BlurViewModelFactory(application) }
    private lateinit var binding: ActivityBlurBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityBlurBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // ぼかし処理全体のキューをWorkManagerに登録する処理
        binding.goButton.setOnClickListener { viewModel.applyBlur(blurLevel) }

        // 処理完了(outputUri)が存在する時はその画像を表示
        binding.seeFileButton.setOnClickListener {
            viewModel.outputUri?.let { currentUri ->
                val actionView = Intent(Intent.ACTION_VIEW, currentUri)
                actionView.resolveActivity(packageManager)?.run {
                    startActivity(actionView)
                }
            }
        }

        // キャンセルボタン
        binding.cancelButton.setOnClickListener { viewModel.cancelWork() }

        // WorkManagerの処理状況を監視する
        viewModel.outputWorkInfos.observe(this, workInfosObserver())
    }

    // WorkManagerの処理状況に応じた画面状態にする
    private fun workInfosObserver(): Observer<List<WorkInfo>> {
        return Observer { listOfWorkInfo ->

            // WorkManagerが開始されるとlistOfWorkInfoに情報が入る
            if (listOfWorkInfo.isNullOrEmpty()) {
                return@Observer
            }

            // WorkManagerの実行情報を取得
            val workInfo = listOfWorkInfo[0]

            if (workInfo.state.isFinished) {
                // ボタン状態を処理完了にする
                showWorkFinished()

                // workInfoから保存処理SuccessのURI情報を取得
                val outputImageUri = workInfo.outputData.getString(KEY_IMAGE_URI)

                // ファイルを見るボタンにURIをセット
                if (!outputImageUri.isNullOrEmpty()) {
                    viewModel.setOutputUri(outputImageUri as String)
                    binding.seeFileButton.visibility = View.VISIBLE
                }
            } else {
                // ボタン状態を処理中にする
                showWorkInProgress()
            }
        }
    }

    /**
     * 保存処理中
     */
    private fun showWorkInProgress() {
        with(binding) {
            progressBar.visibility = View.VISIBLE
            cancelButton.visibility = View.VISIBLE
            goButton.visibility = View.GONE
            seeFileButton.visibility = View.GONE
        }
    }

    /**
     * 処理終了
     */
    private fun showWorkFinished() {
        with(binding) {
            progressBar.visibility = View.GONE
            cancelButton.visibility = View.GONE
            goButton.visibility = View.VISIBLE
        }
    }

    /**
     * ぼかしレベル
     */
    private val blurLevel: Int
        get() =
            when (binding.radioBlurGroup.checkedRadioButtonId) {
                R.id.radio_blur_lv_1 -> 1
                R.id.radio_blur_lv_2 -> 2
                R.id.radio_blur_lv_3 -> 3
                else -> 1
            }
}

ViewModel

WorkManagerのリクエストをキューに登録
リクエストは「一時ファイル削除」、ぼかし処理(3回まで)、ファイル保存の順
チェーンの2重起動しない(チェーン名にIMAGE_MANIPULATION_WORK_NAME)
ファイル保存処理は充電中の制約あり
初回のぼかし処理はアクティビティから取得したimageUriをインプット
2回目以降のぼかし処理とファイル保存処理のインプットはぼかし処理のResult.successでoutputを設定
ファイル保存処理のResult.successはワークマネージャーのステータスから利用する
チェーンのキャンセル処理あり

/**
 * WorkManagerのリクエストをキューに登録
 * リクエストは「一時ファイル削除」、ぼかし処理(3回まで)、ファイル保存の順
 * チェーンの2重起動しない(チェーン名にIMAGE_MANIPULATION_WORK_NAME)
 * ファイル保存処理は充電中の制約あり
 * 初回のぼかし処理はアクティビティから取得したimageUriをインプット
 * 2回目以降のぼかし処理とファイル保存処理のインプットはぼかし処理のResult.successでoutputを設定
 * ファイル保存処理のResult.successはワークマネージャーのステータスから利用する
 * チェーンのキャンセル処理あり
 */
import android.app.Application
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.example.background.workers.BlurWorker
import com.example.background.workers.CleanupWorker
import com.example.background.workers.SaveImageToFileWorker

class BlurViewModel(application: Application) : ViewModel() {

    // internalは同じモジュール内からアクセス可能
    internal var imageUri: Uri? = null
    internal var outputUri: Uri? = null

    // workManager
    private val workManager = WorkManager.getInstance(application)

    // WorkManagerの処理状況を格納
    internal val outputWorkInfos: LiveData<List<WorkInfo>>

    init {
        // workManagerで指定タグの処理状況を監視(保存処理に付けたタグを監視)
        outputWorkInfos = workManager.getWorkInfosByTagLiveData(TAG_OUTPUT)
        // 入力イメージのURIを取得(固定値)
        imageUri = getImageUri(application.applicationContext)
    }


    /**
     * 入力のDataオブジェクト
     * keyとValueペアのオブジェクト
     */
    private fun createInputDataForUri(): Data {
        // Dataビルダーオブジェクトを生成
        val builder = Data.Builder()
        // DataオブジェクトにimageUriをセット
        imageUri?.let {
            builder.putString(KEY_IMAGE_URI, imageUri.toString())
        }
        // Dataオブジェクトを返却
        return builder.build()
    }



    /**
     * WorkManagerでWorkRequestをキューに登録する
     */
    internal fun applyBlur(blurLevel: Int) {
        // WorkRequestを単独ではなくチェーンとして実行(先頭がCleanupWorker)
        // 処理チェーンに名前を付けて一度に複数のチェーン起動を避ける
        // ExistingWorkPolicyのオプションは次のいずれか(REPLACE,KEEP,APPEND)
        var continuation = workManager
            .beginUniqueWork(
                IMAGE_MANIPULATION_WORK_NAME,
                ExistingWorkPolicy.REPLACE,
                OneTimeWorkRequest.from(CleanupWorker::class.java)
            )

        // ぼかし処理を指定回数キューに登録
        for (i in 0 until blurLevel) {
            // ぼかし処理のworkManagerを定義
            val blurBuilder = OneTimeWorkRequestBuilder<BlurWorker>()

            if (i == 0) {
                // 初回処理の入力データをセット。引数はイメージuriのデータオブジェクトを渡す
                // 2回目以降の入力データはBlurWorker内で実施
                blurBuilder.setInputData(createInputDataForUri())
            }
            // ぼかし処理をキューに登録(指定回数繰り返す)
            continuation = continuation.then(blurBuilder.build())
        }

        // ファイル保存時に充電中の制約を付加
        val constraints = Constraints.Builder()
            .setRequiresCharging(true)
            .build()

        // 保存処理のworkManagerを定義
        // workInfo取得用のタグをセットする
        val save = OneTimeWorkRequestBuilder<SaveImageToFileWorker>()
            .setConstraints(constraints)
            .addTag(TAG_OUTPUT)
            .build()
        continuation = continuation.then(save)

        // Actually start the work
        continuation.enqueue()
    }

    // キャンセル処理
    internal fun cancelWork() {
        workManager.cancelUniqueWork(IMAGE_MANIPULATION_WORK_NAME)
    }

    private fun uriOrNull(uriString: String?): Uri? {
        return if (!uriString.isNullOrEmpty()) {
            Uri.parse(uriString)
        } else {
            null
        }
    }

    // 固定で入力イメージファイルのUriを出力
    private fun getImageUri(context: Context): Uri {
        val resources = context.resources
        val imageUri = Uri.Builder()
            .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
            .authority(resources.getResourcePackageName(R.drawable.test))
            .appendPath(resources.getResourceTypeName(R.drawable.test))
            .appendPath(resources.getResourceEntryName(R.drawable.test))
            .build()
        return imageUri
    }

    internal fun setOutputUri(outputImageUri: String?) {
        outputUri = uriOrNull(outputImageUri)
    }
}

// ViewModelのFactoryクラス
class BlurViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return if (modelClass.isAssignableFrom(BlurViewModel::class.java)) {
            BlurViewModel(application) as T
        } else {
            throw IllegalArgumentException("Unknown ViewModel class")
        }
    }
}

その他

@file:JvmName("Constants")

package com.example.background

// Notification Channel constants

// Name of Notification Channel for verbose notifications of background work
@JvmField val VERBOSE_NOTIFICATION_CHANNEL_NAME: CharSequence =
        "Verbose WorkManager Notifications"
const val VERBOSE_NOTIFICATION_CHANNEL_DESCRIPTION =
        "Shows notifications whenever work starts"
@JvmField val NOTIFICATION_TITLE: CharSequence = "WorkRequest Starting"
const val CHANNEL_ID = "VERBOSE_NOTIFICATION"
const val NOTIFICATION_ID = 1

// The name of the image manipulation work
const val IMAGE_MANIPULATION_WORK_NAME = "image_manipulation_work"

// Other keys
const val OUTPUT_PATH = "blur_filter_outputs"
const val KEY_IMAGE_URI = "KEY_IMAGE_URI"
const val TAG_OUTPUT = "OUTPUT"

const val DELAY_TIME_MILLIS: Long = 3000
@file:JvmName("WorkerUtils")

package com.example.background.workers

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.renderscript.Allocation
import androidx.renderscript.Element
import androidx.renderscript.RenderScript
import androidx.renderscript.ScriptIntrinsicBlur
import com.example.background.CHANNEL_ID
import com.example.background.DELAY_TIME_MILLIS
import com.example.background.NOTIFICATION_ID
import com.example.background.NOTIFICATION_TITLE
import com.example.background.OUTPUT_PATH
import com.example.background.R
import com.example.background.VERBOSE_NOTIFICATION_CHANNEL_DESCRIPTION
import com.example.background.VERBOSE_NOTIFICATION_CHANNEL_NAME
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.util.UUID


private const val TAG = "WorkerUtils"

// 処理状況をNotificationで表示する
fun makeStatusNotification(message: String, context: Context) {

    // Make a channel if necessary
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        val name = VERBOSE_NOTIFICATION_CHANNEL_NAME
        val description = VERBOSE_NOTIFICATION_CHANNEL_DESCRIPTION
        val importance = NotificationManager.IMPORTANCE_HIGH
        val channel = NotificationChannel(CHANNEL_ID, name, importance)
        channel.description = description

        // Add the channel
        val notificationManager =
                context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?

        notificationManager?.createNotificationChannel(channel)
    }

    // Create the notification
    val builder = NotificationCompat.Builder(context, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle(NOTIFICATION_TITLE)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setVibrate(LongArray(0))

    // Show the notification
    NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder.build())
}

/**
 * 3秒の停止
 */
fun sleep() {
    try {
        Thread.sleep(DELAY_TIME_MILLIS, 0)
    } catch (e: InterruptedException) {
        Log.e(TAG, e.message)
    }

}

/**
 * Blurs the given Bitmap image
 * @param bitmap Image to blur
 * @param applicationContext Application context
 * @return Blurred bitmap image
 */
@WorkerThread
fun blurBitmap(bitmap: Bitmap, applicationContext: Context): Bitmap {
    lateinit var rsContext: RenderScript
    try {

        // Create the output bitmap
        val output = Bitmap.createBitmap(
                bitmap.width, bitmap.height, bitmap.config)

        // Blur the image
        rsContext = RenderScript.create(applicationContext, RenderScript.ContextType.DEBUG)
        val inAlloc = Allocation.createFromBitmap(rsContext, bitmap)
        val outAlloc = Allocation.createTyped(rsContext, inAlloc.type)
        val theIntrinsic = ScriptIntrinsicBlur.create(rsContext, Element.U8_4(rsContext))
        theIntrinsic.apply {
            setRadius(10f)
            theIntrinsic.setInput(inAlloc)
            theIntrinsic.forEach(outAlloc)
        }
        outAlloc.copyTo(output)

        return output
    } finally {
        rsContext.finish()
    }
}

/**
 * Writes bitmap to a temporary file and returns the Uri for the file
 * @param applicationContext Application context
 * @param bitmap Bitmap to write to temp file
 * @return Uri for temp file with bitmap
 * @throws FileNotFoundException Throws if bitmap file cannot be found
 */
@Throws(FileNotFoundException::class)
fun writeBitmapToFile(applicationContext: Context, bitmap: Bitmap): Uri {
    val name = String.format("blur-filter-output-%s.png", UUID.randomUUID().toString())
    val outputDir = File(applicationContext.filesDir, OUTPUT_PATH)
    if (!outputDir.exists()) {
        outputDir.mkdirs() // should succeed
    }
    val outputFile = File(outputDir, name)
    var out: FileOutputStream? = null
    try {
        out = FileOutputStream(outputFile)
        bitmap.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, out)
    } finally {
        out?.let {
            try {
                it.close()
            } catch (ignore: IOException) {
            }

        }
    }
    return Uri.fromFile(outputFile)
}


トレーニング > KOTLIN を用いた ANDROID の基本 > ワークマネージャー > スケジュールタスク > ワークマネージャーでのバックグラウンド処理 > 3. アプリに WorkManager を追加する