【Android】サービスの概要とサンプルコード

今回はバックグラウンドで処理を実行できるサービスについての説明とサンプルコードをご紹介したいと思います。

## 環境
Android Studio：2020.3.1 Patch 4
kotlin：1.5.31
targetSdkVersion：31
minSdkVersion：27

## サービスとは
サービスとは Activity が終了した状態でもバックグラウンドで処理を継続して実行することができるアプリコンポーネントです。
Activity と異なり UI(ユーザーインターフェース)は持ちません。
用途としてはバックグラウンドでのサーバーとの通信、ファイル入出力、音楽の再生などが挙げられます。

## サービスの種類
サービスには次の３種類があります。

#### バックグラウンドサービス
通常のサービスです。
Activityが終了した後も何らかの処理を継続的に行うことができます。
アプリが終了したときは処理を停止します。

#### フォアグラウンドサービス
ユーザーが存在を認識できるサービスです。
Activity が終了した後だけでなくアプリが終了した後も動作し続けます。
サービスが動いている間は専用の通知を通知ウィジェットに表示することでユーザーにサービスが継続していることを認識させなければなりません。
また、この通知はサービスが終了するまで消すことはできません。

#### バインドされたサービス
Activity から操作したり Activity と情報のやり取りを行うことができたりするサービスです。
複数の Activity を1つのサービスにバインドさせて互いにやり取りすることも可能です。
1つのサービスにバインドしている全ての Activity が終了した場合は自動的にサービスも終了します。

今回は全ての基本となるバックグラウンドサービスのサンプルコードを実装していきたいと思います。

## サンプルコード
ここからはサンプルコードを書いていきたいと思います。
内容は Service の動きをログに表示するシンプルなものになります。

まずは Activity を作成します。

``` MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        buttonStart.setOnClickListener {
            val intent = Intent(this, SampleService::class.java)
            intent.putExtra("text", "hello world")
            startService(intent)

            buttonStart.isEnabled = false
            buttonStop.isEnabled = true
        }

        buttonStop.setOnClickListener {

            val intent = Intent(this, SampleService::class.java)
            stopService(intent)

            buttonStart.isEnabled = true
            buttonStop.isEnabled = false
        }
    }
}
```
```buttonStart``` を押した時に Intentクラスの引数に起動したい Serviceクラスの名前を指定し、```startService()``` で起動します。
また、Activity 間の遷移と同様に ```putExtra()``` メソッドで起動するサービスに値を引き渡すことができます。
今回は Activity から 「text」という名前で「hello world」という文字列をサービスに引き渡します。

```buttonStop``` を押した時には改めて Intentクラスを作成し、```stopService()``` でサービスを停止します。

レイアウトファイルはこちらになります。

```　activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent">

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/buttonStart"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:text="Start"
            android:enabled="true"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/buttonStop"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/buttonStop"
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:text="Stop"
            android:enabled="false"
            android:layout_marginTop="20dp"
            app:layout_constraintTop_toBottomOf="@+id/buttonStart"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
```

次に、Service を実装していきます。
まずActivityと同じように Manifest ファイルに作成する Service を記述する必要があります。

```
<service
    android:name=".SampleService"
    android:enabled="true"
    android:exported="false" />
```

以下は各属性の説明です。

```android:name```
唯一の必須属性です。
完全修飾名（パッケージ名を含めた名前）にする必要があります。
「.サービス名」の形で記述するとサービス名の先頭に manifest 要素に記述したパッケージ名を付与することになります。

```android:enabled```
サービスをシステムがインスタンスできるかどうか。
trueの場合はインスタンス化できて、false ならできません。
デフォルト値はtrue。

```android:exported```
他のアプリコンポーネントがサービスを呼び出せるか。
true なら呼び出せて、false なら呼び出せなません。
デフォルト値はマニフェストの ```service``` タグに ```intent-filter``` が含まれていれば true、含まれていなければ false です。

Manifest ファイルへの追加ができたら Service 専用のファイルを作って実装していきます。
実装するクラスは大元の Serviceクラスを継承する必要があります。

``` SoundManagerService.kt
class SampleService : Service() {

    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }

    override fun onCreate() {
        super.onCreate()
        Log.d("onCreate", "onCreate")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("onStartCommand", "onStartCommand")

        intent?.getStringExtra("text")?.let {
            Log.d("intent", it)
        }

        return START_NOT_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("onDestroy", "onDestroy")
    }

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        Log.d("onTaskRemoved", "onTaskRemoved")
    }
}
```

```startService()``` で Service が起動されると ```onCreate()``` → ```onStartCommand()``` の順で呼び出されます。
    
Serviceで実現したい処理の多くは ```onStartCommand()``` メソッドの中に記述します。
また、このメソッドの第一引数には Activity から送られた intent が入ります。
ここでは「text」という名前で「hello world」という文字列を Activity から受け取っています。

また、このメソッドは整数を返す必要があります。
この整数は、システムがサービスを強制終了した後の処理を定義するもので、```START_NOT_STICKY```, ```START_STICKY```, ```START_REDELIVER_INTENT``` のいずれかから選択します。
これらの定数の説明は以下のリンクをご確認ください。

https://developer.android.com/guide/components/services?hl=ja#ExtendingService
 
今回は アプリが終了した後にサービスを常に動作させ続ける必要がないため、```START_NOT_STICKY``` を指定しています。

```onDestroy()``` は ```stopService()``` でサービスが終了した時に呼び出されます。

```onTaskRemoved()``` はユーザーがアプリをタスクキルした時に呼び出されます。

なお、```onBind()```　はバインドされたサービスを作成する際に呼ばれるメソッドですが、Activity をサービスにバインドしない場合も必ず記述する必要があります。

実装は以上です。
```buttonStart``` を押すとログに「onCreate」→「onStartCommand」の順で表示され、その後　Activity から引き継いだ「hello world」が表示されます。
```buttonStop``` を押すと「onDestory」が表示されてサービスが終了します。

なお、アプリをタスクキルした時は ```onDestory``` は呼ばれず、代わりに ```onTaskRemoved``` が呼び出されます。
タスクキルした時に何か処理を行いたければ ```onTaskRemoved``` に書くのが良いでしょう。


## 参考
[サービスの概要  \|  Android デベロッパー  |  Android Developers](https://developer.android.com/guide/components/services?hl=ja#StartingAService)
基礎&応用力をしっかり育成! Androidアプリ開発の教科書 第2版 Kotlin対応 なんちゃって開発者にならないための実践ハンズオン (CodeZine BOOKS)(著：WINGSプロジェクト 齊藤 新三、監修：山田 祥寛)