Service
是Android
中一个类,它是Android四大组件之一,使用Service
可以在后台执行长时间的操作( perform long-running operations in the background
),Service
并不与用户产生UI交互。其他的应用组件可以启动Service
,即便用户切换了其他应用,启动的Service
仍可在后台运行。一个组件可以与Service
绑定并与之交互,甚至是跨进程通信(IPC
)。例如,一个Service
可以在后台执行网络请求、播放音乐、执行文件读写操作或者与 content provider
交互 等。
本文将介绍Services
的定义、创建、启动、绑定、前台Service等相关内容,如需访问官方原文,您可以点击这个链接:《Services》
Services
Services
有两种启动形式:
Started
:其他组件调用startService()
方法启动一个Service
。一旦启动,Service
将一直运行在后台(run in the background indefinitely
)即便启动Service
的组件已被destroy
。通常,一个被start的Service
会在后台执行单独的操作,也并不给启动它的组件返回结果。比如说,一个start
的Service
执行在后台下载或上传一个文件的操作,完成之后,Service
应自己停止。Bound
:其他组件调用bindService()
方法绑定一个Service
。通过绑定方式启动的Service
是一个client-server
结构,该Service
可以与绑定它的组件进行交互。一个bound service
仅在有组件与其绑定时才会运行(A bound service runs only as long as another application component is bound to it
),多个组件可与一个service
绑定,service
不再与任何组件绑定时,该service
会被destroy
。
当然,service
也可以同时在上述两种方式下运行。这涉及到Service
中两个回调方法的执行:onStartCommand()
(通过start
方式启动一个service
时回调的方法)、onBind()
(通过bind方式启动一个service
回调的方法)。
无论通过那种方式启动service
(start、bind、start&bind),任何组件(甚至其他应用的组件)都可以使用service
。并通过Intent
传递参数。当然,您也可以将Service
在manifest
文件中配置成私有的,不允许其他应用访问。
!请注意:Service
运行在主线程中(A service runs in the main thread of its hosting process
),Service
并不是一个新的线程,也不是新的进程。也就是说,若您需要在Service
中执行较为耗时的操作(如播放音乐、执行网络请求等),需要在Service
中创建一个新的线程。这可以防止ANR
的发生,同时主线程可以执行正常的UI操作。
使用Service还是使用Thread?
Service
是一个运行在后台的组件,并不与用户交互。您仅在需要的时候创建Service
( create a service only if that is what you need
)。
当用户正在与UI交互时,需要执行一些主线程无法完成的工作,应当创建一个线程。例如当activity
正在运行时,需要播放音乐,此时需要在Activity
的onCreate()
中创建线程并在onStart()
中开启。最后在onStop()
中停止。您也可以考虑使用AsyncTask
或 HandlerThread
来替代Thread
创建线程。
Service
基础(The Basics)
为了创建Service
,需要继承Service
类。并重写它的回调方法,这些回调方法反应了Service
的生命周期,并提供了绑定Service
的机制。最重要的Service
的生命周期回调方法如下所示:
onStartCommand()
:当其他组件调用startService()
方法请求启动Service
时,该方法被回调。一旦Service
启动,它会在后台独立运行。当Service
执行完以后,需调用stopSelf()
或stopService()
方法停止Service
。(若您只希望bind Service
,则无需调用这些方法)onBind()
:当其他组件调用bindService()
方法请求绑定Service
时,该方法被回调。该方法返回一个IBinder
接口,该接口是Service
与绑定的组件进行交互的桥梁。若Service
未绑定其他组件,该方法应返回null
。onCreate()
:当Service
第一次创建时,回调该方法。该方法只被回调一次,并在onStartCommand()
或onBind()
方法被回调之前执行。若Service
处于运行状态,该方法不会回调。onDestroy()
:当Service
被销毁时回调,在该方法中应清除一些占用的资源,如停止线程、接触绑定注册的监听器或broadcast receiver
等。该方法是Service
中的最后一个回调。
如果某个组件通过调用startService()
启动了Service
(系统会回调onStartCommand()
方法),那么直到在Service
中手动调用stopSelf()
方法、或在其他组件中手动调用stopService()
方法,该Service
才会停止。
如果某个组件通过调用bindService()
绑定了Service
(系统不会回调onStartCommand()
方法),只要该组件与Service
处于绑定状态,Service
就会一直运行,当Service
不再与组件绑定时,该Service
将被destroy
。
当系统内存低时,系统将强制停止Service
的运行;若Service
绑定了正在与用户交互的activity
,那么该Service
将不大可能被系统kill
( less likely to be killed
)。如果创建的是前台Service
,那么该Service
几乎不会被kill
(almost never be killed
)。否则,当创建了一个长时间在后台运行的Service
后,系统会降低该Service
在后台任务栈中的级别——这意味着它容易被kill
(lower its position in the list of background tasks over time and the service will become highly susceptible to killing
),所以在开发Service
时,需要使Service
变得容易被restart
,因为一旦Service
被kill
,再restart
它需要其资源可用时才行(restarts it as soon as resources become available again
),当然这也取决于onStartCommand()
方法返回的值,这将在后续介绍。
在manifest
文件中注册service
(Declaring a service in the manifest
)
在manifest
文件中注册service
的方式如下:
除此之外,在<service>
标签中还可以配置其他属性,比如,需要启动该service
所需的权限、该service
应运行在哪个进程中 等( permissions required to start the service and the process in which the service should run
)。android:name
属性是唯一不可缺省的,它指定了Service
的全限定类名。一旦发布了应用,该类名将不可更改。
!请注意:为了保证应用的安全,请使用显式Intent启动或绑定一个Service
,请不要在<service>
标签中配置intent-filter
。
若不确定该启动哪个Service
,那么可以在<service>
中配置intent-filter
,并在Intent
中排除该Service
(supply intent filters for your services and exclude the component name from the Intent
),但必须调用Intent
的setPackage()
方法,来为启动的service
消除歧义(provides sufficient disambiguation for the target service
)。
注:setPackage()
方法传入一个String
参数,代表一个包名。该方法表示该Intent
对象只能在传入的这个包名下寻找符合条件的组件,若传入null
,则表示可以在任意包下寻找。
将android:exported
属性设为false
,表示不允许其他应用程序启动本应用的组件,即便是显式Intent
也不行(even when using an explicit intent
)。这可以防止其他应用程序启动您的service
组件。
使用start
方式启动Service
(Creating a Started Service
)
其他组件调用startService()
方法可以启动一个Service
,接着,Service
会回调onStartCommand()
生命周期方法。startService()
方法中传入一个Intent
参数,用于显式指定目标Service
的名字,并携带data
以供Service
使用,该Intent
参数将回传至onStartCommand()
方法中。
比如说,Activity
需要向在线数据库中上传数据,那么可以调用startService()
启动一个Service
,并将数据传入Intent
的data
中,接着,onStartCommand()
方法会接收这个Intent
并开启一个线程将数据上传至网络,当数据上传完成后,该Service
将停止并被destroy
。
一般使用如下两种方式创建一个start Service
:
-
继承
Service
类:请务必在Service
中开启线程来执行耗时操作,因为Service
运行在主线程中。 -
继承
IntentService
类:IntentService
继承于Service
,若Service
不需要同时处理多个请求,那么使用IntentService
将是最好选择:您只需要重写onHandleIntent()
方法,该方法接收一个回传的Intent
参数,您可以在方法内进行耗时操作,因为它默认开启了一个子线程,操作执行完成后也无需手动调用stopSelf()
方法,onHandleIntent()
会自动调用该方法。
继承IntentService
类(Extending the IntentService class
)
在大多数情况下,start Service
并不会同时处理多个请求(don’t need to handle multiple requests simultaneously
),因为处理多线程较为危险(a dangerous multi-threading scenario
),所以继承IntentService
类带创建Service
是个不错选择。
使用IntentService
的要点如下:
- 默认在子线程中处理回传到
onStartCommand()
方法中的Intent
; - 在重写的
onHandleIntent()
方法中处理按时间排序的Intent
队列,所以不用担心多线程(multi-threading
)带来的问题。 - 当所有请求处理完成后,自动停止
service
,无需手动调用stopSelf()
方法; - 默认实现了
onBind()
方法,并返回null
; - 默认实现了
onStartCommand()
方法,并将回传的Intent
以序列的形式发送给onHandleIntent()
,您只需重写该方法并处理Intent
即可。
综上所述,您只需重写onHandleIntent()
方法即可,当然,还需要创建一个构造方法,示例如下:
如果您还希望在IntentService
的 继承类中重写其他生命周期方法,如onCreate()
、onStartCommand()
或 onDestroy()
,那么请先调用各自的父类方法以保证子线程能够正常启动。
比如,要实现onStartCommand()
方法,需返回其父类方法:
除onHandleIntent()
外,onBind()
方法也无需调用其父类方法。
继承Service
类(Extending the Service class
)
如果您需要在Service
中执行多线程而不是处理一个请求队列(perform multi-threading instead of processing start requests through a work queue
),那么需要继承Service
类,分别处理每个Intent
。
在Service
中执行操作时,处理每个请求都需要开启一个线程,并且同一时刻一个线程只能处理一个请求( for each start request, it uses a worker thread to perform the job and processes only one request at a time
)。
注意到onStartCommand()
返回一个整形变量,该变量必须是下列常量之一:
START_NOT_STICKY
:若执行完onStartCommand()
方法后,系统就kill了service
,不要再重新创建service
,除非系统回传了一个pending intent
。这避免了在不必要的时候运行service
,您的应用也可以restart
任何未完成的操作。START_STICKY
:若系统在onStartCommand()
执行并返回后kill
了service
,那么service
会被recreate
并回调onStartCommand()
。dangerous
不要重新传递最后一个Intent
(do not redeliver the last intent
)。相反,系统回调onStartCommand()
时回传一个空的Intent
,除非有pending intents
传递,否则Intent
将为null
。该模式适合做一些类似播放音乐的操作。START_REDELIVER_INTENT
:若系统在onStartCommand()
执行并返回后kill
了service
,那么service
会被recreate
并回调onStartCommand()
并将最后一个Intent
回传至该方法。任何pending intents
都会被轮流传递。该模式适合做一些类似下载文件的操作。
启动服务(Starting a Service)
若需要启动Service
,见下面所示:
startService(intent)
方法将立即返回,并回调onStartCommand()
(请不要手动调用该方法),若该Service
未处于运行状态,系统将首先回调onCreate()
,接着再回调onStartCommand()
。若您希望Service
可以返回结果,那么需要通过调用getBroadcast
返回的PendingIntent
启动Service
(将PendingIntent
包装为Intent
),service
可使用broadcast
传递结果。
多个启动Service
的请求可能导致onStartCommand()
多次调用,但只需调用stopSelf()
、stopService()
这两个方法之一,就可停止该服务。
停止服务(Stopping a service)
一个启动的Service
必须管理自己的生命周期。系统不会主动stop
或destroy
一个运行的Service
,除非系统内存紧张,否则,执行完onStartCommand()
方法后,Service
依然运行。停止Service
必须手动调用stopSelf()
(在Service
中)或调用stopService()
(在启动组件中)。
一旦调用了上述两种方法之一,系统会尽快destroy
该Service
(as soon as possible
)。
若系统正在处理多个调用onStartCommand()
请求,那么在启动一个请求时,您不应当在此时停止该Service
(you shouldn’t stop the service when you’re done processing a start request
)。为了避免这个问题,您可以调用stopSelf(int)
方法,以确保请求停止的Service
时最新的启动请求( your request to stop the service is always based on the most recent start request
)。这就是说,当调用stopSelf(int)
方法时,传入的ID代表启动请求(该ID会传递至onStartCommand()
),该ID与请求停止的ID一致。则如果在调用stopSelf(int)
之前,Service
收到一个新的Start
请求,ID将无法匹配,Service
并不会停止。
为了节省内存和电量,当Service
完成其工作后将其stop
很有必要。如有必要,可以在其他组件中调用stopService()
方法,即便Service
处于绑定状态,只要它回调过onStartCommand()
,也应当主动停止该Service
。
创建绑定Service(Creating a Bound Service)
通过其他组件调用bindService()
方法可以绑定一个Service
以保持长连接(long-standing connection
),这时一般不允许其他组件调用startService()
启动Service
。
当其他组件需要与Service
交互或者需要跨进程通信时,可以创建一个bound Service
。
为创建一个bound Service
,必须重写onBind()
回调,该方法返回一个IBinder
接口。该接口时组件与Service
通信的桥梁。组件调用bindService()
与Service
绑定,该组件可获取IBinder
接口,一旦获取该接口,就可以调用Service
中的方法。一旦没有组件与Service
绑定,系统将destroy
它,您不必手动停止它。
为创建一个bound Service
,必须定义一个接口 ,该接口指定组件与Service
如何通信。定义的接口在组件与Service
之间,且必须实现IBinder
接口。这正是onBind()
的返回值。一旦组件接收了IBinder
,组件与Service
便可以开始通信。
多个组件可同时与Service
绑定,当组件与Service
交互结束后,可调用unbindService()
方法解绑。bound Service
比start Service
要复杂,故我将在后续单独翻译。
向用户发送通知(Sending Notifications to the User)
运行中的Service
可以通过Toast Notifications
或 Status Bar Notifications
向用户发送通知。Toast
是一个可以短时间弹出的提醒框。二Status Bar
是顶部状态栏中出现的太有图标的信息,用户可以通过下拉状态栏获得具体信息并执行某些操作(如启动Activity
)。
通常,Status Bar
用于通知某些操作已经完成,如下载文件完成。当用户下拉状态栏后,点击该通知,可获取详细内容,如查看该下载的文件。
运行前台Service
(Running a Service in the Foreground
)
前台Service
用于动态通知消息,如天气预报。该Service
不易被kill
。前台Service
必须提供status bar
,只有前台Service
被destroy
后,status bar
才能消失。
举例来说,一个播放音乐的Service
必须是前台Service
,只有这样用户才能确知其运行状态。为前台Service
提供的status bar
可以显示当前音乐的播放状态,并可以启动播放音乐的Activity
。
调用startForeground()
可以启动前台Service
。该方法接收两个参数,参数一是一个int
型变量,用户指定该通知的唯一性标识,而参数而是一个Notification
用于配置status bar
,示例如下:
!注意:为startForeground()
设置的ID必须是0。
调用stopForeground()
来移除(remove
)前台Service
。该方法需传入一个boolean
型变量,表示是否也一并清除status bar
上的notification
(indicating whether to remove the status bar notification as well
)。该方法并不停止Service
,如果停止正在前台运行的Service
,那么notification
也一并被清除。
Service
生命周期(Managing the Lifecycle of a Service
)
从Service
的启动到销毁,有两种路径:
- A started service:需手动停止
- A bound service:可自动停止
如下图所示 :
这两条路径并不是毫不相干的:当调用startService()
start
一个Service
后,您仍可以bind
该Service
。比如,当播放音乐时,需调用startService()
启动指定播放的音乐,当需要获取该音乐的播放进度时,有需要调用bindService()
,在这种情况下,知道Service
被unbind
,调用stopService()
或stopSelf()
都不能停止该Service
。
实现Service
的生命周期回调(Implementing the lifecycle callbacks
)
这些生命周期方法在使用时无需调用各自的父类方法。
在两条生命周期路径中,都包含了两个嵌套的生命周期:
- 完整生命周期(
entire lifetime
):从onCreate()
被调用到onDestroy()
返回。与Activity
类似,一般在onCreate()
中做一些初始化工作,而在onDestroy()
做一些资源释放工作。如,若Service
在后台播放一个音乐,就需要在onCreate()
方法中开启一个线程启动音乐,并在onDestroy()
中结束线程。-
无论是startService()
还是 bindService()
启动Service
,onCreate()
和 onDestroy()
均会被回调。
- 活动生命周期(
active lifetime
):从onStartCommand()
或onBind()
回调开始。由相应的startService()
或bindService()
调用。
若是Start Service
,那么Service
的活动生命周期结束就意味着其完整生命周期结束 (the active lifetime ends the same time that the entire lifetime ends
),即便onStartCommand()
返回后,Service
仍处于活动状态;若是bound Service
,那么当onUnbind()
返回时,Service
的活动生命周期结束。
!请注意:针对Start Service
,由于Service
中没有类似onStop()
的回调,所以在调用stopSelf()
或stopService()
后,只有onDestroy()
被回调标志着Service
已停止。