Service概念
Service是Android中的四大组件之一,和Activity一样继承自Context,但是Service没有UI界面,是可以在后台运行的应用组件。
分类
Service包括有不同的类型:前台Service,后台Service,绑定Service。
-
前台Service:前台服务可以执行一些用户能够注意到的操作,例如音频播放器可以使用前台服务来播放音频文件,前台服务会显示通知,提示用户当前的服务正在执行。
-
后台Service:后台Service可以在应用后台执行用户不会直接注意到的操作。
在Android 8.0之后的版本,系统对运行在后台的服务进行了限制,在部分情况下也可以使用JobScheduler推进后台工作进行。
需要注意的是,创建好的Service会在其托管的主线程中运行,不会去主动自己的线程,也不会在单独的进程中运行(除非在注册时指定独立进程)。因此,如果在Service中存在有耗时操作的话,则需要在Service中创建单独的线程来执行,否则会有ANR风险。
一、基本使用
1. 自定义Service类
首先我们先创建一个MainService类继承自Service,代码如下:
class MainService : Service() {
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
}
}
上述代码中的四个方法基本上就是Service的核心方法了,在这些方法中能够对服务进行绑定,处理生命周期等操作,下面我们对这几个方法进行简单说明。
1. onCreate()
首先是onCreate方法,onCreate方法会在Service被首次创建时执行,在StartCommand()
和onBind()
方法之前执行,执行一些Service启动之后的动作。
如果Service已经在运行状态了,那么即使再去调用startService()
方法也不会再执行。
2. onStartCommand()
当其他的组件执行启动Service时,通过调用startService()
方法来调用起此方法。
此方法执行后,Service会处在运行状态,在服务完成工作之后,我们可以主动去执行stopService()
来停止服务。
如果是要实现另外的组件和当前Service绑定,则此方法可以不用实现,而是实现onBind()
方法。
每次执行startService()方法都会调用到此方法。
需要注意的是此方法需要一个返回值,用来描述系统终止服务的情况下系统应该如何继续服务,返回值需要是如下的几个常量之一:
-
START_NOT_STICKY:被杀后不重启,不保持启动状态,可以随时停止
这种模式是和启动一些在内存压力大时能够被停止的任务,如定时数据轮询。
-
START_STICKY:被杀后自动重启,保持启动状态,不保持Intent,会重新调用onStartCommand方法,无新Intent传入的话则为空Intent;杀死重启后,不继续执行先前的任务,能够接收新任务。
-
START_REDELIVER_INTENT:如果存在未处理完的Intent,则被杀死后会重启,并在重启后发送所有的Intent,stopSelf后释放持有的Intent;如果没有尚未处理完的Intent的,服务不会重启。
-
START_STICKY_COMPATIBILITY:START_STICKY的通用选项,该选项不能保证,服务被杀死后onStartCommand会被重新调用
3. onBind()
如果要实现其他组件和当前Service进行绑定,我们可以通过bindService()
来进行绑定,同时此方法会被调用。
如果不需要绑定Service,此方法可以返回null。
4. OnDestory()
当服务可以被销毁时,此方法会被执行。
此方法是服务终止前的最后一个调用,因此需要在此方法内对Service内的资源进行释放,防止出现不安全隐患。
2. 声明
在创建了Service文件之后,我们Service还需要在AndroidManifest.xml
文件中进行声明注册,在注册之后才能够进行使用,和Activity等其他组件是一样的,代码如下:
<manifest ... >
...
<application ... >
<service android:name=".service.MainService"/>
...
</application>
</manifest>
Service可以在声明的额外的设置其他的属性,我们对常用的进行简单的说明:
android:exported="false"
此属性在四大组件中都可以设置,其作用是:是否支持其它应用调用当前组件。true为支持,false为不支持。
默认值:如果包含有intent-filter 默认值为true; 没有intent-filter默认值为false。
android:process=":xxx"
此属性是执行当前的组件运行在独立的进程中,进程名为xxx.
3. 服务启动
我们可以通过调用startService()
来启动服务,由于在Android8.0之后,系统对于后台服务有很大的限制,所以可以调用startForgroundService()
来创建一个服务并在前台执行,需要注意的是,如果执行了此方法,则需要在5s内在onCreate()
方法中执行startService()
方法,否则可能会ANR。
示例如下,我们首先创建一个服务MainService
,代码如下:
package com.example.demowork1.service
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.example.common.util.LogUtil
class MainService : Service() {
private val serviceID = 101
override fun onCreate() {
LogUtil.d("MainService onCreate")
super.onCreate()
setForegroundService()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
LogUtil.d("MainService onStartCommand")
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent?): IBinder? {
LogUtil.d("onBind")
return null
}
override fun onDestroy() {
LogUtil.d("onDestroy")
super.onDestroy()
}
/**
* 将service设置为前台服务
*/
private fun setForegroundService() {
val channelId = "DemoWork1MainService"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
var notificationChannel =
manager.getNotificationChannel(channelId)
if (notificationChannel == null) {
notificationChannel =
NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH)
notificationChannel.vibrationPattern = LongArray(0)
notificationChannel.setSound(null, null)
manager.createNotificationChannel(notificationChannel)
}
}
val builder =
NotificationCompat.Builder(this, channelId)
builder.setSound(null)
builder.setVibrate(longArrayOf())
startForeground(serviceID, builder.build())
}
}
如上述代码,在OnCreate()
方法中执行了startForground()
方法,则我们可以在调用时执行如下代码打开Service:
var intent = Intent()
intent.setClass(this,MainService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
}
else{
startService(intent)
}
在启动Service时可以采用intent传输数据到Service中。
4. 服务停止
如果组件通过调用 startService(intent)
(每次执行此方法都会调用 onStartCommand()
)启动服务,则服务会一直运行,直到其使用 stopSelf()
自行停止运行,或由其他组件通过调用 stopService()
将其停止为止,而后系统会将服务销毁。
Service的回收规则如下:
- 如果将服务绑定到拥有前台焦点的 Activity,则它其不太可能会终止
- 被声明为前台运行的服务,也基本上不会被终止
- 如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升
二、绑定服务
如果组件通过调用 bindService()
来创建服务,且不去调用 onStartCommand()
,则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁。
绑定示例代码如下,首先我们先创建绑定的Service——BinderService,需要对其的onBind()
和onUnbind()
方法进行重写,逻辑如下:
package com.example.demowork1.service
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import com.example.common.util.LogUtil
import kotlin.random.Random
class BinderService : Service() {
private var binder: MyBinder = MyBinder()
override fun onCreate() {
super.onCreate()
LogUtil.d("BinderService onCreate")
}
override fun onDestroy() {
LogUtil.d("BinderService onDestroy")
super.onDestroy()
}
override fun onBind(intent: Intent): IBinder {
LogUtil.d("BinderService onBind")
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
LogUtil.d("BinderService onUnbind")
return super.onUnbind(intent)
}
fun getRandomNumber():Int{
return Random(10).nextInt()
}
}
class MyBinder : Binder() {
fun getService(): BinderService {
return BinderService()
}
}
然后我们在Activity中进行绑定,首先我们需要定义一个ServiceConnection
来连接绑定,如下:
private val conn: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
isBound = true
val myBinder = binder as MyBinder
binderService = myBinder.getService()
LogUtil.d("ActivityA onServiceConnected")
val num: Int? = binderService?.getRandomNumber()
LogUtil.d("ActivityA 中调用 TestService的getRandomNumber方法, 结果: $num")
}
override fun onServiceDisconnected(name: ComponentName) {
isBound = false
LogUtil.d("ActivityA onServiceDisconnected")
}
}
然后我们在Activity中定义绑定和解除绑定的方法,剩余代码如下:
class ServiceActivity : BaseActivity<ActivityServiceBinding>() {
private var binderService: BinderService? = null
private var isBound = false
...//ServiceConnection
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding.btnOpenService.setOnClickListener {
startService()
}
viewBinding.btnStopService.setOnClickListener {
stopService()
}
viewBinding.btnBinderService.setOnClickListener {
binderService()
}
viewBinding.btnUnBinderService.setOnClickListener {
unBinderService()
}
}
/**
* 绑定服务
*/
private fun binderService() {
var bindIntent = Intent(this, BinderService::class.java)
bindService(bindIntent, conn, Context.BIND_AUTO_CREATE)
}
/**
* 解除Service绑定
*/
private fun unBinderService() {
unbindService(conn)
}
...
override fun initViewBinding() {
viewBinding = ActivityServiceBinding.inflate(layoutInflater)
}
}
生命周期
Service的生命周期主要是包括OnCreate()
和OnDestory()
,无论是启动服务还是绑定服务这两个生命周期回调方法一定会走到。
上一张经典的Service的生命周期图:

三、前台服务
前台服务是可以被用户注意到的服务,会在状态栏出显示通知,以便用户能够看到你的应用程序正在前台执行任务,如果不需要服务被用户注意到,可以使用WorkManager等后台任务来实现。
在Android8.0之后对于后台服务不再支持,启动服务时需要指定为前台服务。
1. 启动前台服务
和startService(Intent intent)
方法类似,启动前台Service使用startForgroundService(Intent intent)
方法,代码演示见启动服务章节,对于Android系统在8.0之上需要调用启动前台服务的方法。
2. 注意事项
启动前台服务需要注意如下事项:
-
在启动了前台Service服务之后,还需要在Service的
onCreate
方法中执行startForeground(serviceID, builder.build())
方法,否则可能会直接ANR。 -
需要注意的是如果系统的版本是Android 9 或者更高的版本需要为应用添加前台服务权限,否则会报错,权限项如下:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
总结
本文为Service基本概念和使用的复习总结。
网友评论