关于服务
定义
Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。
类型
- 前台服务
- 后台服务
- 绑定服务
虽然分开概括讨论启动服务和绑定服务,但服务可同时以这两种方式运行,它既可以是启动服务(以无限期运行),亦支持绑定。唯一的问题在于实现一组回调方法:onStartCommand()(让组件启动服务)和 onBind()(实现服务绑定)。
如何创建服务
如要创建服务,必须创建 Service 的子类(或使用它的一个现有子类)。在实现中,必须重写一些回调方法,从而处理服务生命周期的某些关键方面,并提供一种机制将组件绑定到服务。
主要的回调方法:
- onStartCommand()
- onBind
- onCreate()
- onDestroy()
除了继承service实现上诉的几个重要回调之外,还需要在
清单文件中注册service:
<manifest ... >
...
<application ... >
<service android:name=".MyService" />
...
</application>
</manifest>
关于service标签,可以操作的属性有:
<service
//向用户描述服务的字符串
android:description="string resource"
//服务是否支持直接启动,即其是否可以在用户解锁设备之前运行。
//注:在直接启动期间,应用中的服务仅可访问存储在设备保护存储区的数据
android:directBootAware=["true" | "false"]
//系统是否可实例化服务 默认为“true”
//<application> 元素拥有自己的 enabled 属性,该属性适用于所有应用组件,包括服务
//<application> 和 <service> 属性都为“true”(因为它们都默认使用该值)时,系统才能启用服务
android:enabled=["true" | "false"]
//其他应用的组件是否能调用服务或与之交互,默认值取决于服务是否包含 Intent 过滤器,没有任何过滤器则为false
//当该值为“false”时,只有同一个应用或具有相同用户 ID 的应用的组件可以启动服务或绑定到服务
android:exported=["true" | "false"]
//阐明服务是满足特定用例要求的前台服务
android:foregroundServiceType=["connectedDevice" | "dataSync" |
"location" | "mediaPlayback" | "mediaProjection" |
"phoneCall"]
//表示服务的图标,如果未设置该属性,则转而使用为应用整体指定的图标
android:icon="drawable resource"
//如果设置为 true,则此服务将在与系统其余部分隔离的特殊进程下运行。此服务自身没有权限,只能通过 Service API 与其进行通信(绑定和启动)
android:isolatedProcess=["true" | "false"]
//可向用户显示的服务名称,如果未设置该属性,则转而使用为应用整体设置的标签
android:label="string resource"
//实现服务的 Service 子类的名称
android:name="string"
//实体启动服务或绑定到服务所必需的权限的名称如果 startService()、bindService() 或 stopService() 的调用者尚未获得此权限,该方法将不起作用,且系统不会将 Intent 对象传送给服务
//如果未设置该属性,则对服务应用由 <application> 元素的 permission 属性所设置的权限。如果二者均未设置,则服务不受权限保护
android:permission="string"
//将运行服务的进程的名称
android:process="string"
>
. . .
</service>
service的活动周期:
- 通过调用 startService() 启动服务
- 通过调用 bindService() 来创建服务,且未调用 onStartCommand()
关于系统可能停止service的情况:
- 只有在内存过低且必须回收系统资源以供拥有用户焦点的 Activity 使用时,Android 系统才会停止服务
- 如果将服务绑定到拥有用户焦点的 Activity,则它其不太可能会终止
- 如果将服务声明为在前台运行,则其几乎永远不会终止
- 如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升,如果服务是启动服务,则必须将其设计为能够妥善处理系统执行的重启
- 如果系统终止服务,则其会在资源可用时立即重启服务,但这取决于从 onStartCommand() 返回的值
onStartCommand()方法的可返回值:
- START_ NOT_STICKY
- START_STICKY
- START_ REDELIVER_INTENT
上面已经提到,创建服务除了继承Service之外,还可以直接使用它的一个现有子类,那么这个之类是谁呢?就是下面这位了...
IntentService
关于两者,他们适应不同的应用场景来实现服务:
- Service
- IntentService
可提供的特性:
- 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent
- 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现
- 在处理完所有启动请求后停止服务,因此永远不必调用 stopSelf()
- 提供 onBind() 的默认实现(返回 null)
- 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现
扩展实现IntentService需要注意的点:
- onStartCommand() 必须返回默认实现,从源码可见缘由:
- 实现onHandleIntent()
至此,我们已经知道如何创建服务,那么创建好的服务又应如何进行开启、停止和绑定呢?这就是接下来我们要解决的问题...
服务的开启、停止、绑定
服务的开启
可以通过将 Intent 传递给 startService() 或 startForegroundService(),从 Activity 或其他应用组件启动服务。Android 系统会调用服务的 onStartCommand() 方法,并向其传递 Intent,从而指定要启动的服务。
服务的停止
除非必须回收内存资源,否则系统不会停止或销毁服务,并且服务在 onStartCommand() 返回后仍会继续运行。服务必须通过调用 stopSelf() 自行停止运行,或由另一个组件通过调用 stopService() 来停止它。
一旦请求使用 stopSelf() 或 stopService() 来停止服务,系统便会尽快销毁服务。
如果服务同时处理多个对 onStartCommand() 的请求,则不应在处理完一个启动请求之后停止服务,因为可能已收到新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为避免此问题,可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。换言之,在调用 stopSelf(int) 时,您需传递与停止请求 ID 相对应的启动请求 ID(传递给 onStartCommand() 的 startId)。此外,如果服务在能够调用 stopSelf(int) 之前收到新启动请求,则 ID 不匹配,服务也不会停止。
服务的绑定
绑定服务允许应用组件通过调用 bindService() 与其绑定,从而创建长期连接。此服务通常不允许组件通过调用 startService() 来启动它。
如要创建绑定服务,您需通过实现 onBind() 回调方法返回 IBinder,从而定义与服务进行通信的接口。
服务只用于与其绑定的应用组件,因此若没有组件与该服务绑定,则系统会销毁该服务。不必像通过 onStartCommand() 启动的服务那样,以相同方式停止绑定服务。
在前台运行的服务
前台服务是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。前台服务必须为状态栏提供通知,将其放在运行中的标题下方。这意味着除非将服务停止或从前台移除,否则不能清除该通知。
在使用前台服务时,应注意以下事项:
创建前台任务,需要请求必要的权限:
创建前台服务的示例:
val pendingIntent: PendingIntent =
Intent(this, ExampleActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build()
//提供给 startForeground() 的整型 ID 不得为 0
startForeground(ONGOING_NOTIFICATION_ID, notification)
移除前台服务:
服务的生命周期
与 Activity 类似,服务也拥有生命周期回调方法,可通过实现这些方法来监控服务状态的变化并适时执行工作。以下展示了每种生命周期方法:
class MyService : Service() {
// 指示服务被终止时的行为
private var startMode: Int = 0
// 绑定客户端的接口
private var binder: IBinder? = null
// 指示是否应使用onRebind
private var allowRebind: Boolean = false
override fun onCreate() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 由于调用startService(),服务正在启动
return mStartMode
}
override fun onBind(intent: Intent): IBinder? {
// 客户端使用bindService()绑定到服务
return mBinder
}
override fun onUnbind(intent: Intent): Boolean {
// 所有客户端都与unbindService()解除绑定
return mAllowRebind
}
override fun onRebind(intent: Intent) {
// 在已经调用onUnbind()之后,客户端使用bindService()绑定到服务
}
override fun onDestroy() {
}
附上一张经典的service生命周期图示:
关于service的生命周期:
- 服务的整个生命周期贯穿调用 onCreate() 和返回 onDestroy() 之间的这段时间。与 Activity 类似,服务也在 onCreate() 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。
- 服务的活动生命周期从调用 onStartCommand() 或 onBind() 开始。每种方法均会获得 Intent 对象,该对象会传递至 startService() 或 bindService()。对于启动服务,活动生命周期与整个生命周期会同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,活动生命周期会在 onUnbind() 返回时结束。
Andoird8.0开始对后台Service限制
我们知道,每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,如果用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响会尤为明显。 所以,为了提升用户体验,从Android 8.0(API 级别 26)开始,对应用在后台运行时可以执行的操作施加了限制。
对于我们开发者而言,最直接的影响就是,应用处于空闲状态时,可以使用的后台 Service 被限制。 这些限制不适用于前台 Service,因为前台 Service 更容易引起用户注意。
那么什么是空闲状态呢?
空闲状态
什么时候应用被视为处于前台
- 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
- 具有前台 Service。
- 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。
创建前台Service方式变更
- Android 8.0 之前
- Android 8.0 开始
在系统增加限制的同时,当然也提供了其他的途径来帮助开发者解决这些限制带来的问题。在大多数情况下,应用都可以使用 JobScheduler 克服这些限制。 这种方法允许应用安排其在未活跃运行时执行工作,不过仍能够使系统可以在不影响用户体验的情况下安排这些作业。
可行的迁移方案
- 如果处于后台时,应用需要创建一个前台 Service,使用 startForegroundService() 方法,而非 startService()。
- 如果 Service 容易引起用户注意,将其设置为前台 Service。 例如,播放音频的 Service 始终应为前台 Service。 使用 startForegroundService() 方法创建 Service, 而非 startService()。
- 寻找一种使用计划作业实现 Service 功能的方式。 如果 Service 未在执行容易立即引起用户注意的操作,一般情况下,都能够使用计划作业。
- 在应用正常处于前台之前,请推迟后台工作。今年金九银十我花一个月的时间收录整理了一套知识体系,如果有想法深入的系统化的去学习的,可以点击传送门,我会把我收录整理的资料都送给大家,帮助大家更快的进阶。