本文主要简单介绍一下关于service的一些基本概念,以及运用service的方法和新版系统对于软件使用service的一些限制等。内容主要基于Andorid官方文档。java
Service 是一种可在后台执行长时间运行操做而不提供界面的应用组件。服务可由其余应用组件启动,并且即便用户切换到其余应用,服务仍将在后台继续运行。此外,组件可经过绑定到服务与之进行交互,甚至是执行进程间通讯 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。android
执行一些用户能注意到的操做。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。即便用户中止与应用的交互,前台服务仍会继续运行。安全
后台服务执行用户不会直接注意到的操做。例如,若是应用使用某个服务来压缩其存储空间,则此服务一般是后台服务。服务器
当应用组件经过调用 bindService() 绑定到服务时,服务即处于绑定状态。绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通讯 (IPC) 跨进程执行这些操做。仅当与另外一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但所有取消绑定后,该服务即会被销毁。网络
虽然分开归纳讨论启动服务和绑定服务,但服务可同时以这两种方式运行,它既能够是启动服务(以无限期运行),亦支持绑定。惟一的问题在于实现一组回调方法:onStartCommand()(让组件启动服务)和 onBind()(实现服务绑定)。app
如要建立服务,必须建立 Service 的子类(或使用它的一个现有子类)。在实现中,必须重写一些回调方法,从而处理服务生命周期的某些关键方面,并提供一种机制将组件绑定到服务。ide
当另外一个组件(如 Activity)请求启动服务时,系统会经过调用 startService() 来调用此方法。执行此方法时,服务即会启动并可在后台无限期运行。若是实现此方法,则在服务工做完成后,需经过调用 stopSelf() 或 stopService() 来中止服务。(若是只想提供绑定,则无需实现此方法。)性能
当另外一个组件想要与服务绑定(例如执行 RPC)时,系统会经过调用 bindService() 来调用此方法。在此方法的实现中,必须经过返回 IBinder 提供一个接口,以供客户端用来与服务进行通讯。务必实现此方法;可是,若是并不但愿被绑定,则应返回 null。ui
首次建立服务时,系统会(在调用 onStartCommand() 或 onBind() 以前)调用此方法来执行一次性设置程序。若是服务已在运行,则不会调用此方法。this
当再也不使用服务且准备将其销毁时,系统会调用此方法。服务应经过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。
除了继承service实现上诉的几个重要回调以外,还须要在
<manifest ... >
...
<application ... >
<service android:name=".MyService" />
...
</application>
</manifest>
复制代码
<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>
复制代码
这会引发对 onStartCommand() 的调用,则服务会一直运行,直到其使用 stopSelf() 自行中止运行,或由其余组件经过调用 stopService() 将其中止为止
服务只会在该组件与其绑定时运行。当该服务与其全部组件取消绑定后,系统便会将其销毁
若是系统在 onStartCommand() 返回后终止服务,则除非有待传递的挂起 Intent,不然系统不会重建服务。这是最安全的选项,能够避免在没必要要时以及应用可以轻松重启全部未完成的做业时运行服务。
若是系统在 onStartCommand() 返回后终止服务,则其会重建服务并调用 onStartCommand(),但不会从新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,不然系统会调用包含空 Intent 的 onStartCommand()。在此状况下,系统会传递这些 Intent。此常量适用于不执行命令、但无限期运行并等待做业的媒体播放器(或相似服务)。
若是系统在 onStartCommand() 返回后终止服务,则其会重建服务,并经过传递给服务的最后一个 Intent 调用 onStartCommand()。全部挂起 Intent 均依次传递。此常量适用于主动执行应当即恢复的做业(例以下载文件)的服务。
上面已经提到,建立服务除了继承Service以外,还能够直接使用它的一个现有子类,那么这个之类是谁呢?就是下面这位了...
关于二者,他们适应不一样的应用场景来实现服务:
这是适用于全部服务的基类。扩展此类时,必须建立用于执行全部服务工做的新线程,由于服务默认使用应用的主线程,这会下降应用正在运行的任何 Activity 的性能。
这是 Service 的子类,其使用工做线程逐一处理全部启动请求。若是不要求服务同时处理多个请求,此类为最佳选择。实现 onHandleIntent(),该方法会接收每一个启动请求的 Intent,以便执行后台工做。
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
复制代码
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
复制代码
至此,咱们已经知道如何建立服务,那么建立好的服务又应如何进行开启、中止和绑定呢?这就是接下来咱们要解决的问题...
能够经过将 Intent 传递给 startService() 或 startForegroundService(),从 Activity 或其余应用组件启动服务。Android 系统会调用服务的 onStartCommand() 方法,并向其传递 Intent,从而指定要启动的服务。
至API 26开始,系统对开启服务有了严格的限制:
若是应用面向 API 级别 26 或更高版本,除非应用自己在前台运行,不然系统不会对使用或建立后台服务施加限制。若是应用须要建立前台服务,则其应调用 startForegroundService()。此方法会建立后台服务,但它会向系统发出信号,代表服务会将自行提高至前台。建立服务后,该服务必须在五秒内调用本身的 startForeground() 方法。
除非必须回收内存资源,不然系统不会中止或销毁服务,而且服务在 onStartCommand() 返回后仍会继续运行。服务必须经过调用 stopSelf() 自行中止运行,或由另外一个组件经过调用 stopService() 来中止它。
一旦请求使用 stopSelf() 或 stopService() 来中止服务,系统便会尽快销毁服务。
若是服务同时处理多个对 onStartCommand() 的请求,则不该在处理完一个启动请求以后中止服务,由于可能已收到新的启动请求(在第一个请求结束时中止服务会终止第二个请求)。为避免此问题,可使用 stopSelf(int) 确保服务中止请求始终基于最近的启动请求。换言之,在调用 stopSelf(int) 时,您需传递与中止请求 ID 相对应的启动请求 ID(传递给 onStartCommand() 的 startId)。此外,若是服务在可以调用 stopSelf(int) 以前收到新启动请求,则 ID 不匹配,服务也不会中止。
绑定服务容许应用组件经过调用 bindService() 与其绑定,从而建立长期链接。此服务一般不容许组件经过调用 startService() 来启动它。
如要建立绑定服务,您需经过实现 onBind() 回调方法返回 IBinder,从而定义与服务进行通讯的接口。
服务只用于与其绑定的应用组件,所以若没有组件与该服务绑定,则系统会销毁该服务。没必要像经过 onStartCommand() 启动的服务那样,以相同方式中止绑定服务。
前台服务是用户主动意识到的一种服务,所以在内存不足时,系统也不会考虑将其终止。前台服务必须为状态栏提供通知,将其放在运行中的标题下方。这意味着除非将服务中止或从前台移除,不然不能清除该通知。
在使用前台服务时,应注意如下事项:
只有当应用执行的任务需供用户查看(即便该任务未直接与应用交互)时,才应使用前台服务。所以,前台服务必须显示优先级为 PRIORITY_LOW 或更高的状态栏通知,这有助于确保用户知道应用正在执行的任务。若是某操做不是特别重要,于是想使用最低优先级通知,则可能不适合使用服务;相反,能够考虑使用Sceduler。
建立前台任务,须要请求必要的权限:
若是应用面向 Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 FOREGROUND_SERVICE 权限。这是一种普通权限,所以,系统会自动为请求权限的应用授予此权限。
若是面向 API 级别 28 或更高版本的应用试图建立前台服务但未请求 FOREGROUND_SERVICE,则系统会抛出 SecurityException。
建立前台服务的示例:
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)
复制代码
移除前台服务:
stopForeground (boolean removeNotification) // 是否同时移除通知
此方法不会中止服务。可是,若是服务仍运行于前台时将其中止,则通知也会随之移除。
与 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的生命周期:
咱们知道,每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,若是用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响会尤其明显。 因此,为了提高用户体验,从Android 8.0(API 级别 26)开始,对应用在后台运行时能够执行的操做施加了限制。
须要注意的是:
默认状况下,这些限制仅适用于适配 Android 8.0(API 级别 26)或更高版本的应用。 然而,即便应用适配的 API 级别低于 26,用户也能够从设置界面,为任意应用启用其中大多数限制。
不会对绑定 Service 产生任何影响。 若是应用定义了绑定 Service,则无论应用是否处于前台,其余组件均可以绑定到该 Service。
IntentService 是一项 Service,所以其遵照针对后台 Service 的新限制。 所以,许多依赖 IntentService 的应用在适配 Android 8.0 或更高版本时没法正常工做。 出于这一缘由,Android 支持库 26.0.0 引入了一个新的JobIntentService类,该类提供与 IntentService 相同的功能,但在 Android 8.0 或更高版本上运行时使用做业而非 Service。
对于咱们开发者而言,最直接的影响就是,应用处于空闲状态时,可使用的后台 Service 被限制。 这些限制不适用于前台 Service,由于前台 Service 更容易引发用户注意。
那么什么是空闲状态呢?
处于前台时,应用能够自由建立和运行前台与后台 Service。 进入后台时,在一个持续数分钟的时间窗内,应用仍能够建立和使用 Service。 在该时间窗结束后,应用将被视为处于空闲状态。 此时,系统将中止应用的后台 Service,就像应用已经调用 Service 的 Service.stopSelf() 方法同样。
建立前台 Service 的方式一般是先建立一个后台 Service,而后将该 Service 推到前台
系统不容许后台应用建立后台 Service。 所以,Android 8.0 引入了一种全新的方法,即 startForegroundService(),以在前台启动新 Service。 在系统建立 Service 后,应用有五秒的时间来调用该 Service 的 startForeground() 方法以显示新 Service 的用户可见通知。 若是应用在此时间限制内未调用 startForeground(),则系统将中止此 Service 并声明此应用为 ANR。
在系统增长限制的同时,固然也提供了其余的途径来帮助开发者解决这些限制带来的问题。在大多数状况下,应用均可以使用 JobScheduler 克服这些限制。 这种方法容许应用安排其在未活跃运行时执行工做,不过仍可以使系统能够在不影响用户体验的状况下安排这些做业。