Help us understand the problem. What is going on with this article?

Jobschedulerの基本

More than 1 year has passed since last update.

検証環境

この記事の内容は、以下の環境で検証した。
* Java:open jdk 1.8.0_152
* Kotlin 1.2.10
* Android Studio 3.0.2
* CompileSdkVersion:26

はじめに

今思えば、Oreoでサービスに関する変更があり、その布石だったJobSchedulerがAndroid 5.0(Lollipop:API Level21)で追加されいたんですね。この記事では、そんなJobSchedulerの最小限の実装方法を説明して行きます。

全体像

JobSchedulerが一体どういうものなのかを図にしてみました。

Untitled.png

図からわかるように、Serviceと違い条件が合致した時に実行されます。
また、ある一定期間でJobServiceなるものを繰り返し実行が可能です。

必要なクラス群

JobSchedulerを使用する上で必要となるクラス群とその役割は、以下のとおりです。

クラス名 役割
android.app.job.JobScheduler ジョブを管理します。登録されたジョブはIDで識別され、キャンセルなども行えます。
android.app.job.JobService 定期的に実行される処理を定義するクラス。
android.app.job.JobInfo Jobが動作する条件やJobServiceを指定したもの。ジョブそのものの情報が格納されています。
android.app.job.JobInfo.Builder JobInfoを生成するためのビルダークラス。

実装方法

全体像

まずは、サンプルで作成したアプリのコードを全て掲載します。
その後、詳細な説明をします。

AndroidManifest.xml

JobServiceもServiceを継承したものなので、AndroidManifest.xmlに登録する必要があります。
また、端末が再起動した時に、Jobを継続するために「uses-permissions」も追加しています。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.casareal.jobscheduler">

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".CasarealJobService"
            android:permission="android.permission.BIND_JOB_SERVICE"></service>
    </application>

</manifest>

レイアウトリソースファイル

JobSchedulerの登録ボタンとキャンセルボタンを保持した画面のレイアウトファイルです。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="jp.co.casareal.jobscheduler.MainActivity">

    <Button
        android:id="@+id/buttonJobStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Job開始!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="@+id/buttonJobCancel"
        />

    <Button
        android:id="@+id/buttonJobCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="52dp"
        android:text="Jobキャンセル!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonJobStart"
        app:layout_constraintVertical_bias="0.046" />

</android.support.constraint.ConstraintLayout>

サービス

Jobで実行する処理を定義しています。

CasarealJobService.kt
package jp.co.casareal.jobscheduler

import android.app.Notification
import android.app.NotificationManager
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Context
import android.util.Log

class CasarealJobService : JobService() {
    override fun onStopJob(params: JobParameters?): Boolean {

        jobFinished(params, false)
        return false
    }

    override fun onStartJob(params: JobParameters?): Boolean {

        Thread(
                Runnable {
                    val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
                    val notification = Notification.Builder(applicationContext)
                            .apply {
                                setContentTitle("JobSchedulerの通知")
                                setContentText("スケジュールで呼び出された処理で通知")
                                setSmallIcon(R.drawable.ic_launcher_background)
                            }.build()

                    manager.notify(1, notification)
                    Log.i(javaClass.name, "スケジュールしたジョブで呼び出された処理")

                    jobFinished(params,false)
                }
        ).start()

        return true
    }

}

アクティビティ

JobInfoを生成して、JobSchedulerに登録しています。

MainActivity.kt
package jp.co.casareal.jobscheduler

import android.app.job.JobInfo
import android.app.job.JobScheduler
import android.content.ComponentName
import android.content.Context
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    val jobId = 1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

    }

    override fun onResume() {
        super.onResume()
        val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler

        buttonJobStart.setOnClickListener {
            val componentName = ComponentName(this,
                    CasarealJobService::class.java)

            val jobInfo = JobInfo.Builder(jobId, componentName)
                    .apply {
                        setBackoffCriteria(10000, JobInfo.BACKOFF_POLICY_LINEAR);
                        setPersisted(false)
                        setPeriodic(0)
                        setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
                        setRequiresCharging(false)
                    }.build()
            scheduler.schedule(jobInfo)
        }

        buttonJobCancel.setOnClickListener {
            scheduler.cancel(jobId);

            // 下記の方法だとアプリで登録したすべてのJobがキャンセルされる
            scheduler.cancelAll()
        }
    }
}

詳細な説明

AndroidManifest.xml

AndroidManifestでJobSchedulerが影響している箇所の詳細について説明します。

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

端末が再起動しても、Jobを実行するには、「android.permission.RECEIVE_BOOT_COMPLETED」のパーミッションを得る必要があります。

<service
    android:name=".CasarealJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"></service>

JobServiceはServiceを継承しているため、実行するにはAndroidManifestに登録する必要があります。そして、android:permission属性に、「android.permission.BIND_JOB_SERVICE」を指定します。

サービス

サービスはこれまでにもあるように、android.app.job.JobServiceクラスを継承します。

class CasarealJobService : JobService() {}

JobServiceクラスを継承したクラスは、下記の2つのメソッドをオーバーライドしなければなりません。それぞれのメソッドの役割と戻りの違いについては表を確認してください。また、オーバーライドはしませんが、重要な「jobFinishedメソッド」があります。jobFinishedも併せて記述しておきます。

メソッド名 役割 戻り値
onStartJob 登録したジョブが実行されると呼び出されるメソッドです。このメソッドはメインスレッドで呼び出されるため、時間がかかる処理は別スレッドで実行します。 【trueの場合】
onStartJobメソッドが終了時点で処理が続いている場合は、trueを返します。もし、trueを返した場合は、処理の最後にjobFinishedメソッドを呼び出さなければなりません。
【falseの場合】
処理が継続していない場合に返す。
onStopJob 処理実行中に条件を満たせず、中断した場合に呼び出されるメソッドです。このメソッドの中でjobFinishedメソッドを呼び出す必要があります。 【trueの場合】
trueを設定するとback-offで設定した内容に併せてリスケジュールされます。
【falseの場合】
このジョブは終了します。
jobFinished ジョブを終了するためのメソッドです。2つの引数をとります。 -

アクティビティ

アクティビティでは、JobInfoを生成しています。
JobInfoを生成するにはJobInfo.Builderを使用します。

val componentName = ComponentName(this,
        CasarealJobService::class.java)

まず初めに、JobServiceを継承したクラスの情報を含むComponentNameを生成します。
JobInfoに設定するために事前に準備しておきます。

val jobInfo = JobInfo.Builder(jobId, componentName)
        .apply {
            setBackoffCriteria(5000, JobInfo.BACKOFF_POLICY_LINEAR);
            setPersisted(false)
            setPeriodic(1000)
            setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE)
            setRequiresCharging(false)
        }.build()

JobInfo.Builderクラスのコンストラクタの第1引数には、Job特定するためのIDを指定し、第2引数には実際のJobであるComponetNameのオブジェクトを渡します。

JobInfo.Builderクラスで設定可能なプロパティは多く存在しますが、ここでは使用頻度が高そうなものだけをピックアップしています。
プロパティのセッターメソッドとその説明は以下のとおりです。

プロパティ名 説明
setBackoffCriteria バックオフの計算ポリシーと基準時間を設定します。
setPersisted 端末が再起動した場合、Jobを継続するかどうかを設定します。
もし、trueを設定した場合は、「uses-permissions」で「RECEIVE_BOOT_COMPLETED」を指定します。
setPeriodic 定期実行する場合の間隔(ミリ秒)を設定します。
setRequiredNetworkType ネットワークに接続している必要があるか指定します。
setRequiresCharging 充電中している必要があるか指定します。

その他にも多くのプロパティが存在します。下記を参照してください。
https://developer.android.com/reference/android/app/job/JobInfo.Builder.html

val scheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler

JobSchedulerを取得しています。

scheduler.schedule(jobInfo)

JobSchedulerに登録し、実行します。

scheduler.cancel(jobId);

// 下記の方法だとアプリで登録したすべてのJobがキャンセルされる
scheduler.cancelAll()

JobScheduler#cancelで、 JobInfoに指定したID を利用してJobをキャンセルできます。
もし、すべてのJobをキャンセルするには、JobScheduler#cancelAllメソッドを呼び出します。

バックオフとは

JobSchedulerで出てくるBack-Offについて説明しておきます。
簡単に説明すると下記のイメージです。

BackOff.png

要するに、条件が合わないから、処理を後回し(Back-Off)することです。

まとめ

Oreoでバックグラウンド処理は、Serviceでは今まで通り動作しません。JobSchedulerを利用する必要がありますが、Serviceとは違い条件が一致するまで動作しないため、注意が必要そうです。

naoi
casareal
システム開発/評価・検証支援/品質改善支援サービスと現場に即した実践的なIT研修サービスを提供しています。
https://www.casareal.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away