[Android]Workerの複数起動を防ぐ

投稿者:

前回の記事でWorkerを複数起動するといろいろと弊害がありそうだと書いたが、今回はWorkerの状態を調べて複数起動を防ぐ方法を記す。

WorkInfoの取得

Workerの稼働状況はWorkInfoというオブジェクトに記録してあり、それをWorkManagerで取得することができる。

メソッド戻り値説明
getWorkInfoById(UUID)ListenableFuture<WorkInfo>IDでWorkInfoを取得
getWorkInfosByTag(tag)ListenableFuture<List<WorkInfo>>タグでWorkInfoを取得

戻り値はどちらもListenableFutureとなっていて、そのままではWorkInfoを利用できないのでListenableFutureからget()メソッドでWorkInfoやListを取り出してやらなければならない。なお、タグで取得するデータがListになっているのは、複数のWorkRequestに同じタグを設定できるためだ。

Workerの状態

Workerの状態はWorkInfoのstateプロパティで知ることができる。

状態説明
BLOCKED前提条件(制約)を満たしていないため、実行できない状態
CANCELLEDキャンセルされた状態
ENQUEUED前提条件が満たされており、実行可能状態でキューに追加されている状態
FAILED処理が失敗した状態
RUNNING処理が実行中
SUCCEEDED処理が正常終了した状態

定期実行の場合、処理の成功・失敗に関わらず、再スケジュールされてENQUEUED状態に移行するようなのでCANCELEDでなければ稼働中とみなしても良いと思われる。

サンプルプログラム

というわけで、サンプルアプリを作ってみた。
基本的には前回のサンプルと同じだが、Workerの稼働状況をチェックするメソッドを追加して、startWork()メソッドの最初で稼働中かどうか調べるようにしてみた

レイアウト
ActivityMain.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:orientation="vertical">

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="Worker Start"/>

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="Worker Stop"/>
    </LinearLayout>

</RelativeLayout>
JAVAファイル
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //定数
    final private static String LOG_TAG = "worker_sample";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button1 = findViewById(R.id.button1);
        button1.setOnClickListener(this);
        Button button2 = findViewById(R.id.button2);
        button2.setOnClickListener(this);
    }


    /* Workerを起動 */
    private void startWrok (int num) {
        //Wrokerが稼働中かチェック
        //  稼働中なら終了
        WorkManager manager = WorkManager.getInstance(this);
        if (isWorkerEnqueued(manager,SampleWorker.WORK_TAG)) {
            Log.d(LOG_TAG, "Worker already enqueued");
            return;
        }

        //Workerに渡すデータを作成
        Data data =  new Data.Builder()
                .putInt("num", num)
                .build();

        //制約(起動条件)を設定
        //  このサンプルでは制約なしでもいいが
        //  とりあえずバッテリー容量が少ないときはNGとした
        Constraints constraints = new Constraints.Builder()
                .setRequiresBatteryNotLow(true)
                .build();

        //WorkRequestの作成
        // PeriodicWorkRequestを使って20分毎に定期実行
        PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(
                SampleWorker.class, 20, TimeUnit.MINUTES)
                .setConstraints(constraints)
                .setInputData(data)
                .addTag(SampleWorker.WORK_TAG)
                .build();

        //キューに登録(スケジューリング)
        manager.enqueue(request);

        UUID uuid = request.getId();
        ListenableFuture<WorkInfo> future = manager.getWorkInfoById(uuid);

        Log.d(LOG_TAG, "Worker scheduled");

    }

    /* Workerを停止 */
    private void stopWork() {
        WorkManager manager = WorkManager.getInstance(this);
        manager.cancelAllWorkByTag(SampleWorker.WORK_TAG);
        Log.d(LOG_TAG, "Worker stopped");
    }

    /* ---------------------------------------------
        Workerの状態を取得する
        引数:workManager = WorKManagerのインスタンス
            tag = タグ
        戻値:true = 稼働中
            false = 非稼働
    ----------------------------------------------- */
    private boolean isWorkerEnqueued(WorkManager workManager, String tag) {
        ListenableFuture<List<WorkInfo>> future =  workManager.getWorkInfosByTag(tag);
        try {
            for (WorkInfo workInfo : future.get()){
                //workInfoからstateを取得して状態をチェック
                if (workInfo.getState() != WorkInfo.State.CANCELLED) {
                    //CANCELLED以外だったら稼働中とみなす
                    return true;
                };
            }
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
            return false;
        }
        return false;
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                startWrok(2);   //Workerを起動
                break;
            case R.id.button2:
                stopWork();     //Workerのキャンセル
                break;
            default:
                break;
        }
    }
}
SampleWorker.java
public class SampleWorker extends Worker {
    //定数
    final private static String LOG_TAG = "worker_sample";
    final public static String WORK_TAG = "SampleWorkerTAG";

    //コンストラクタ
    public SampleWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        //データを取得
        Data data = getInputData();
        int num = data.getInt("num", 1);

        //出力テキストの作成
        //numの個数だけ"Beautiful"というテキストを出力
        StringBuilder sb = new StringBuilder();
        for (int i=0; i<num; i++) {
            sb.append("Beautiful ");
        }
        sb.append(getId().toString());
        String output = sb.toString().trim();

        //出力
        Log.d(LOG_TAG,output);  //ログに出力
        return Result.success();
    }
}

前回と同じくアプリを起動すると次の画面が開く。

動作も前回と同じなのだが、一応書いておくと、「WORKER START」ボタンをクリックするとWorkerがスケジューリングされ、20分毎にデバッグ用ログが出力される。「WORKER STOP」ボタンをクリックするとスケジューリングされているWorkerがキャンセルされる。ログはアクティビティ上には表示されないため何も動いていないように見えるが裏ではログが吐き出されている。前回と違うのは「WORKER START」を連続でクリックしてもWrokerが複数起動しないところだ。

実行結果のログは次の通りになる。

15:35:27.930 3338-3338/net.fineblue206.workersample D/worker_sample: Worker scheduled
15:35:28.054 3338-6207/net.fineblue206.workersample D/worker_sample: Beautiful Beautiful 8dec5e07-142f-464e-bcc2-8b549007828a
15:35:37.050 3338-3338/net.fineblue206.workersample D/worker_sample: Worker already enqueued
15:37:56.502 3338-3338/net.fineblue206.workersample D/worker_sample: Worker already enqueued
15:55:28.155 3338-4975/net.fineblue206.workersample D/worker_sample: Beautiful Beautiful 8dec5e07-142f-464e-bcc2-8b549007828a
16:15:28.289 3338-6176/net.fineblue206.workersample D/worker_sample: Beautiful Beautiful 8dec5e07-142f-464e-bcc2-8b549007828a
16:23:16.707 3338-3338/net.fineblue206.workersample D/worker_sample: Worker stopped

「START WORK」でWorkerを動かした後に二回ボタンをクリックしているが、既に同じタグのWorkerがキューに登録されているので起動を中止して複数起動を防いでいる。そして最初に動かしたWorkerだけが20分間隔で実行されている。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください