Android プログラミング

フォアグラウンドサービスの最低限の実装について【Kotlin, Android Studio, バックグラウンドでのアプリ実行】

動画あります。

この記事の動画版ができました。応援してくださる方はぜひチャンネル登録をお願いします。

コトの発端

Android StudioでKotlinを使ってAndroidアプリを開発していた。

アプリを閉じてもバックグラウンドで実行し続けて欲しい時とかあるよね。

そんなときは実行中である旨を通知領域に表示することを条件に、バックグラウンドでの実行が許可されるフォアグラウンドサービス(Foreground Service)を使うよ。

 →サイレントなバックグラウンド実行はGoogle様の怒りに触れたので禁止になったよ。

フォアグラウンドサービスはいろいろと実装とか権限とかややこしいので、自分用のメモとして、最低限の実装をまとめておく。

フォアグランドサービスを初めて実装する初心者や入門者には優しい記事かもしれない。

前提

バックグラウンドの動作に制限がかかったのがAndroid 8.0(APIレベル 26)からなので、対象はそれ以降のバージョンとなる。

ただし、現時点最新のPreview版であるAndroid 12(APIレベル 31)からはフォアグラウンドサービスの起動には大きな制限がかかるようだ。

https://developer.android.com/about/versions/12/foreground-services?hl=ja

イメージとしてはAPI 26からAPI 30といったところか。

実装

マニフェスト

フォアグラウンドサービスを実行するためにはFOREGROUND_SERVICEのパーミッションが必要なのでAndroidManifest.xmlに追加。

Protection levelはnormalなのでユーザに許可を求める必要は無い様子。

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

https://developer.android.com/reference/android/Manifest.permission?hl=ja#FOREGROUND_SERVICE

それと、実行させるサービスと、通知領域からフォアグラウンドサービスを停止させるためのブロードキャストレシーバーを登録しておく。前提は以下の通り。

例)
サービス名:TestForegroundService
ブロードキャストレシーバー名:TestForegroundReceiver

applicationタグの間に追加

<service
    android:name=".TestForegroundService"
    android:enabled="true"
    android:exported="true"></service>
<receiver android:name=".TestForegroundReceiver" android:exported="true">
    <intent-filter>
        <action android:name="(ドメインとアプリ名)" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>

ブロードキャストレシーバー

通知領域からフォアグラウンドサービスを停止させるためのブロードキャストレシーバー を定義する。

TestForegroundReceiver.ktを新規作成し、下記のクラスを作成する。

class TestForegroundReceiver:BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val targetIntent = Intent(context, TestForegroundService::class.java)
        context.stopService(targetIntent)
    }
}

メインアクティビティー

必要最低限の実装。

起動したらonCreateのタイミングで即フォアグラウンドサービスを起動することにした。

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

        val intent = Intent(this,TestForegroundService::class.java)
        startForegroundService(intent)
    }

}

サービス

最後はメインのサービスを定義。

フォアグラウンドで実行させるサービスは下記の通り。

おおまかに、

 1.通知領域タップで戻ってくる先のActivity

 2.通知チャネル登録

 3.ブロードキャストレシーバーをPendingIntent化

 4.通知の作成(ここでPendingIntentを通知領域に渡す)

 5.フォアグラウンド開始。

通知チャネルのIDは任意の文字列、startForegroundの通知IDは任意の整数(ただし0以外)でいいっぽい。アプリ内で通知IDとかが重複しなければいいという認識だけど、どうだろ。

下記にコードを示す。

class TestForegroundService: Service(){
    companion object {
        const val CHANNEL_ID = "1111"
    }
    override fun onBind(p0: Intent?): IBinder? {
        return null
    }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i("Service","onStartCommand called")

        //1.通知領域タップで戻ってくる先のActivity
        val openIntent = Intent(this, MainActivity::class.java).let {
            PendingIntent.getActivity(this, 0, it, 0)
        }

        //2.通知チャネル登録
        val channelId = CHANNEL_ID
        val channelName = "TestService Channel"
        val channel = NotificationChannel(
            channelId, channelName,
            NotificationManager.IMPORTANCE_DEFAULT
        )
        val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        manager.createNotificationChannel(channel)

        //3.ブロードキャストレシーバーをPendingIntent化
        val sendIntent = Intent(this, TestForegroundReceiver::class.java).apply {
            action = Intent.ACTION_SEND
        }
        val sendPendingIntent = PendingIntent.getBroadcast(this, 0, sendIntent, 0)
        //4.通知の作成(ここでPendingIntentを通知領域に渡す)
        val notification = NotificationCompat.Builder(this, CHANNEL_ID )
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("フォアグラウンドのテスト中")
            .setContentText("終了する場合はこちらから行って下さい。")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(openIntent)
            .addAction(R.drawable.ic_launcher_foreground, "実行終了", sendPendingIntent)
            .build()

        //5.フォアグラウンド開始。
        startForeground(2222, notification)

        return START_STICKY
    }

    override fun stopService(name: Intent?): Boolean {
        return super.stopService(name)
    }
}

実行

アプリを起動すると、直後にフォアグラウンドサービスが起動し、通知領域にてフォアグラウンドサービスが実行されている旨が通知される。

アプリをキルしてもフォアグラウンドサービスはキルされず、通知領域の「実行終了」をタップすることでフォアグラウンドサービスが終了する。

参考にさせていただいたページ

Androidアプリで通知にボタンを追加して、タップ時にForeground Serviceを停止する

https://dev.classmethod.jp/articles/android-notification-action-stop-foreground-service/

サービスの概要

https://developer.android.com/guide/components/services?hl=ja

いつもの

記事の正確性については無保証です。

  • この記事を書いた人
あっきー

あっきー

とある企業の研究者。研究分野以外に手を出しすぎて毎日が慌ただしい。 研究者の肩書きが正しいかどうかは万年の謎。 得意ジャンルはデータベースとセキュリティーですが、AIやIoT、アプリ開発など、手広く活動しています。

-Android, プログラミング
-, ,

Translate »