検証環境
この記事の内容は、以下の環境で検証した。
- Java:open jdk 1.8.0_152
- Kotlin 1.2.10
- Android Studio 3.0.2
- CompileSdkVersion:26
はじめに
Android 8.0であるOreoからバックグラウンドに関して、制限がかかってきます。
その制限から既存のServiceを救う方法の記事です。
Foreground Serviceとは・・・
Foreground Serviceとは、通常のサービスと違い、通知(Notification)を表示し、バックグラウンドで実行している事をユーザに認識させた状態で実行するものです。
処理自体はMainスレッドとは別スレッドで実行されているが、ユーザが通知を通じ認識できるため、Foreground Serviceとなります。
完成イメージ
Foreground Serviceか開始してから終わるまで通知は消せません。
サービスの終了とともに通知を消すことも可能です。
このサービスは内部で5秒スリープして、スリープ後に通知を消すサンプルです。
実装方法
実装方法がminSdkVersionによって異なりますが、
共通していけることは、サービスを実行するonStartCommandメソッド内で
通知(Notification)を表示させます。
そして、その通知をstartForegroundメソッドで呼び出すことで
ユーザが認識できる「Foreground Service」になります。
実装方法は以下の通りです。
レイアウト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.foregroundservicekotlin.MainActivity">
<Button
android:id="@+id/buttonServiceStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_start_service"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
minSdkVersionが26未満の場合
Activityクラス
サービスの起動方法は今まで通り。
package jp.co.casareal.foregroundservicekotlin
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import jp.co.casareal.foregroundservicekotlin.service.ForegroundService
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonServiceStart.setOnClickListener {
val serviceIntent = Intent(this, ForegroundService::class.java)
startService(serviceIntent)
}
}
}
Serviceクラス
サービスクラスの全体像は以下の通り。
package jp.co.casareal.foregroundservicekotlin.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.support.v4.app.NotificationCompat
import jp.co.casareal.foregroundservicekotlin.R
class ForegroundService : Service() {
override fun onBind(intent: Intent): IBinder? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this).apply {
mContentTitle = "通知のタイトル"
mContentText = "通知の内容"
setSmallIcon(R.mipmap.ic_launcher)
}.build()
Thread(
Runnable {
(0..5).map {
Thread.sleep(1000)
}
stopForeground(true)
// もしくは
// stopSelf()
}).start()
startForeground(1, notification)
return START_STICKY
}
}
コードの説明
①Notificationの作成
サービスが起動している最中の通知を作成しています。
val notification = NotificationCompat.Builder(this).apply {
mContentTitle = "通知のタイトル"
mContentText = "通知の内容"
setSmallIcon(R.mipmap.ic_launcher)
}.build()
②startForegroundメソッド
通知を表示させます。Foreground Serviceにするには、startForegroundメソッドを呼び出します。
第1引数には通知のIDを指定します。第2引数に、表示するNotificationのオブジェクトを指定します。
このことからわかるようにNotificationManagerは使用しません。
通知のIDに0を指定すると、通知が表示されないので注意してください。
startForeground(1, notification)
③ stopForegroundメソッド
サービスの処理が終了する際に、stopForegroundメソッドを呼び出します。
引数には、Boolean型をしていします。 true の場合、呼び出しと同時に通知を削除します。 falseの場合は、通知が残ります。
stopForeground(true)
④ stopSelfメソッド
stopSelfを呼び出すことでサービスを終了します。その場合、通知は削除されます。
minSdkVersionが26以上の場合
Activityクラス
サービスを呼び出す時のメソッドが異なります。
package jp.co.casareal.foregroundserviceoreo
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import jp.co.casareal.foregroundserviceoreo.service.ForegroundService
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonServiceStart.setOnClickListener {
val serviceIntent = Intent(this, ForegroundService::class.java)
startForegroundService(serviceIntent)
}
}
}
コードの説明
①startForegroundServiceメソッド
Foreground Serviceとしてサービスを起動するには、startForegroundServiceメソッドでサービスを起動します。
5秒以内に起動したサービスクラスでstartForegroundメソッドを呼び出さないとANRになります。
startForegroundService(serviceIntent)
Serviceクラス
package jp.co.casareal.foregroundserviceoreo.service
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.support.v4.app.NotificationCompat
import jp.co.casareal.foregroundserviceoreo.R
class ForegroundService : Service() {
override fun onBind(intent: Intent): IBinder? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val name = "通知のタイトル的情報を設定"
val id = "casareal_foreground"
val notifyDescription = "この通知の詳細情報を設定します"
if (manager.getNotificationChannel(id) == null) {
val mChannel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH)
mChannel.apply {
description = notifyDescription
}
manager.createNotificationChannel(mChannel)
}
val notification = NotificationCompat.Builder(this,id).apply {
mContentTitle = "通知のタイトル"
mContentText = "通知の内容"
setSmallIcon(R.drawable.ic_launcher_background)
}.build()
Thread(
Runnable {
(0..5).map {
Thread.sleep(1000)
}
stopForeground(Service.STOP_FOREGROUND_DETACH)
}).start()
startForeground(1, notification)
return START_STICKY
}
}
コードの説明
①stopForegroundメソッド
26未満との差分としては、引数に渡せる定数が追加されています。
引数の種類と詳細は以下の通りです。
定数 | 詳細 |
---|---|
Service.STOP_FOREGROUND_REMOVE | 26未満でtrueと渡した時と似たような動きをします。既に同じ通知がある場合は、通知を削除して新しい通知を表示します。 |
Service.STOP_FOREGROUND_DETACH | 通知とサービスを切り離します。動きとしては26未満でfalseを渡した時と似たような動きをします。 |
②Notifaicaiton
詳細は下記の記事を参照してください。
ターゲットを28以上にする場合(Pie以降)
Pie(API Level 28)から更にForeground Serviceについて変更が発生しています。
ターゲットのAPI Levelが28以降の場合は、Manifestファイルに下記を追記する必要があります。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
まとめ
Foreground ServiceとしてServiceを起動すにはどうやらNotificationが必要です。Notificationの知識もしっかりしておかないといつか痛い目にあいそうです。