Android O(API 26)からBackground Serviceの実行が制限されてForegroundで実行する必要があります。
Activityが実行している場合、問題ないだろうが、BroadcastReceiverのようForegroundではない状態で、Background Serviceを実行させることはなくなりました。
こういうときは、サービスをForegroundで実行する必要があります。 Foregroundで実行させると、サービスが実行中であるコンテンツのNotificaitonが登録され、ユーザーが認知することができます。
この記事では、Foregroundでサービスを実行させる方法について説明します。
この記事で紹介されたSample AppはGitHub - StartForegroundServiceで確認することができます。 この記事の例では、Kotlinで作成されました。
Service定義
次のように MyService
というサービスクラスを実装し、AndroidManifestに定義したと仮定します。
class MyService : Service() {
override fun onCreate() {
super.onCreate()
Log.d("Test", "MyService is started")
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
<application>
...
<service android:name=".MyService" />
</application>
Foreground Service実行
Android O(API 26)未満のバージョンでは、 startService()
でサービスを実行することができたが、Android O以上のデバイスでは、サービスがBackgroundで実行されるときに実行されていません。このとき startForegroundService()
でサービスをForegroundで実行する必要があります。
したがって、次のようなサービスを実行することができます。
val intent = Intent(context, MyService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
FOREGROUND_SERVICEパーミッション
Foregroundでサービスを実行するには、 FOREGROUND_SERVICE
パーミッションをAndroidManifestに登録する必要があります。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
startForegroundService()でNotification登録
startForegroundService()
でサービスが実行されると、実行されたサービスは、5秒以内に startForeground()
を呼び出して、サービスが実行中であるNotificaitonを登録する必要があります。もし呼び出さない場合、システムはサービスを強制的に終了させます。
Service.startForeground()
は次のように Notificaiton ID
とNotificaiton
オブジェクトを引数として受け取ります。
注意すべき点は、IDで0を渡すと、動作がないということです。 0以外の数字をIDで渡す必要があります。
public final void startForeground(int id, Notification notification)
次のようにNotificaiton ChannelとNotificationオブジェクトを作成し、 startForeground()
を呼び出すことができます。
class MyService : Service() {
companion object {
const val NOTIFICATION_ID = 10
const val CHANNEL_ID = "primary_notification_channel"
}
override fun onCreate() {
super.onCreate()
Log.d("Test", "MyService is started")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("MyService is running")
.setContentText("MyService is running")
.build()
Log.d("Test", "start foreground")
startForeground(NOTIFICATION_ID, notification)
}
}
private fun createNotificationChannel() {
val notificationChannel = NotificationChannel(
CHANNEL_ID,
"MyApp notification",
NotificationManager.IMPORTANCE_HIGH
)
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.RED
notificationChannel.enableVibration(true)
notificationChannel.description = "AppApp Tests"
val notificationManager = applicationContext.getSystemService(
Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(
notificationChannel)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
Android O(API 26)以上のバージョンでNotificationを登録する前に、Notificaiton Channelを登録する必要があります。
テスト
サービスを実行してみると、サービスが実行され、次のようにNotifciatonが見えることを確認することができます。
Troubleshooting
FOREGROUND_SERVICE
権限をアプリに追加しない場合は、次のようなエラーでアプリが終了します。
12-24 12:58:32.783 3688 3688 E AndroidRuntime: FATAL EXCEPTION: main
12-24 12:58:32.783 3688 3688 E AndroidRuntime: Process: com.example.app, PID: 3688
12-24 12:58:32.783 3688 3688 E AndroidRuntime: java.lang.RuntimeException: Unable to create service com.example.example.MyService: java.lang.SecurityException: Permission Denial: startForeground from pid=3688, uid=10153 requires android.permission.FOREGROUND_SERVICE
startForegroundService()
の呼び出し後、実行されたサービスで、5秒以内に startForeground()
を呼び出していない場合、システムによってサービスが終了します。
12-24 11:53:19.793 504 530 W ActivityManager: Bringing down service while still waiting for start foreground: ServiceRecord{21733d5 u0 com.example.app/.MyService}
12-24 11:53:19.912 504 3971 I ActivityManager: Crashing app skipping ANR: ProcessRecord{1bdf9c0 3660:com.example.app/u0a153} Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{21733d5 u0 com.example.app/.MyService}
参考
Related Posts
- Android - FusedLocationProviderClientに位置情報を取得する
- Android - GPS、Network位置情報を取得する(LocationManager)
- Android - adbコマンドでActivity実行
- アンドロイド - MediaStoreにメディアファイルを保存する方法
- Android - Runtime permissionリクエスト方法と例(kotlin)
- Android11 - Storage(ストレージ)の変更まとめ
- Jetpack Compose - RowとColumn
- Android 13 - 細かいメディアファイルの権限
- Android 13でNotification権限をリクエスト、通知を表示する
- エラー解決:android gradle plugin requires java 11 to run. you are currently using java 1.8.
- Query method parameters should either be a type that can be converted into a database column or a List
- Android - TabLayoutの実装方法(+ ViewPager2)
- Android - adbコマンドで特定のパッケージのプロセスの終了
- Android - adb push、pullでファイルのコピー、ダウンロード
- Android - adbコマンドでsettings value確認、変更、
- Android 12 - IntentFilterのexported明示的な宣言
- Android - adb logcatコマンドでログ出力
- Android - ACTION_BOOT_COMPLETEDイベント受信
- Android - Foreground Service実行
- Android - ファイル入出力の例(Read、Write、内部、外部ストレージ)
- Android - アプリの権限を確認(Permission check)
- Android - adbで実行中のプロセス、スレッドリスト及びメモリ情報の確認
- Android - Broadcast Receiver登録およびイベントの受信方法
- Android - Cleartext HTTP ... not permitted例外解決方法
- Androidのビルドエラー - Calls to Java default methods are prohibited in JVM target 1.6
- アンドロイド - Assetsでファイルを読み取る方法
- アンドロイドのさまざまなNotification種類と実装方法
- アンドロイド - INSTALL_FAILED_TEST_ONLYエラー解決方法
- Android EspressoのCustom Matcher実装方法
- Android Espressoを使用してUIをテストする方法(3)
- アンドロイド - CTS hostsideをgradleで構築する方法
- Androidのアプリのデータフォルダーのパスと内部/外部ストレージ説明
- アンドロイド - 最初のApp作成
- Androidをインストールする方法(Windows)