Service是一个应用程序组件,可以在后台执行耗时的操作,并且没有用户界面。其它应用程序的组件可以启动服务,即使用户切换到其它的应用它还会继续运行在后台。此外,应用程序组件还能绑定到服务并和它进行交互,甚至执行进程间通信(IPC)。例如,服务可以处理网络传输、音乐播放、执行文件I/O、或者与content provider进行交互,所有这些都是在后台执行。
带着问题去学习
- 注册Service需要注意什么
- 什么是Service以及描述下它的生命周期。
- Service与Activity怎么实现通信
- Service有哪些启动方法,有什么区别,怎样停用Service?
- Service和Activity在同一个线程吗 main 线程 UI线程
- Service里面可以弹土司么
服务基本上可以采取两种形式:
Started(启动的)
当应用组件(例如activity)通过调用startService()开启它时,service是启动的。一旦启动,一个service可以无限地运行在后台,甚至启动它的组件已经销毁。通常,一个开启的service执行单一操作并且不会给调用者返回结果。例如,它可能通过网络下载或上传文件。当操作完成,service应该把自己停止。
Bound(绑定的)
当应用组件通过调用bindService()绑定到它时,service是绑定的。一个绑定的service提供客户端/服务器接口允许组件和service交互,甚至跨进程操作使用进行间通信(IPC)。只要其它应用组件绑定到service,绑定的service就会运行。多个组件可以绑定到service,但当它们都解绑后,service会被销毁。
尽管这个文档对service的两种类型分别进行了讨论,你的service可以同时用这两种方式工作——它可以被启动(无限地运行),也可以被绑定。这是一个简单的问题在于你是否实现了这两个回调方法:onStartCommand()允许组件启动它,onBind()允许对service进行绑定。
不管你的应用是否启动,绑定或两者,任何应用组件都可以使用service(甚至不同的应用),就像任何组件都可以使用一个activity——通过使用Intent开启它。然而,你可以声明service是私有的,在清单文件中,阻止其它应用程序访问。会在“在清单文件中声明service”这一部分有更多的讨论。
警告:service运行在它主进程中的主线程——service不会创建它自己的线程并且不会运行在单独的进程(除非另有说明)。这意味着,如果你的service占用过多的CPU(例如播放MP3或访问网络),你应该在service中创建一个新的线程去做这些事情。通过使用单独的线程,你可以减少应用程序无响应(ANR)错误的风险并且应用的主线程可以用activity保持对用户交互的关注。
基础
想要创建一个service,你必须创建一个Service的子类(或一个它的存在的子类)。在你的实现中,你需要重写一些回调方法来处理service生命周期的关键方面并且对于组件绑定到service提供一个机制,如果合适的话。你应该重写的最重要的回调方法如下:
onStartCommand()
当其它组件例如activity通过调用startService()来请求service启动时系统会调用这个方法。一旦这个方法执行,service就启动并且可以无限地运行在后台。如果你实现这个,它主要负责当任务完成后停止service,通过调用stopSelf() 或 stopService()。(如果你只想提供绑定,你不需要实现这个方法。)
onBind()
当其它组件通过调用bindService()来绑定到service时系统会调用这个方法。在你实现的这个方法中,你必须提供一个客户端用来和service交互的接口,通过返回一个IBinder。你必须总是实现这个方法,但如果你不想允许绑定,你应该返回null。
onCreate()
当service第一次被创建时系统会调用这个方法,可以执行只需一次配置的程序(会在onStartCommand() 或 onBind()调用之前)。如果你的service已经运行,这个方法不会被调用。
onDestroy()
当service不再被使用被销毁时系统会调用这个方法。你的service应该实现这个用于清理所有的资源,例如线程,注册的监听,receivers等等。这是service接收到的最后一次调用。
如果一个组件通过调用startService()启动一个service(这会导致调用onStartCommand()),然后它会一直运行直接它自身调用stopSelf()或其它组件通过调用stopService()停止它。
如果一个组件调用bindService()创建一个service(并且onStartCommand()不会被调用),然后只要组件绑定到service,service就会运行。一旦所有的客户端从service解绑,系统就会销毁它。
当内存不足时并且为了用户正在关注的activity必须释放系统资源,Android系统将会强行停止一个service。如果service被用户正在关注的activity所绑定,那就不太可能被杀死,如果service被声明运行在前台(稍候讨论),也几乎不可能被杀死。否则,如果service被启动并且是长时间运行,系统会降低它在后台任务超时列表中的位置并且service将变得极易被杀死——如果你的service启动,当系统重启它时你必须设计好优雅的处理。如果系统杀死了你的service,当内存充足时系统会尽快重启它(但这也依赖于onStartCommand()返回的值,稍候介绍它)。关于当系统可能销毁一个service更多的信息,请查看Processes and Threading 文档。
在下面的部分,你可以看到怎样创建service的每种类型并且怎样从其它应用组件使用它。
在清单文件声明一个service
就像activity(或其它组件),你必须在你应用的清单文件中声明所有的service。
声明你的service,添加一个1
2
3
4
5
6
7<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
关于在清单文件中声明你的service更多的信息请查看
这有你可以包含在
为了确保你的应用是安全的,当启动或绑定Service时总是使用一个显式的intent并且不要给service声明意图过滤器(intent filters)。 这个是至关重要的,如果你允许一些不清楚数量的组件开启service,你可以为你的service添加意图过滤器并且从Intent排除组件名称,但你必须为intent使用setPackage()设置包,为目标service提供了充足的说明。
另外,你也可以确保你的service只对你的应用可用,通过包含 android:exported属性并设置它为”false”。这会有效阻止其它应用开启你的srevice,甚至当使用一个显式intent。
创建一个启动的service
一个启动的service是其它组件通过调用startService()启动的,然后会调用service的onStartCommand()方法。
当一个service启动后,它的生命周期独立于启动它的组件并且service可以无限地运行在后台,甚至当启动它的组件被销毁后。正因如此,当service处理工作完成后应该调用stopSelf()停止运行,或其它组件通过调用stopService()进行停止。
一个应用组件例如activity可能通过调用startService()开启service,通过intent指定service和包含service需要用的所有数据。service可以在onStartCommand()方法中接收这个intent。
例如,如果一个activity需要保存一些数据到在线数据库。activity可以启动一个service,通过intent传递需要保存的数据到startService()。service在onStartCommand()方法接收intent,连接网络和执行数据库处理。当处理完成,service停止运行并且销毁。
警告:默认情况下,一个service运行在它声明的应用程序的同一个进程中并且在应用的主线程中。因此,当用户在相同的应用和activity进行交互时,如果你的service执行密集的或阻塞操作,service将会降低activity的性能。为了避免影响应用的性能,你应该在service中开启一个新的线程。
通常,创建一个启动的service你可以继承下面两个类:
Service
这是所有service的基类。当你继承这个类,重要的是你得创建一个新的线程去处理所有service的工作,因此service使用的是你应用的主线程,默认情况下,这会降低你应用正在运行的activity的性能。
IntentService
这是Service的一个子类使用了一个工作线程去处理所有的开启请求,每次处理一个。如果你不需要你的service同时处理多个请求这是最好的选择。你需要做的就是实现onHandleIntent(),为每个开启请求接收intent处理后台任务。
下面的部分描述了你可以怎样用这些类中的一个实现你的service。
继承IntentService类
因为大多数启动的service不需要同时处理多个请求(这实际上是一个危险的多线程情况),使用IntentService实现你的service可能是最好的方式。
IntentService的特点:
- 创建一个默认的工作线程执行所有从你应用的主线程分发到onStartCommand()的intent。
- 创建一个工作队列每次通过一个intent到你实现的onHandleIntent(),因此你不必担心多线程的问题。
- 所有请求处理完成后停止service,因此你不用调用stopSelf()。
- 提供一个默认onBind()的实现返回null。
- 提供一个默认onStartCommand()的实现发送intent到工作队列然后发送到你实现的onHandleIntent()。
所有的这些意味着你只需要实现onHandleIntent()去处理客户端提供任务。(但是,你还需要为service提供一个构造方法。)
这是一个实现IntentService的例子:
1 | public class HelloIntentService extends IntentService { |
这就是你需要的:一个构造方法和一个onHandleIntent()的实现。
如果你决定也重写其它的方法,例如onCreate(), onStartCommand(), 或 onDestroy(),确保调用父类的实现,因为IntentService可以适当的处理工作线程的生命周期。
例如,onStartCommand()必须返回默认的实现(处理得到的intent分发给onHandleIntent()):1
2
3
4
5@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);
}
继承Service
正如你看到的前几部分,使用IntentService让你实现一个service非常简单。然而,如果你需要让你的service执行多线程(而不是通过一个工作队列处理开启请求),那么你可以继承Service处理每个intent。
相比较而言,下面是一个实现Service类的示例代码执行和上面使用IntentService的例子同样的代码。就是说,对于每个开启请求,它使用了一个工作线程去执行任务并且同一时间处理一个请求。
1 | public class HelloService extends Service { |
正如你看到的,有很多需要处理的工作比使用IntentService。
然而,因为你在onStartCommand()处理每次调用,你可以同时执行多次请求。这个例子没有这么做,但如果你想这么做,你可以对每次请求创建一个新线程并且马上运行它们(而不是等前面的请求处理完成)。
注意onStartCommand()方法必须返回一个integer。这个值描述了系统杀死它后系统应该怎样延续service(上面所讨论的,IntentService的默认实现为你进行了处理,但你可以修改它)。从onStartCommand()返回的值必须是下面中的一个:
START_NOT_STICKY
如果系统在onStartCommand()返回后杀死了service,系统不会重新创建service,除非传递的是挂起的intent。这是一个安全的选项避免运行你的service当没必要并且当你的应用程序可以重启所有未完成的任务。
START_STICKY
如果系统在onStartCommand()返回后杀死了service,系统会重新创建service并且调用onStartCommand(),但不会重新分发最后的intent。而是系统会使用一个null的intent调用onStartCommand(),除非这是被挂起的intent启动的service,这种情况下,这些intent会被传递。这适用于媒体播放器(或相似的service)不是执行命令,但无限的运行并且等待一个任务。
START_REDELIVER_INTENT
如果系统在onStartCommand()返回后杀死了service,系统会重新创建service并且用分发到service的最后的intent调用onStartCommand()。所有被挂起的intent都会被依次传递。这适用于一个service正在执行一个应该立即被恢复的任务,例如下载一个文件。
对于这些返回值更详细的信息,请查看每个常量链接的参考文档。
开启Service
你可以从activity或其它应用组件通过Intent(指定开启的service)传递给startService()开启一个service。Android系统调用service的onStartCommand()方法并且传递Intent参数。(你从不应该直接调用onStartCommand()。)
例如,一个activity使用显式intent用startService()开启前面部分示例的service:
1 | Intent intent = new Intent(this, HelloService.class); |
startService()方法会立即返回并且Android系统会调用service的onStartCommand()方法。如果service还没有运行,系统会首先调用onCreate(),然后调用onStartCommand()。
如果service不支持绑定,使用startService()分发的intent只是应用组件和service之间的通信模式。然而,你想让service返回一个结果,开启service的客户端可以为广播(使用getBroadcast())创建一个PendingIntent并且用开启service的intent分发到service。service可以使用广播分发一个结果。
多次请求开启service会导致相应的多次调用service的onStartCommand()。然而,只需要一次停止service(使用stopSelf() 或 stopService())请求就可以停止它。
停止Service
一个开启的service必须管理它的生命周期。就是说,系统不会停止或销毁service除非它必须释放系统内存并且service持续运行到onStartCommand()返回之后。因此,service必须通过调用stopSelf()停止运行或其它组件通过调用stopService()停止service。
一旦使用stopSelf() 或 stopService()请求停止,系统将尽可能快的销毁service。
然而,如果你的service在onStartCommand()同时处理多个请求,当你处理完一个请求后你不应该停止service,因为你可能接收到新的请求(在第一个请求结束停止将终止第二个请求)。为了避免这个问题,你可以使用stopSelf(int)来确保停止你请求的service总是基于最近的开启请求。也就是说,当你调用stopSelf(int)时,你通过开启请求ID(startId被传递到onStartCommand())去停止相应的请求。如果你调用stopSelf(int)之前service接收到了一个新的开启请求,ID不会匹配并且service不会停止。
注意:重要的是当service完成工作时你的应用应该停止它,避免消耗系统资源和电量。必要时,其它组件可以通过调用stopService()停止service。即使你为service开启了绑定,如果它之前接收到了onStartCommand()调用你也必须自己停止service。
对于service生命周期更多的信息,请看下面“管理Service生命周期”部分。
创建一个绑定的Service
一个绑定的Service允许应用组件通过调用bindService()绑定到它为了创建长久连接(并且通常不允许组件通过调用startService()开启它)。
当你想从activity和你应用中的其它组件和service进行交互时你应该创建一个绑定的service或给其它应用暴露你应用的一些功能,通过进程间通信(IPC)。
为了创建一个绑定的service,你必须实现 onBind()回调方法并且返回一个定义了和service通信的接口的IBinder。其它应用组件可以通过bindService()获取接口并且开始在service上调用方法。service只为绑定到它的应用组件服务,因此当没有应用组件绑定到它时,系统会销毁它(你不需要停止一个绑定的service,但当service通过onStartCommand()方式启动时,你必须自己停止)。
为了创建一个绑定的service,第一件事情你必须定义一个指定客户端可以怎样和service进行通信的接口。这个接口必须是service和客户端之间IBinder的实现并且你的service必须从onBind()回调方法返回。一旦客户端接收到IBinder,它可以通过那个接口和service进行交互。
多个客户端可以同时绑定到service。当客户端完成和service的交互,调用unbindService()解绑。一旦没有绑定到service的客户端,系统会销毁service。
这有多种实现绑定service的方式并且实现比开启一个service更为复杂,因此关于绑定service的讨论在单独的文档 Bound Services。
给用户发送通知
一旦运行,service可以使用 Toast通知Toast Notifications)或 状态条通知(Status Bar Notifications)通知用户事件。
Toast通知是一个在当前窗口表面上出现一会儿然后消失的消息,状态条通知在状态条用一个消息提供一个图标,用户可以点击它获取一个动作(例如打开一个activity)。
通常,当一些后台工作完成后(例如一个文件下载完成)状态条通知是最好的方法并且用户可以马上查看它。当用户从扩展视图选择通知时,通知可以打开一个activity(例如下载文件的视图)。
查看 Toast Notifications 或 Status Bar Notifications开发指导获取更多信息。
运行前台Service
一个前台service所做的处理被认为是用户正在关注事情并且当内存不足时系统不会考虑杀死它。一个前台service必须在状态条提供一个通知,放在“进行中”标题的下面,并且通知不会消失除非service被停止或从前台移除。
例如,一个音乐播放器从一个service播放音乐,service应该设置运行在前台,因为显然用户关注它的操作。状态条上的通知可能指示当前歌曲并且允许用户打开一个activity和音乐播放器进行交互。
为了让service运行在前台,调用startForeground()。这个方法需要两个参数:一个整型的通知的唯一标识和通知的状态条。例如:1
2
3
4
5
6
7Notification 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()。这个方法需要一个boolean值,表示是否也移除状态条通知。这个方法不会停止service。然而,当service运行在前台时你停止了service,通知也会被移除。
关于通知更多的信息,请查看 Creating Status Bar Notifications。
管理Service生命周期
service的生命周期比activity的要简单的多。然而,你更关注你的service是怎样创建和销毁的,因为一个service可以在用户不知情的情况下运行在后台。
service生命周期——从创建到销毁——有两条不同的路线:
- 启动的service
当其它组件调用startService()时service被创建。然后service无限地运行并且必须通过调用stopSelf()进行停止。其它组件也可以通过调用stopService()停止service。当service停止后,系统会销毁它。
- 绑定的service
当其它组件(客户端)调用bindService()时service被创建。客户端通过IBinder接口和serivice进行通信。客户端可以调用unbindService()关闭连接。多个客户端可以绑定到同一个service并且当它们都解绑后,系统销毁service。(service不需要停止自己。)
这两条路线并不是完全独立的。就是说,你可以绑定到一个使用startService()开启的service。例如,一个后台音乐service可以通过调用startService()用一个标识音乐播放的Intent开启。稍后,可能用户想要控制播放器或获取当前歌曲的信息,一个activity可以通过调用bindService()绑定到service。就像这种情况,stopService() 或 stopSelf()实际上不会停止service直到所有的客户端都解绑。
实现生命周期回调
像activity一样,service也有生命周期回调方法,你可以实现它监视service状态的改变并在合适的时间执行任务。下面这个基本的service演示了生命周期的每个方法:
1 | public class ExampleService extends Service { |
注意:和activity生命周期的回调方法不一样,你不需要调用这些回调方法的父类实现。
图2 service生命周期。图表的左边显示的是当使用startService()创建service时的生命周期,图表的右边显示的是当使用bindService()创建service时的生命周期。
通过实现这些方法,你可以监视service两个嵌套循环的生命周期:
service完整的生命期发生在onCreate()被调用时和onDestroy()返回时之间。和activity一样,一个service在onCreate()做初始化设置,在onDestroy()释放所有持有的资源。例如,一个音乐播放service在onCreate()创建线程播放音乐,在 onDestroy()停止线程。
所有的service都会调用onCreate() 和 onDestroy()方法,通过startService() 或 bindService()创建。service的活动期从调用onStartCommand() 或 onBind()开始。每个方法都会分别处理通过startService() 或 bindService()传递的Intent。
如果service是活动的,活动期结束时间和整个生命期结束时间是相同的(service会继续活动,甚至当onStartCommand()返回后)。如果service是绑定的,当onUnbind()返回时活动期结束。
注意:尽管一个开启的service可以通过stopSelf() 或 stopService()停止,对于service没有相应的回调(没有onStop()回调)。因此,除非service绑定到一个客户端,当service停止时系统会销毁它——可以接收到onDestroy()回调。
图2说明了service典型的回调方法。尽管图分别说明通过startService()创建的和通过bindService()创建的,记住所有的service,无论它是怎样启动的,都可以潜在的允许客户端绑定到它。因此,一个service在最初用onStartCommand()启动时(客户端调用startService())也可以接收onBind()回调(当客户端调用bindService())。
对于创建支持绑定的service更多的信息,查看Bound Services文档,在Managing the Lifecycle of a Bound Service部分包含更多关于onRebind()回调方法的信息。