前言
Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)
;
例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O
或与内容提供程序进行交互。
1. Service的三种不同的服务类型
1.1 前台Service
前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。即使用户停止与应用的交互,前台服务仍会继续运行。
1.2 后台Service
后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。
1.3 绑定Service
当应用组件通过调用 bindService()
绑定到服务时,服务即处于绑定状态。绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC)
跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
2. Service的两种状态
2.1 启动状态
当应用组件(如 Activity)通过调用 startService()
启动服务时,服务即处于“启动
”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。
2.2 绑定状态
当应用组件通过调用 bindService()
绑定到服务时,服务即处于“绑定
”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC)
跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
3. 生命周期函数概述
如要创建服务,您必须创建 Service 的子类(或使用它的一个现有子类)。在实现中,您必须重写一些回调方法,从而处理服务生命周期的某些关键方面,并提供一种机制将组件绑定到服务(如适用);
3.1 生命周期回调函数(重写最重要的回调函数)
3.1.1 onStartCommand()函数
当另一个组件(如 Activity)请求启动服务时,系统会通过调用 startService()
来调用此方法。执行此方法时,服务即会启动并可在后台无限期运行
。如果您实现此方法,则在服务工作完成后,您需负责通过调用 stopSelf()
或 stopService()
来停止服务。(如果您只想提供绑定,则无需实现此方法。)
3.1.2 onBind()函数
当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用 bindService()
来调用此方法。在此方法的实现中,您必须通过返回 IBinder
提供一个接口,以供客户端用来与服务进行通信。请务必实现此方法;但是,如果您并不希望允许绑定,则应返回 null。
3.1.3 onCreate()函数
首次创建服务时,系统会(在调用 onStartCommand()
或 onBind()
之前)调用此方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法
。
3.1.4 onDestroy()函数
当不再使用服务且准备将其销毁
时,系统会调用此方法。服务应通过实现此方法来清理任何资源
,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。
总结:对以上生命周期函数的使用总结!!!重点
如果组件通过调用 startService()
启动服务(这会引起对 onStartCommand()
的调用),则服务会一直运行,直到其使用 stopSelf()
自行停止运行,或由其他组件通过调用 stopService()
将其停止为止。
如果组件通过调用 bindService()
来创建服务,且未调用 onStartCommand()
,则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁
。
3.2 管理服务的生命周期
服务的生命周期比 Activity 的生命周期要简单得多。但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户未意识到的情况下运行于后台。
服务生命周期(从创建到销毁)可遵循以下任一路径:
- 启动服务:该服务在其他组件调用
startService()
时创建,然后无限期运行,且必须通过调用stopSelf()
来自行停止运行。此外,其他组件也可通过调用stopService()
来停止此服务。服务停止后,系统会将其销毁。 - 绑定服务:该服务在其他组件(客户端)调用
bindService()
时创建。然后,客户端通过 IBinder 接口与服务进行通信。客户端可通过调用unbindService()
关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。(服务不必自行停止运行。)
这两条路径并非完全独立。您可以绑定到已使用 startService()
启动的服务。例如,您可以使用 Intent(标识要播放的音乐)来调用 startService()
,从而启动后台音乐服务。随后,当用户需稍加控制播放器或获取有关当前所播放歌曲的信息时,Activity 可通过调用 bindService()
绑定到服务。此类情况下,在所有客户端取消绑定之前,stopService()
或 stopSelf()
实际不会停止服务。
3.2.1 实现生命周期回调
与 Activity 类似,服务也拥有生命周期回调方法,您可通过实现这些方法来监控服务状态的变化并适时执行工作。以下框架服务展示了每种生命周期方法:
class ExampleService : Service() {
private var startMode: Int = 0 // indicates how to behave if the service is killed
private var binder: IBinder? = null // interface for clients that bind
private var allowRebind: Boolean = false // indicates whether onRebind should be used
override fun onCreate() {
// The service is being created
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// The service is starting, due to a call to startService()
return mStartMode
}
override fun onBind(intent: Intent): IBinder? {
// A client is binding to the service with bindService()
return mBinder
}
override fun onUnbind(intent: Intent): Boolean {
// All clients have unbound with unbindService()
return mAllowRebind
}
override fun onRebind(intent: Intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
override fun onDestroy() {
// The service is no longer used and is being destroyed
}
}
注意:与 Activity 生命周期回调方法不同,您不需要调用这些回调方法的超类实现。
上图:服务生命周期。左图显示使用 startService()
创建的服务的生命周期,右图显示使用 bindService()
创建的服务的生命周期。
上图:展示服务的典型回调方法。尽管该图分开介绍通过 startService()
创建的服务和通过 bindService()
创建的服务,但请记住,无论启动方式如何,任何服务均有可能允许客户端与其绑定。因此,最初使用 onStartCommand()
(通过客户端调用 startService()
)启动的服务仍可接收对 onBind()
的调用(当客户端调用 bindService()
时);
通过实现这些方法,您可以监控服务生命周期的以下两种嵌套循环:
- 服务的整个生命周期贯穿调用
onCreate()
和返回onDestroy()
之间的这段时间。与 Activity 类似,服务也在 onCreate() 中完成初始设置,并在onDestroy()
中释放所有剩余资源。例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。 - 服务的活动生命周期从调用
onStartCommand()
或onBind()
开始。每种方法均会获得 Intent 对象,该对象会传递至startService()
或bindService()
。
对于启动服务,活动生命周期与整个生命周期会同时结束(即便是在onStartCommand()
返回之后,服务仍然处于活动状态)。对于绑定服务,活动生命周期会在onUnbind()
返回时结束。
3.3 使用清单文件声明服务
如同对 Activity
及其他组件的操作一样,您必须在应用的清单文件中声明所有服务
。
如要声明服务,请添加 < service>
元素作为 < application>
元素的子元素。下面是示例:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
3.3.1 使用清单文件声明服务的详细信息
3.3.1.1 使用时的基本语法
<service android:description="string resource"
android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:foregroundServiceType=["camera" | "connectedDevice" |
"dataSync" | "location" | "mediaPlayback" |
"mediaProjection" | "microphone" | "phoneCall"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</service>
包含于:
<application>
可包含:
<intent-filter>
<meta-data>
将服务(Service
子类)声明为应用的一个组件。与 Activity
不同,服务缺少可视化界面。服务用于实现长时间运行的后台操作,或可由其他应用调用的富通信 API。
必须用清单文件中的 < service>
元素表示所有服务。系统不会识别和运行任何未进行声明的服务。
3.3.1.2 使用时的基本属性
android:description
:向用户描述服务的字符串。此标签应设置为对字符串资源的引用,以便可以像界面中的其他字符串一样进行本地化。android:directBootAware
:服务是否可感知直接启动(direct-boot)
;即,它是否可以在用户解锁设备之前运行,默认值为falseandroid:enabled
:系统是否可以实例化服务。如果可以,则设为“true”;如果不能,则设为“false”。默认值为“true”。android:exported
:其他应用的组件是否能调用服务或与之交互。“true”表示可以,“false”表示不可以。当该值为“false”时,只有同一个应用或具有相同用户 ID 的应用的组件可以启动服务或绑定到服务。android:foregroundServiceType
: 阐明服务是满足特定用例要求的前台服务。例如,“location” 类型的前台服务表示应用正在获取设备的当前位置,目的通常是继续用户发起的操作,且该操作与设备位置相关。android:icon
:表示服务的图标。此属性必须设置为对包含图片定义的可绘制资源的引用。如果未设置此属性,则改用为整个应用指定的图标;android:isolatedProcess
:如果设置为 true,则此服务将在与系统其余部分隔离的特殊进程下运行。此服务自身没有权限,只能通过 Service API 与其进行通信(绑定和启动);android:label
:可向用户显示的服务名称。 如果未设置此属性,则改用整个应用的标签集;android:name
:实现服务的 Service 子类的名称。此名称应该是一个完全限定类名称(如“com.example.project.RoomService
”)。不过,作为一种简写形式,如果名称的第一个字符是句点,(如“.RoomService”),则会将其附加到 元素中指定的软件包名称;android:permission
:实体启动服务或绑定到服务所必需的权限的名称。如果 startService()、bindService() 或 stopService() 的调用方尚未获得此权限,该方法将不起作用,且系统不会将 Intent 对象传送给服务。android:process
:运行服务的进程的名称。通常,应用的所有组件都会在为应用创建的默认进程中运行。它与应用软件包的名称相同。 元素的 process 属性可以为所有组件设置不同的默认值。不过,组件可以使用其自己的 process 属性替换默认属性,从而允许您跨多个进程分布应用。
4. 创建启动服务
4.1 扩展 IntentService 类
由于大多数启动服务无需同时处理多个请求(实际上,这种多线程情况可能很危险),因此最佳选择是利用 IntentService
类实现服务;
IntentService
类会执行以下操作:
- 创建默认的工作线程,用于在应用的主线程外执行传递给
onStartCommand()
的所有Intent
; - 创建工作队列,用于将
Intent
逐一传递给onHandleIntent()
实现,这样您就永远不必担心多线程问题; - 在处理完所有启动请求后停止服务,因此您永远不必调用
stopSelf()
; - 提供 onBind() 的默认实现(返回 null);
- 提供
onStartCommand()
的默认实现,可将 Intent 依次发送到工作队列和onHandleIntent()
实现;
如要完成客户端提供的工作,请实现 onHandleIntent()
。不过,您还需为服务提供小型构造函数。
以下是 IntentService
的实现示例:
/**
* A constructor is required, and must call the super [android.app.IntentService.IntentService]
* constructor with a name for the worker thread.
*/
class HelloIntentService : IntentService("HelloIntentService") {
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
override fun onHandleIntent(intent: Intent?) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000)
} catch (e: InterruptedException) {
// Restore interrupt status.
Thread.currentThread().interrupt()
}
}
}
您只需要一个构造函数和一个 onHandleIntent()
实现即可。
如果您还决定重写其他回调方法(如 onCreate()
、onStartCommand()
或 onDestroy()
),请确保调用超类实现,以便 IntentService
能够妥善处理工作线程的生命周期。
例如,onStartCommand()
必须返回默认实现,即如何将 Intent 传递给 onHandleIntent()
;
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
return super.onStartCommand(intent, flags, startId)
}
除 onHandleIntent()
之外,您无需从中调用超类的唯一方法就是 onBind()
。只有在服务允许绑定时,您才需实现该方法。
在下一部分中,您将了解如何在扩展 Service
基类时实现同类服务,此类包含更多代码,但如需同时处理多个启动请求,则更适合使用该基类。
4.2 扩展服务类
借助 IntentService
,您可以非常轻松地实现启动服务。但是,若要求服务执行多线程(而非通过工作队列处理启动请求),则可通过扩展 Service
类来处理每个 Intent
。
为进行比较,以下示例代码展示了 Service
类的实现,该类执行的工作与上述使用 IntentService
的示例完全相同。换言之,对于每个启动请求,其均使用工作线程来执行作业,且每次仅处理一个请求。
class HelloService : Service() {
private var serviceLooper: Looper? = null
private var serviceHandler: ServiceHandler? = null
// Handler that receives messages from the thread
private inner class ServiceHandler(looper: Looper) : Handler(looper) {
override fun handleMessage(msg: Message) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000)
} catch (e: InterruptedException) {
// Restore interrupt status.
Thread.currentThread().interrupt()
}
// Stop the service using the startId, so that we don't stop
// the service in the middle of handling another job
stopSelf(msg.arg1)
}
}
override fun onCreate() {
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work will not disrupt our UI.
HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply {
start()
// Get the HandlerThread's Looper and use it for our Handler
serviceLooper = looper
serviceHandler = ServiceHandler(looper)
}
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
serviceHandler?.obtainMessage()?.also { msg ->
msg.arg1 = startId
serviceHandler?.sendMessage(msg)
}
// If we get killed, after returning from here, restart
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
// We don't provide binding, so return null
return null
}
override fun onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
}
}
如您所见,相较于使用 IntentService,此示例需要执行更多工作。
但是,由于 onStartCommand()
的每个调用均有您自己处理,因此您可以同时执行多个请求。此示例并未这样做,但若您希望如此,则可为每个请求创建新线程,然后立即运行这些线程(而非等待上一个请求完成)。
请注意,onStartCommand()
方法必须返回整型数。整型数是一个值,用于描述系统应如何在系统终止服务的情况下继续运行服务。IntentServics
的默认实现会为您处理此情况;但您可以对其进行修改。从 onStartCommand()
返回的值必须是以下常量之一:
START_NOT_STICKY:
如果系统在onStartCommand()
返回后终止服务,则除非有待传递的挂起 Intent,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。START_STICKY
:如果系统在 onStartCommand() 返回后终止服务,则其会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会调用包含空 Intent 的 onStartCommand()。在此情况下,系统会传递这些 Intent。此常量适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务);START_REDELIVER_INTENT
:如果系统在onStartCommand()
返回后终止服务,则其会重建服务,并通过传递给服务的最后一个 Intent 调用onStartCommand()
。所有挂起 Intent 均依次传递。此常量适用于主动执行应立即恢复的作业(例如下载文件)的服务。
4.3 启动服务
您可以通过将 Intent 传递给 startService()
或 startForegroundService()
,从 Activity 或其他应用组件启动服务。Android 系统会调用服务的 onStartCommand()
方法,并向其传递 Intent,从而指定要启动的服务;
例如,Activity 可以结合使用显式 Intent 与 startService()
,从而启动上文中的示例服务 (HelloService):
Intent(this, HelloService::class.java).also { intent ->
startService(intent)
}
startService()
方法会立即返回,并且 Android 系统会调用服务的 onStartCommand()
方法。如果服务尚未运行,则系统首先会调用 onCreate()
,然后调用 onStartCommand()
。
如果服务亦未提供绑定,则应用组件与服务间的唯一通信模式便是使用 startService()
传递的 Intent。但是,如果您希望服务返回结果,则启动服务的客户端可以为广播(通过 getBroadcast()
获得)创建一个 PendingIntent
,并将其传递给启动服务的 Intent 中的服务。然后,服务便可使用广播传递结果。
多个服务启动请求会导致多次对服务的 onStartCommand()
进行相应的调用。但是,如要停止服务,只需一个服务停止请求(使用 stopSelf()
或 stopService()
)即可;
4.4 停止服务
启动服务必须管理自己的生命周期。换言之,除非必须回收内存资源,否则系统不会停止或销毁服务,并且服务在 onStartCommand()
返回后仍会继续运行。服务必须通过调用 stopSelf() 自行停止运行,或由另一个组件通过调用 stopService()
来停止它。
一旦请求使用 stopSelf() 或 stopService()
来停止服务,系统便会尽快销毁服务。
如果服务同时处理多个对 onStartCommand()
的请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已收到新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为避免此问题,您可以使用 stopSelf(int)
确保服务停止请求始终基于最近的启动请求。换言之,在调用 stopSelf(int)
时,您需传递与停止请求 ID 相对应的启动请求 ID(传递给 onStartCommand()
的 startId)。此外,如果服务在您能够调用 stopSelf(int)
之前收到新启动请求,则 ID 不匹配,服务也不会停止。
5. 创建绑定服务
绑定服务允许应用组件通过调用 bindService()
与其绑定,从而创建长期连接。此服务通常不允许组件通过调用 startService()
来启动它。
如需与 Activity 和其他应用组件中的服务进行交互,或需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。
如要创建绑定服务,您需通过实现 onBind()
回调方法返回 IBinder
,从而定义与服务进行通信的接口。然后,其他应用组件可通过调用 bindService()
来检索该接口,并开始调用与服务相关的方法。服务只用于与其绑定的应用组件,因此若没有组件与该服务绑定,则系统会销毁该服务。您不必像通过 onStartCommand()
启动的服务那样,以相同方式停止绑定服务。
如要创建绑定服务,您必须定义指定客户端如何与服务进行通信的接口。服务与客户端之间的这个接口必须是 IBinder
的实现,并且服务必须从 onBind()
回调方法返回该接口。收到 IBinder
后,客户端便可开始通过该接口与服务进行交互。
多个客户端可以同时绑定到服务。完成与服务的交互后,客户端会通过调用 unbindService()
来取消绑定。如果没有绑定到服务的客户端,则系统会销毁该服务。
6. 向用户发送通知
处于运行状态时,服务可以使用 Toast
通知或状态栏通知来通知用户所发生的事件。
Toast 通知是指仅在当前窗口界面显示片刻的消息。状态栏通知在状态栏中提供内含消息的图标,用户可通过选择该图标来执行操作(例如启动 Activity)。
通常,当某些后台工作(例如文件下载)已经完成,并且用户可对其进行操作时,状态栏通知是最佳方法。当用户从展开视图中选定通知时,该通知即可启动 Activity(例如显示已下载的文件)。
注意:尽管您需通过调用 stopSelf() 或 stopService() 来停止绑定服务,但该服务并没有相应的回调(没有 onStop() 回调)。除非服务绑定到客户端,否则在服务停止时,系统会将其销毁(onDestroy() 是接收到的唯一回调)。