Android Oreoでサービスを使ってみる

## 始めに
Androidではバックグラウンドで処理したい場合serviceを使うことになります。
serviceについて調べて簡単なサンプルコードを作成してみます。

## サービスの形式
サービスには2つの形式があり、呼び出すメソッドが異なります。

・開始されたサービス
`startService()`で開始します。
開始されたサービスは自ら停止させるまで動き続きます。
呼び出し元に結果を戻すことはなく、処理が実行されるのみです。

・バインドされたサービス
`bindService()`でバインドします。
バインドされたサービスには要求を送信したり、結果を取得したりなど制御が可能です。
ただし、呼び出し元のActivityが終了すると一緒に終了されます。

## サービスのコールバックメソッド
`onCreate()`
　最初に一度呼ばれるので、初期化処理を行います。
`onStartCommand()`
　サービスで実行する処理を書きます。
`onDestroy()`
　サービスの終了処理
`onBind()`
　`bindService()`で呼び出した場合、呼び出せれます。

## Android Oreoでのサービス
[バックグラウンド実行制限](https://developer.android.com/about/versions/oreo/background#services)
Android Oreo(8.0 / API Level 26)からバックグラウンド処理に制限が追加されました。
そのため、上記の`startService`の代わりに新しく導入された`startForegroundService()`
を使います。
(実際にはSDKのバージョンによって処理を分ける必要があります)

`startForegroundService()`を使う場合、色々と仕様がありました。
[Android Oからのバックグラウンド・サービスの制限事項を実演する。](https://qiita.com/nukka123/items/791bc4f9043764789ee6)
こちらでユースケースを分かりやすくまとめてくださっていたので、参考にさせていただきました。

## ソース
スタートボタンを押してサービス開始、ストップボタンでサービス停止します。
サービス開始10秒後にログ出力するので、その前にアプリをバックグラウンドにしたりして確認してみます。

### サービスクラスをManifestに追記
```xml:AndroidManifest.xml
<service android:name=".TestService"/>
```

### MainActivity (ボタンの設定のみ)
```java:MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startButton = findViewById(R.id.button_start);
        Button stopButton = findViewById(R.id.button_stop);

        // サービス開始
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent serviceIntent = new Intent(getApplication(), TestService.class);
                startForegroundService(serviceIntent);
            }
        });

        // サービス停止
        stopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent serviceIntent = new Intent(getApplication(), TestService.class);
                stopService(serviceIntent);
            }
        });
    }
}
```
### サービスクラス
```java:TestService.java
public class TestService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("DEBUG", "called TestService.onCreate()");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("DEBUG", "called TestService.onStartCommand()");
        String channelId = "service";
        String title = "TestService";

        // 通知設定
        NotificationManager notificationManager =
                (NotificationManager)getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel channel =
                new NotificationChannel(channelId, title, NotificationManager.IMPORTANCE_DEFAULT);

        if(notificationManager != null) {
            notificationManager.createNotificationChannel(channel);
            Notification notification = new Notification.Builder(getApplicationContext(), channelId)
                    .setContentTitle(title)
                    .setSmallIcon(R.drawable.menu)
                    .setContentText("service start")
                    .build();

            // フォアグラウンドで実行
            startForeground(1, notification);

            // 10秒後にログ出力
            try {
                Thread.sleep(10000);
                Log.i("INFO", "processing service");
            } catch (InterruptedException e) {
                e.printStackTrace();

            }
        }

        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("DEBUG", "called TestService.onDestroy()");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
```


## ハマったところ
サービス開始するとクラッシュが繰り返し起こる事象が発生しました。
調べてみたところ、`startForeground()`で引数に渡す通知の設定が原因でした。
Android8.0だと`Notification.setSmallIcon()`でAdaptive iconを指定してはいけないらしいです。
私は`R.mipmap.ic_launcher`を指定してクラッシュしました。
回避のため適当な画像を指定するように修正しています。
[Android 8.0 で通知を表示すると System UI がクラッシュする](https://qiita.com/kawmra/items/9d80f15ea906f703d0d3)

## まとめ
簡単にサービスの動きを検証してみました。(検証方法がちょっとすっきりしませんでしたが、、)
バージョンによって実装方法も異なるうえに、色々と制限も厳しいのでうまく活用するにはもっと勉強が必要そうです。

以下は参考とさせていただいたサイトです。
[[Android] Service の使い方](https://akira-watson.com/android/service.html)
[Android Oreo: 通知とサービスのフォアグラウンド実行]
(https://rakuishi.com/archives/android-oreo-notification-foreground/)
[サービス | Android Developers](https://developer.android.com/guide/components/services?hl=ja)