Sersives是一种没有用户界面,能够在后台执行长期运行操作的应用程序组件。其他应用程序组件可以启动(start)Service,即使用户切换到其他应用程序,该Service仍然在后台运行。另外,组件也可以绑定(bind)Service来与之交互,甚至进行进程间通信(IPC)。例如,service能够控制网络交易,播放音乐,执行文件I/O,与content provider交互等等,并且都是在后台完成的。
service本质上有两种形式:
Started
当某个应用程序组件(例如activity)通过调用startService()启动service,该service处于started状态。一旦被started,service可以无限期地在后台运行,就算启动它的组件被销毁,service也不会停止。通常,处于started状态的service执行独立的操作并且不用给调用者返回结果。例如,用它在网上下载或上传一个文件。当操作完成,service应该自己终止(stop)自己。
Bound
当某个应用程序组件通过调用bindService()与service绑定,该service处于bound状态。处于bound状态的service提供一种“客户端-服务器”式的交互,绑定该service的组件可以发送请求,取得结果,甚至可以跨进程进行这些操作。处于bound状态的service仅在有组件与之绑定的情况下才运行。多个组件可以同时绑定service,当所有组件都与service解除绑定,该service就销毁了。
无论你的应用程序是被started,bound,或者两者都有,任何应用程序组件都可以使用service(包括完全独立的另一个应用程序),就像任何组件可以通过intent使用activity一样。更多信息见Declaring the service in manifest。
注意:service在持有它的进程的主线程中运行——它并不创建自己的线程,也不会在其他的进程中运行(除非你指定)。这意味着,如果service要做比较耗CPU的工作,或者一些阻塞操作(如MP3回放或者网络工作),你应该在service中创建新线程来做这些工作。使用新线程会降低你的应用程序无响应的风险,并且程序主线程可以留给用户与activity交互。
技巧:该使用service还是thread?
service仅仅是一种组件,即使在用户与应用程序没有交互时也可以在后台运行。所以,你应该仅在需要时创建service。
如果你需要在主线程外执行某项操作,并且仅在用户和程序有交互时执行,那你应该创建新线程而不是service。例如,如果想仅仅在你的activity运行时播放音乐,你可以在onCreate()方法中创建一个新线程,在onStop()中终止它。同时,应考虑使用AsyncTask或HandlerThread,而不是传统的Thread类。有关线程更多信息请参考Processes and Threading文件。
需要记住当你使用service,它默认也在你程序的主线程中运行。要用service执行耗CPU或是易阻塞的操作,仍然需要在service中创建新线程。
The Basics
要创建service,必须继承Service类(或是已有的子类)。具体实现中,你需要重写一些回调函数来控制service生命周期的关键部分,并为其他组件绑定service提供途径。下面是一些最可能重写的回调函数:
onStartCommand()
当另一个组件(如activity)通过调用startService()启动service,系统会调用该方法。一旦该方法被执行,service将启动并在后台一直运行。如果实现了该方法,就有责任在service工作完成时关闭它——通过调用stopSelf()或者stopService()。(如果仅仅想提供绑定功能,则不需要实现该方法。)
onBind()
当另一个组件通过调用bindService()来绑定service(例如要执行RPC),系统会调用该方法。要实现该方法,必须提供一个接口,该接口返回一个IBinder对象供客户端与service进行通讯。onBind()是service必须实现的方法,但如果不希望service与其他组件绑定,可以返回null。
onCreate()
service第一次被创建时系统调用该方法,来执行一些一次性的操作(该方法在onStartCommand()和onBind()方法之前被调用)。如果service已经运行,该方法不会被调用。
onDestroy()
在service不再被使用,将要销毁时系统调用该方法。service应该实现该方法来清理资源,例如线程,已注册的listener和receiver等等。该方法将是service最后调用的方法。
如果组件通过调用startService()(将导致onStartCommand()被调用)来启动service,该service将一直运行直到它自己调用stopSelf()或者其他组件调用stopService()。
如果组件调用bindService()(onStartCommand()将不被调用)来创建service,该service仅仅在组件绑定它的时候运行。一旦所有客户端都与service解除绑定,系统将销毁service。
android系统只在内存不足,必须为占据焦点的activity恢复系统资源时才会强制终止service。如果该service与占据焦点的activity绑定,那么它被kill掉的可能性就降低了,如果该service被声明为run in the foreground(稍后讨论),那么它几乎不肯能被kill掉。另外,如果service处于started状态并长期运行,那么系统会随时间降低该service在后台任务列表中的位置,该service将很可能被kill掉——如果service是被started的,必须要针对系统可能重新启动它来设计。因为如果系统kill掉了你的service,当资源可用时又会立即重启该service(也取决于onStartCommand()的返回值,稍后讨论)。关于系统可能销毁service的更多信息,参考Processes and Threading 文件。
下面你将学习如何创建各种类型的service,以及其他程序组件如何使用它们。
在manifest文件中声明service
和activity(以及其他组件)类似,你必须在你的应用程序manifest文件中声明所有的service。
要声明service,添加一个<service>元素作为<application>元素的子元素。例如:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
有许多其他的属性可以放在<service>元素中来定义许多特性,例如启动service所需的权限,service应该在什么时候运行。android:name是唯一必须有的属性——它指定service的类名。程序一旦发布,这个名字就不该再有改动。因为如果改动,由于启动或绑定service对于显示intent有依赖性,可能导致破坏代码。
为了确保程序安全,务必使用显示intent来启动或绑定service,并且不要为service声明intent filter。如果对于启动哪个service的不确定性是必要的,也可以为service声明intent filter并且让Intent不包括component name,但你必须用setPackage()方法为intent设置包,以此明确目标service。
另外,可以添加android:exported属性并赋“false”值,来保证service是能用于自己的程序。如果这么做,就算使用显示intent,其他程序也无法启动你的service。
Creating a Started Service
处于started状态的service,是被其他组件通过startService()启动的,这种service将会调用onStartCommand()方法。
当service被started,它的生命周期与启动它的组件独立,可以在后台一直运行,就算启动它的组件被销毁也不受影响。这样的service应该在工作完成时自己调用stopSelf()来结束自己,其它组件也可以调用stopService()来停止它。
例如activity等应用程序组件可以通过startService()来启动service,并传递Intent指定service以及携带service要用的数据。service在onStartCommand()方法中接收该Intent。
举个例子,假设一个activity需要保存一些数据到在线数据库。该activity可以start一个service并给startService()传递一个intent来递送要保存的数据。该service在onStartCommand()中接收该intent,连接Internet并执行数据库事务。当事务完成,service自己是stop并被销毁。
注意:默认情况下,service与应用程序运行在同一进程,并且在程序的主线程中运行。所以,如果service要做耗CPU或是易阻塞的操作,而用户要与同一程序的activity交互,service将会减缓activity的动作。为了避免这种情况,你应该启动新线程来运行该service。
习惯上,要创建一个started service,可以继承以下两种类:
Service
这是所有service的基类。当继承该类,很重要的一点是,创建一个新线程来完成service的全部工作,因为该service默认使用程序的主线程,那可能导致你的activity运行缓慢。
IntentService
这是Service的子类,使用一个辅助线程来处理所有的start请求,并且同一时间仅处理一个请求。如果你不需要service同时处理多个请求,这将是你最好的选择。你要做的仅仅是实现onHandleIntent()方法,该方法接收每个start请求的intent以便后台工作。
以下将描述如何使用这些类来实现service
继承IntentService类
因为大多数started service不需要同时处理多个请求(那实际上是个危险的多线程场景),那么使用IntentService类可能是最佳选择。
IntentService类做以下工作:
创建一个默认的辅助线程(而不是程序的主线程)来执行所有传递给onStartCommand()。
为onHandleIntent()方法的实现创建一个工作队列,一次传递一个intent,所以你永远不必担心多线程。
当有所start请求都被处理完毕会自动终止service,所以你永远不必调用stopSelf()。
实现默认返回null的onBind()方法。
实现默认的onStartCommand()方法来把intent传给工作队列,然后传给onHandleIntent()方法。
所有这些都表明,你唯一要做的事情就是实现onHandleIntent()方法来完成客户端的请求。(你也需要为service提供一个构造器)
下面是实现IntentService的例子:
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("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
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
}
你需要提供的仅仅是:一个构造器和实现onHandleIntent()方法。
如果也要重写其他回调方法,例如onCreate(),onStartCommand(),或者onDestroy(),切记调用对应的父类实现,这样IntentService才能正确处理辅助线程的声明周期。
例如,onStartCommand()必须返回默认的实现(这是intent如何传递给onHandleIntent()方法)。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
除了onHandleIntent(),另一个唯一不必调用父类的方法是onBind()(但如果你的service允许绑定你就得实现该方法。)
下面你将学习继承Service基类的service,这种实现会多很多代码,但是当需要同时处理多个start请求时,那将是最合适的选择。
继承Service类
如同上一部分你所看到,使用IntentService使你实现一个started service变得十分简单。然而,如果service要实现多线程(而不是通过一个工作队列一个一个完成),可以继承Service类来处理每个intent。
为方便比较,下面的示例代码中,继承Service类的例子和之前使用IntentService的例子完成了相同的工作。为每一个start请求,它使用一个辅助线程来完成工作,并且每次仅进行一个。
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
// 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
public void 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 thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
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
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// We don't provide binding, so return null
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
如你所见,这比使用IntentService工作量要大很多。
不过,由于在onStartCommand()自己处理每个调用,亦可以同时执行多个请求。上面的代码中并没有如此做,不过如果你希望这样做,可以为每一个请求创建一个线程并立即运行(而不是等待之前的请求完成)。
应该注意到onStartCommand()必须返回一个整型值。该整型值描述系统在kill掉service后该如何继续service(上面的讨论中,IntentService的默认实现已经为你做了处理,你可以进行修改)。onStartCommand()的返回值必须是以下的其中一种:
START_NOT_STICKY
如果在onStartCommand()返回后,系统kill掉了service,除非有等待递送的intent,否则不会重新创建service。如果要避免service在不必要的时候运行,并且程序能够容易得重新继续未完成的工作,这将是最安全的选择。
START_STICKY
如果在onStartCommand()返回后,系统kill掉了service,重新创建service并调用onStartCommand()方法,但是不会重新传递上一个intent。除非有等待递送的intent,这种情况下,这些intent将被传过来,否则,系统会传一个null intent来调用onStartCommand()。这种情况适合媒体播放器(或类似的service),不执行命令,而是一直运行等待工作。
START_REDELIVER_INTENT
如果在onStartCommand()返回后,系统kill掉了service,重新创建service并传递上一个intent来调用onStartCommand()方法。其它任何等待递送的intent都在之后轮流传过来。这种情况适合那种不断执行工作,应该被立即恢复的service,例如下载文件。
Starting a Service
通过传递一个intent(指明要启动的service),你可以从activity或者其它程序组件来启动service。android系统调用service的onStartCommand()方法并传递该intent。(不要直接调用onStartCommand())。
例如,activity可以用显示intent调用startService()方法,来启动前面的示例service(HelloService)。
Intent intent = new Intent(this, HelloService.class);
startService(intent);
startService()方法立即返回,接着android系统调用service的onStartCommand()方法。如果service还没有运行,系统先调用onCreate(),再调用onStartCommand()。
如果service没有提供binding,那么传递给startService()的intent将是程序组件和service之前通信的唯一方式。然而,如果希望service返回一个结果,那么启动service的客户端可以创建一个能发送广播的PendingIntent(有getBroadcast()方法),把它放入启动service的Intent中来传给service。该service就可以使用broadcast来传递结果。
多个启动service的请求将调用多次service的onStartCommand()方法。不过,要终止service仅需一个终止请求(通过stopSelf()或stopService())。
Stopping a service
started service必须管理其自身生命周期。除非系统必须恢复内存资源而且service在onStartCommand()返回后还在继续运行,否则,系统不会终止或销毁该service。所以,service必须调用stopSelf()终止自己,或者其它组件调用stopService()来终止它。
一旦收到stopSelf()或stopService()的终止请求,系统会尽可能快地销毁service。
然而,如果service在onStartCommand()方法中处理多个请求,当你执行完一个start请求时不该终止service,因为可能已经收到一个新的start请求(在第一个请求结束时终止将导致第二个请求被终止)。要避免这种问题,可以使用stopSelf(int)来确保终止service的请求总是基于最近的start请求。当你调用stopSelf(int),会传递start请求的ID(就是传给onStartCommand()方法的startId)给对应的stop请求。然后,如果service在你调用stopSelf()之前收到一个新的start请求,那么ID不会匹配,service也不会终止。
注意:应用程序在工作完成时终止其service十分重要,这可以避免浪费系统资源和电池。其他组件可以调用stopService()来终止service。即使你运行绑定service,如果service的onStartCommand()方法不再被调用,你必须终止service。
Creating a Bound Service
bound service允许程序组件通过调用bindservice()与该service绑定,从而创建一个长期的连接。(并且通常不允许组件调用startService()来启动它)。
如果想要通过进程间通信(IPC)在程序中让service与activity等其他组件交互,或者想要给其他程序提供你的程序的某些功能,此时,你应该使用bound service。
要创建bound service,必须实现onBind()方法来返回一个IBinder对象,该对象定义了与service通信的接口。其他程序组件就可以调用bindService()来取得该接口并调用service的方法。这种service仅在程序组件绑定的时候运行,所以当没有组件绑定该service时,系统将销毁service(与started service不同,你不需要终止bound service)。
为了创建bound service,要做的第一件事是定义接口,这个接口指定客户端与service通信的方式。这个在service和客户端之间的接口必须是IBinder对象,并且是service的onBind()方法的返回值。一旦客户端接收了这个IBinder对象,就可以通过该接口与service交互。
多个客户端可以同时绑定同一个service。当某个客户端完成了与service的交互,可以调用unbindService()来解除绑定。当没有客户端与该service绑定时,系统将销毁该service。
实现一个bound service有很多方式,并且比started service复杂的多,关于这些将在有关 Bound Service的一个单独的课程中继续讨论。
Sending Notification to the User
一旦运行,service可以使用Toast Notifications或Status Bar Notifications来向用户通知事件。
toast notification在当前窗口浮现要传递的消息,一会就消失,而status bar notification在状态栏提供图标和消息,用户可以选择并采取相应的行为(比如打开activity)。
一般来说,当某些后台工作完成(如文件下载完成),应该使用status bar notification,当用户在状态栏的扩展视图中选择了某项通知,将会启动一个activity(例如打开下载的文件)。
更多信息请参看Toast Notifitions或Status Bar Notifications开发指导。
Running a Service in the Foreground
foreground service是指这种service,用户清楚地知道它的存在,当系统内存不足时不会销毁它。foreground service必须在状态栏提供一个通知,意味着除非该service被终止,或者被从前台移除,否则该通知不能被移走。
例如,通过service播放音乐的播放器就应该被设置在前台运行,因为用户明确知道它在运行。在状态栏的通知可以表明当前曲目,并允许用户进入activity与播放器交互。
要让你的service前台运行,调用startForeground()方法。该方法有两个参数:一个整型作为通知的唯一标识,另一个参数是status bar显示的Notifitions对象。例如:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
注意:给startForeground()传递的整型ID一定不能为0。
要把service从前台移除,调用stopForeground()。该方法有一个布尔值参数,表明是否也把状态栏的通知一起移除。该方法并不会终止service。然而,如果service在前台运行时被终止,那么通知也会被移除。
关于通知的更多信息,参看Creating Status Bar Notifitions。
Managing the Lifecycle of a service
service的生命周期比activity的要简单得多。然而它却更加重要,你需要密切注意service如何被创建和销毁,因为service可以后台运行,用户可能并不知道。
service的生命周期——从被创建到被销毁——有两种线路:
started service:
这种service在其他组件调用startService()时被创建,然后一直运行并且必须调用stopSelf()来终止自己。其他组件可以调用stopService()来终止它。当service被终止,系统将销毁它。
bound service:
这种service在其它组件调用bindService()时被创建。客户端通过IBinder接口与service通信。客户端可以调用unbindService()来结束连接。多个客户端可以绑定同一个service,当所有客户端解除绑定,系统会销毁该service。(该service不需要自我终止)
这两种线路不是完全分离的。你可以绑定一个被startService()启动的service。例如,一个后台音乐service可以被startService()启动,通过一个Intent指定要播放的音乐。稍后,可能用户想对播放器进行操作,或者取得当前曲目的信息,activity可以通过bindService()绑定该service。在这种情况下,stopService()或者stopSelf()无法终止service直到所有客户端解除绑定。
Implementing the lifecycle callbacks
和activity类似,service也有生命周期回调函数,你可以实现这些方法来监控service状态的改变,并在正确的时机执行工作。下面的框架service演示了每个生命周期的方法:
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
注意:和activity的生命周期回调函数有所不同,你不需要调用这些回调函数的父类。
实现这些方法,你可以监控service生命周期的两种嵌套循环:
service的entire lifetime,从被调用onCreate()方法开始到onDestroy()方法返回。与activity类似,service在onCreate()中完成初始化设置并在onDestroy()中释放所有保留的资源。例如,音乐播放service会在onCreate()中创建播放音乐的线程,在onDestroy()中终止该线程。
所有service都会调用onCreate()和onDestroy()方法,无论被startService()还是bindService()所创建。
service的active lifetime,从onStartCommand()或onBind()方法开始。无论哪个方法都传递一个Intent分别给startService()或者bindService()。
如果service是被started,则active lifetime的结束时间点和entire lifetime的结束时间点相同。(在onStartCommand()返回之后service依然处于活跃期)。如果是bound service,则active lifetime在onUnbind()方法返回后结束。
注意:尽管started service是被stopSelf()或stopService()终止,但service没有与其对应的回调函数(没有onStop()函数)。所以,除非service与客户端绑定,否则当service终止时系统就销毁它——唯一接收的回调是onDestroy()函数。
上图表明了service的典型回调函数。尽管图中service被分为startService()和bindService()创建,应该记住,无论它是如何创建的,都可能允许客户端与其绑定。所以,最初被onStartCommand()(通过某个客户端调用startService())创建的service仍然可以接到onBind()回调(当客户端调用bindService())。
更多有关创建service,提供绑定的信息,参见Bound Service文件。该文件在Managing the Lifecycle of a Bound Service部分也包含更多关于onRebind()回调函数的信息。