はじめに
この記事で説明した通り、様々な機能を活用していきましょう。
WorkManagerのキューに追加して、処理を実行下はいいけど、何かしらのタイミングや条件で処理をキャンセルしたい場合があります。
また、Workの状態を何かしらの条件を使いたいなんてことも大いにあります。
今回は、状態の確認方法やキャンセルの方法を説明していきます。
動作環境
この記事の動作環境は以下のとおりです。
- Android Studio:3.3
- Kotln:1.3.11
- Open JDK:1.8
- compileSdkVersion:28
- targetSdkVersion:28
- minSdkVersion:19
目標
- キューに追加したWorkerの情報を取得する
- Workerの状態を確認する
- Workerをキャンセルする
説明
Workerの保持する情報
定期的に実行する処理はWorkerクラスを継承して定義していきます。
このWorkerクラスは、様々な情報を保持しています。
その中に、UUID(UUIDとは、重複のない一意のユニークなIDを取得するためのクラス)やタグがあります。
UUIDやタグはキューに追加したWorkerを取得する際に使用します。
イメージにするとこんな感じになります。
WorkRequestに情報を追加する
キューに登録したWorkerを取得するには、WorkRequestのオブジェクトを生成するタイミングでタグ設定、WorkRequestのオブジェクト生成時に作られたUUDIを取得しておく必要があります。
タグを設定するには、Builderクラスで行います。
val periodicWork = PeriodicWorkRequest.Builder(
MyWorker::class.java,
Duration.ofMinutes(15)
).apply {
addTag(workerTag)
}.build()
UUIDを取得するには、Builderでビルドしたあとに取得します。
private var workerId: UUID? = null
workerId = periodicWork.id
これらを取得することによってキューに追加されてもWorkerクラスのオブジェクトの情報が取得できます。
キューに追加したWorkerの情報を取得する
キューについかしたWorkerの情報は先に記述した通り、タグとUUIDを利用してうけとります。
WorkManagerクラスのメソッドを利用します。
メソッド名 | 引数 | 戻り値 |
---|---|---|
getWorkInfoById | UUID | ListenableFuture |
getWorkInfosByTag | String | ListenableFuture> |
戻り値の方がどちらも ListenableFutureとなっています。
このクラスはGoogleが用意したAPIですので、詳細はAPIを確認してください。
そのため、これらのメソッドで取得したままではWorkInfoは利用できないので、ListenableFutureからWorkInfoやListを取得する必要があります。
イメージとしては、下図のようになります。
それぞれのメソッドで取得するコードの例も確認してみます。
val info = manager.getWorkInfoById(workerId!!)
val infoByTags = manager.getWorkInfosByTag(workerTag)
Workerの状態を確認する
getWorkInfoByXXXで取得したWorkInfoにWorkの状態が格納されています。状態はstateプロパティとして格納されています。
状態はWorkクラス内にEnumで宣言されています。どの様な状態があるか確認します。
WorkInfo.State
状態 | 説明 |
---|---|
BLOCKED | 前提条件を満たしていないため、実行出来ていない状態を表します。 |
CANCELLED | キャンセルされた状態を表します。関連しているWorkRequestもキャンセルと扱われ、関連するWorkRequestも実行されません。※実行の関連は、別の記事で紹介します。 |
ENQUEUED | 前提条件が満たされており、実行可能状態でキューについかされている状態を表します。 |
FAILED | 処理が失敗したことを表します。関連しているWorkRequestも失敗と扱われ、関連するWorkRequestも実行されません。※実行の関連は、別の記事で紹介します。 |
RUNNING | 処理が実行中であることを表します。 |
SUCCEEDED | 処理が正常終了したことを表します。PeriodicWorkRequest(定期的に実行)の場合は、この状態にはならず、ENQUEUEDに遷移し、再度実行されます。 |
説明からもわかるように、stateプロパティは実行状態によって変化します。
また、WorkRequstの種類によっても異なります。
フロー自体は正確ではありませんが、状態のイメージを掴むためにフロー図にしてみました。
しっかりと状態が確認できれば、何故動いていないかなどのデバッグに役立つと思います。
実際にコードでも確認しておきましょう。
val info = manager.getWorkInfoById(workerId!!)
Toast.makeText(this, "State:${info.get().state}", Toast.LENGTH_LONG).show()
val infoByTags = manager.getWorkInfosByTag(workerTag)
infoByTags.get().forEach {
Toast.makeText(this, "State:${it.state}", Toast.LENGTH_LONG).show()
}
キューに追加したWorkRequestキャンセルする
キューにWorkRequestをキャンセルするには、キューに登録したWorkRequestのIDやタグを指定してキャンセルします。WorkManagerのメソッドをキャンセルします。
キャンセルするWorkRequestを特定するための情報によって、メソッド名が異なります。
メソッド一覧(一部抜粋)
メソッド名 | 引数 | 説明 |
---|---|---|
cancelAllWork | なし | 未完了のWorkRequestをすべてキャンセルします。しかし、アプリに悪影響を及ぼし、意図しない不具合が発生する可能性があります。このメソッドはおすすめしません。(公式サイトでおすすめされていません。) |
cancelAllWorkByTag | タグ名:String | タグが指定されているWorkRequestをすべてキャンセルします。すでに実行中の場合は、継続する可能性があります。また、関連するWorkRequestでは、Workerクラスを継承したクラス内にオーバーライドしたonStoppedメソッドが呼ばれます。 |
cancelWorkById | WorkのID:UUID | 基本的な動作は「cancelAllWorkByTag」と同じです。唯一の違いは、UUIDで取得できたWorkをキャンセルします。 |
実装としては、Workコードでも確認しておきます。
manager.cancelWorkById(workerId!!)
manager.cancelAllWorkByTag(workerTag)
実装コード
この記事で動作確認したコードをすべて掲載します。
AndroidManifest.xmlはテンプレートで作成された状態から変更していないため、省略します。
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "jp.co.casareal.workmanagerstateandcancel"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
def work_version = "1.0.0"
implementation "android.arch.work:work-runtime-ktx:$work_version"
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Workスタート"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintHorizontal_bias="0.498" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="188dp"
android:layout_marginBottom="51dp" app:layout_constraintBottom_toTopOf="@+id/btnState"/>
<Button
android:text="状態表示"
android:layout_width="158dp"
android:layout_height="47dp"
android:id="@+id/btnState"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintTop_toBottomOf="@+id/btnStart"
app:layout_constraintBottom_toTopOf="@+id/btnCancel" app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintVertical_bias="0.586"/>
<Button
android:text="キャンセル"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btnCancel"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp" app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp" android:layout_marginBottom="140dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.498"
android:layout_marginTop="107dp" app:layout_constraintTop_toBottomOf="@+id/btnState"/>
</android.support.constraint.ConstraintLayout>
package jp.co.casareal.workmanagerstateandcancel
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.support.v4.app.NotificationCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import java.lang.Exception
import java.text.SimpleDateFormat
class MyWorker(cxt: Context, params: WorkerParameters) : Worker(cxt, params) {
val notificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// カテゴリー名(通知設定画面に表示される情報)
val name = "通知のタイトル的情報を設定"
// システムに登録するChannelのID
val id = "casareal_chanel"
// 通知の詳細情報(通知設定画面に表示される情報)
val notifyDescription = "この通知の詳細情報を設定します"
private val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
companion object {
var nid = 1;
}
init {
// Channelの取得と生成
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationManager.getNotificationChannel(id) == null
val mChannel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH)
mChannel.apply {
description = notifyDescription
}
notificationManager.createNotificationChannel(mChannel)
}
}
override fun doWork(): Result {
for (i in 0..3) {
val notification = NotificationCompat.Builder(applicationContext, id).apply {
setContentText("${nid}回目のメッセージ:${simpleDateFormat.format(System.currentTimeMillis())}")
setSmallIcon(R.drawable.ic_launcher_background)
}
notificationManager.notify(MyWorker.nid, notification.build())
MyWorker.nid++
}
return Result.success()
}
}
package jp.co.casareal.workmanagerstateandcancel
import android.os.Build
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import androidx.work.Constraints
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import kotlinx.android.synthetic.main.activity_main.*
import java.time.Duration
import java.util.*
class MainActivity : AppCompatActivity() {
private val manager = WorkManager.getInstance()
private var workerId: UUID? = null
private val workerTag = "myworktag"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnStart.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val periodicWork = PeriodicWorkRequest.Builder(
MyWorker::class.java,
Duration.ofSeconds(20)
).apply {
addTag(workerTag)
}.build()
workerId = periodicWork.id
manager.enqueue(periodicWork)
}
}
btnState.setOnClickListener {
// キューに追加したWorkerの取得
val info = manager.getWorkInfoById(workerId!!)
Toast.makeText(this, "State:${info.get().state}", Toast.LENGTH_LONG).show()
val infoByTags = manager.getWorkInfosByTag(workerTag)
infoByTags.get().forEach {
Toast.makeText(this, "State:${it.state}", Toast.LENGTH_LONG).show()
}
}
btnCancel.setOnClickListener {
manager.cancelWorkById(workerId!!)
manager.cancelAllWorkByTag(workerTag)
}
}
}
まとめ
この記事では以下が理解できました。