启动前台服务
前台服务可以给用户提供界面上的操作。
每个前台服务都必须要在通知栏显示一个通知(notification)。用户可以感知到app的前台服务正在运行。
这个通知(notification)默认是不能移除的。服务停止后,通知会被系统移除。
当用户不需要直接操作app,app需要给用户一个状态显示的时候,可以用前台服务。
市面上的app,例如各类音乐app
本文针对Android 8(Oreo,SDK_INT 26)及以后的版本。
使用说明
本例会使用1个Activity和1个Service。演示如何启动前台服务,停止服务。
manifest
在manifest里注册ForegroundDemoAct
和ForegroundService1
。并且申请权限FOREGROUND_SERVICE
。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rustfisher.tutorial2020">
<!-- 前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application ... >
<service android:name=".service.foreground.ForegroundService1" />
<activity
android:name=".service.foreground.ForegroundDemoAct"
android:launchMode="singleTop" />
</application>
</manifest>
Activity的启动模式我们选择了singleTop
。是为了方便演示点击通知时候的跳转效果。
启动前台服务
在activity中启动服务,调用startForegroundService(Intent)
方法。
startForegroundService(Intent(applicationContext, ForegroundService1::class.java))
然后在service中,需要对应地使用startForeground
方法。
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand flags:$flags, startId:$startId [$this] ${Thread.currentThread()}")
val pendingIntent: PendingIntent =
Intent(this, ForegroundDemoAct::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val chanId = "f-channel"
val chan = NotificationChannel(chanId, "前台服务channel",
NotificationManager.IMPORTANCE_NONE)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
Log.d(TAG, "服务调用startForeground")
val notification: Notification =
Notification.Builder(applicationContext, chanId)
.setContentTitle("RustFisher前台服务")
.setContentText("https://an.rustfisher.com")
.setSmallIcon(R.drawable.f_zan_1)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
} else {
Log.d(TAG, "${Build.VERSION.SDK_INT} < O(API 26) ")
}
return super.onStartCommand(intent, flags, startId)
}
我们来看service里的这段代码。创建了一个简单的Notification
。
- PendingIntent会被分配给Notification,作为点击通知后的跳转动作
- 使用NotificationManager先创建了一个NotificationChannel
- 用Notification.Builder配置并创建一个Notification,例如配置标题,内容文字,图标等
- 启动前台服务,调用
startForeground(1, notification)
方法
在设备上会显示出一个通知(以OnePlus5为例)
点击这个通知,会跳转到ForegroundDemoAct。这是之前用PendingIntent设置的。
停止服务
可以用stopService
来停止服务
stopService(Intent(applicationContext, ForegroundService1::class.java))
这样Service退出,走onDestroy
方法。
停止前台服务
在Service中调用stopForeground(boolean)
方法,能停止前台,但是不退出整个服务。
这个boolean表示是否取消掉前台服务的通知。false表示保留通知。
例如在Service中调用
stopForeground(false)
服务变成了后台服务,并没有退出。此时对应的通知可以滑动取消掉。
报错信息
ANR
在Activity中调用startForegroundService(Intent)
启动服务,但是不调用Service.startForeground()
。
一加5手机Android10运行log如下
2021-08-26 23:03:25.352 25551-25551/com.rustfisher.tutorial2020 D/rustAppUseStartService: 调用 startForegroundService 主线程信息Thread[main,5,main]
2021-08-26 23:03:25.368 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onCreate Thread[main,5,main] rustfisher.com
2021-08-26 23:03:25.370 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onStartCommand flags:0, startId:1 [com.rustfisher.tutorial2020.service.foreground.ForegroundService1@c77d408] Thread[main,5,main]
2021-08-26 23:03:35.375 1596-1720/? W/ActivityManager: Bringing down service while still waiting for start foreground: ServiceRecord{53d70f2 u0 com.rustfisher.tutorial2020/.service.foreground.ForegroundService1}
2021-08-26 23:03:35.382 25551-25551/com.rustfisher.tutorial2020 D/rustAppForeground1: onDestroy [com.rustfisher.tutorial2020.service.foreground.ForegroundService1@c77d408] Thread[main,5,main]
2021-08-26 23:03:52.956 1596-1720/? E/ActivityManager: ANR in com.rustfisher.tutorial2020
PID: 25551
Reason: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{53d70f2 u0 com.rustfisher.tutorial2020/.service.foreground.ForegroundService1}
Bad notification
我们在ForegroundService1的方法onStartCommand
里加入startForeground
。
如果startForeground(0, noti)
的id传入0,则会报错RemoteServiceException
。
29871-29871/com.rustfisher.tutorial2020 E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rustfisher.tutorial2020, PID: 29871
android.app.RemoteServiceException: Bad notification for startForeground
小结
- 通过
startForegroundService()
启动前台服务,必须在Service中有startForeground()
,否则会ANR,或者崩溃 startForeground()
中的id
不能为0,notification
不能为null- 服务可以退出前台
stopForeground