一)概述
一个Service是没有界面且能长时间运行于后台的应用组件.其它应用的组件可以启动一个服务运行于后台,即使用户切换到另一个应用也会继续运行.另外,一个组件可以绑定到一个service来进行交互,即使这个交互是进程间通讯也没问题.例如,一个aservice可能处理网络事物,播放音乐,执行文件I/O,或与一个内容提供者交互,所有这些都在后台进行.一个service本质上可有两种表现形式:
-
Started
一个service在某个应用组件(比如一个activity)调用startService()时就处于"started"状态(注意,可能已经启动了).一旦运行后,service可以在后台无限期地运行,即使启动它的组件销毁了.通常地,一个startedservice执行一个单一的操作并且不会返回给调用者结果.例如,它可能通过网络下载或上传一个文件.当操作完成后,service自己就停止了
-
Bound
一个service在某个应用组件调用bindService()时就处于"bound"状态.一个boundservice提供一个client-server接口以使组件可以与service交互,发送请求,获取结果,甚至通过进程间通讯进行交叉进行这些交互.一个boundservice仅在有其它应用的组件绑定它时运行.多个应用组件可以同时绑定到一个service,但是当所有的自由竞争组件不再绑定时,service就销毁了
尽管这个文档是把这两种service分开讲的,但你的service可以在这两种方式下工作.—它可以是started(无限期运行)同时也允许绑定.唯一的简单问题是你是否实现了一对回调方法:onStartCommand()允许组件启动它并且onBind()允许绑定
不论你是应用是否启动,或绑定到一个服务或两者都做了,任何应用组件都可以使用service(即使从另一个应用),跟任何组件都可以使用activity一样通过一个Intent启动它.然而,你可以在manifest文件中声明服务为私有,并且阻止另外的应用访问它.这在讲如何于manifest文件中声明service时会详细讲解.
注意:一个service是运行在它所在进程的主线程中的,service不会创建它自己的thread也不会运行于单独的进程(除非你另外指定).这表示,如果你的service想做一些狂耗CPU的工作或阻塞型的操作(比如MP3播放或网络通讯),你必须在service中创建一个新的线程来做那些工作.通过使用一个分离的线程,你将减少"应用没有反应"(ANR)错误并且应用的主线程可以保持activity对用户操作的快速反应.
基础
你应使用一个service还是线程?
一个service是一个在用户不与你的应用交互时依然可以运行于后台的简单组件.所以,只有你需要这样做时才创建一个service.
如果你需要执行的工作不在主线程中,但是只有用户在与你的应用交互时才进行,那么你可能应该创建一个新的线程而不是一个service.例如,如果你想播放一些音乐,但是只在你的activity运行时才播放,你应该在onCreate()中创建一个线程,在onStart()运行这个线程,然后在onStop()中停止它.也可以考虑使用AsyncTask或HandlerThread,来代替传统的线程类.
记住,如果你使用了service,它默认会固定运行于你的应用的主线程,所以你应该在其中创建一个线程来执行耗时或阻塞的操作.
要创建一个service,你必须创建一个Service类(或某个已存在的子类)的子类.在你的实现中,你应覆写一些处理有关service生命期的关键方面的回调方法并且提供一个能让组件绑定到service的机制(如果需要).你应覆写的最重要的回调方法是:
-
onStartCommand()
系统在其它组件比如activity通过调用startService()请求service启动时调用这个方法.一旦这个方法执行,service就启动并且在后台长期运行.如果你实现了它,你需要负责在service完成任务时停止它,通过调用stopSelf()或stopService().(如果你只想提供绑定,你不需实现此方法).
-
OnBind()
当组件调用bindService()想要绑定到service时(比如想要执行进程间通讯)系统调用此方法.在你的实现中,你必须提供一个返回一个IBinder来以使客户端能够使用它与service通讯,你必须总是实现这个方法,但是如果你不允许绑定,那么你应返回null.
-
OnCreate()
系统在service第一次创建时执行此方法,来执行只运行一次的初始化工作(在调用它方法如onStartCommand()或onBind()之前).如果service已经运行,这个方法不会被调用.
-
OnDestroy()
系统在service不再被使用并要销毁时调用此方法.你的service应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等.这是service收到的最后一个调用.
如果一个组件通过调用startService()启动一个service(最终导致onStartCommand()被调用),之后service会保持运行,直到它通过stopSelf()停止自己或另外的组件调用stopService()停止它.
如果一个组件调用bindService()来创建service(onStartCommand()不会被调用),那么service只是运行在绑定期间.一旦service从所有的客户端解除绑定,系统就会杀了它.
Android系统只在内存很少并且必须为具有用户焦点的actvity釋放资源时才会强制停止一个service.如果service是绑定到具有用户焦点的activity上,那么它很难被杀死,并且如果service被声明为运行于前台(后面将讨论),那么它将永不被杀死,除非,如果这个service启动并且长期运行,那么系统将会降低它在后台任务超时列表中的位置然后这个将变成高度易被杀对象—如果你的service被启动,那么它必须被设计为能优雅地处理被系统重启的操作.如果系统杀死了你的service,它会在资源重新可用时立马重启它(但是依赖于你在onStartCommand()中的返回值).关于系统何时杀死一个service的更多信息。
二)创建一个service
在manifest中声明一个service
跟activity以及其它组件一样,你必须在你的应用的manifest文件中声明所有的service们.
要声明你的service,添加一个<service>元素作为<application>元素的儿子.例如:
- <manifest ... >
- ...
- <application ... >
- <service android:name=".ExampleService" />
- ...
- </application>
- </manifest>
有许多属性你可以包含在<service>元素中,比如启动service的权限和service运行所在的进程.android::name属性是哇一必须的,它指定了service类的名字.一旦你发布了你的应用,你不应再改变这个名字,因为如果你改了,你可能使一些通过明确的intent来引用你的service的功能无法运行.
就像一个activity,一个service可以定义intent过滤器来使得其它组件使用明确的intent调用自己.通过声明intent过滤器,你设备上的任意应用中的组件都可以通过给startService()传递匹配的intent来启动你的sevice.
如果你打算只在本应用内使用自己的service,那么你不需指定任何intent过滤器.不使用intent过滤器,你必须使用一个明确指定service的类名的intent来启动你的service.
另外,你也可以通过包含android:exported属性,并指定其值为”false”来保证你的service是私有的.即使你的service使用了intent过滤器,也会起作用.
创建一个"启动的"Service
针对Android1.6或更早的版本:
如果你创建的应用是针对Android1.6或更早版本的,你需要实现onStart()而不是onStartCommand()(在Android2.0中,onStart()被废弃代替之以onStartCommand()).
更多关于如何兼容2.0之前版本的知识,请看onStartCommand()文档.
一个启动的service,在被其它组件调用startService()来启动时,会导致service的onStartCommand()方法被调用.
当一个service被启动后,它的生命期就不再依赖于启动它的组件并且可以独立运行于后台,即使启动它的组件死翘翘了.所以,service应该工作完成后调用stopSelf()自己停止掉,或者其它组件也可以调用stopService()停止service.
一个应用组件,比如一个activity可以通过调用startService()启动service同时传递一个指定service和service所用的数据的Intent,service在方法onStartCommand()中接收这个Intent.
例如,假设一个activity需要保存一些数据到一个在线数据库中.这个activity可以通过传递一个intent给startService()来启动一个service并且把数据传给service来让servcie存储它们.service在onStartCommand()中接收intent,连接到Internet然后执行数据库事物.当事物完成后,service停止自己然后被销毁.
小心:service默认运行在声明它的应用进程的主线程中.所以,如果你的service执行密集运算或阻塞操作并且与跟用户交互的activity位于相同的应用中,这个service将会拉低activity的性能.要避免影响应用的性能,你必须在service中启动一个线程.
传统上,有两个类你可以从它派生来创建"启动的"service:
-
Service
这是所有service的基类.当你派生这个类时,在service中创建一个新的线程来做所有的工作是十分重要的.因为这个service会默认使用你的应用的主线程,这将拉低你的应用中所有运行的activity的性能
-
IntentService
这是一个Service的子类,使用一个工作线程来处理所有的启动请求,一次处理一个.这是你不需你的service同时处理多个请求时的最好选择.你所有要做的就是实现onHandleIntent(),这个方法接收每次启动请求发来的intent,于是你可以做后台的工作.
从IntentService类派生
因为大多数"启动的"service不需要同时处理多个请求,可能从IntentService实现你的service是最好的选择.
IntentService做了以下工作:
-
创建一个默认的工作线程在主线程之外执行所有派发到onStartCommand()的intent.
-
创建一个工作队列,某个时间只传递一个intent到你的onHandleIntent()实现中,于是你不必担心多线程的问题.
-
当所有开始的请求都处理后,停止service,所以你永远不需调用stopSelf().
-
提供onBind()的默认实现,返回null.
-
提供一个onStartCommand()的默认实现,把intent加入到工作队列之后会传给你的onHandleIntent()实现.
以上实现使得你可以仅仅实现onHandleIntent()来做要做的工作即可.(当然,你还是要实现一个小小的构造函数).
下面是一个实现IntentService的例子:
- public class HelloIntentService extends IntentService {
- /**
- * 一个构造函数是必须的,并且你必须调用父类的IntentService(String)以传入工作线程的名字.
- */
- public HelloIntentService() {
- super("HelloIntentService");
- }
- /**
- * IntentService在默认的工作线程中调用这个方法<p> *当这个方法返回后,IntentService停止服务,如果能停止的话.
- */
- @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) {
- }
- }
- }
- }
- }</p>
以上就是你所有需要做的:一个构造函数和一个onHandleIntent()的实现.
如果你决定重写其它的方法,比如onCreate(),onStartCommand(),oronDestroy(),要保证调用父类的对应实现,这样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
从类Service派生
如你在上节所见,使用类IntentService使得你实现一个"开始的"service非常容易.然而,如果你需要你的service以多线程方式执行(而不是使用工作队列),那么你需要从类Service派生来处理每个intent.
相比之下,下面的例子从类Service派生并实现了与上面使用IntentService例子完全相同的工作.也就是在一个线程中序列化的处理每个"开始"请求.
- <span style="font-size:18px;">public class HelloService extends Service {
- private Looper mServiceLooper;
- private ServiceHandler mServiceHandler;
- // 处理从线程收到的消息们
- private final class ServiceHandler extends Handler {
- public ServiceHandler(Looper looper) {
- super(looper);
- }
- @Override
- public void handleMessage(Message msg) {
- // 通常我们在这里做一些工作比如下载一个文件
- // 在我们的例子中,仅仅是睡5秒钟.
- long endTime = System.currentTimeMillis() + 5*1000;
- while (System.currentTimeMillis() < endTime) {
- synchronized (this) {
- try {
- wait(endTime - System.currentTimeMillis());
- } catch (Exception e) {
- }
- }
- }
- // 使用startId停止服务,从而使我们不会在处理
- // 另一个工作的中间停止service
- stopSelf(msg.arg1);
- }
- }
- @Override
- public void onCreate() {
- // 启动运行service的线程.注意我创建了一个分离的线程,因为service通常都是在进程的
- // 主线程中运行,但我们不想让主线程阻塞.我们还把新线程
- // 搞成后台级的优先级,从而减少对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();
- // 对于每个开始请求,发送一消息来开始一次工作,并且把
- // start ID也传过去,所以当完成一个工作时,我们才知道要停止哪个请求.
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- mServiceHandler.sendMessage(msg);
- // 如果我们在这里返回后被被杀死了,重启之.
- 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();
- }
- }
- </span>
如你所见,要做的工作比使用IntentService时多一些.
然而,因为你自己处理每次对onStartCommand()的调用,你可以同时执行多个请求.这个例子并没有那样做,但是如果那是你所需要的,那么你可以为每个请求创建一个新的线程并且立即运行它们(而不是等待上一个请求完成).
注意方法onStartCommand()必须返回一个整数.这个整数描述了在系统杀死它的事件中系统如何继续这个服务(如前面所述,IntentService的默认实现为你处理这些,当然你也能够去改写它).onStartCommand()也返回值必须是下面常量之一:
-
START_NOT_STICKY
如果系统在onStartCommand()返回后杀死了服务,不要重新创建这个service,除非还有挂起的intent需要被传送.这是避免在不必要时运行你的service和当你的应用可以简单重启任何未竟的工作时的最佳选择.
-
START_STICKY
如果系统在onStartCommand()返回后杀死了这个service,会重新创建这个service并且调用onStartCommand(),但是不再重新发送上次最后一个intent,而是使用一个nullintent调用onStartCommand(),除非有一些挂起的intent,在此情况下,这些挂起的intent被派送.这适合于媒体播放器(or或相似也的服务),它不执行命令,但是无限期的运行并等待一个工作.
-
START_REDELIVER_INTENT
如果系统在onStartCommand()返回后杀死了service,重新创建这个service并且使用上次最后一个intent调用onStartCommand().任何挂起的intent都顺序地被派送.这适合于活跃地执行一个工作并且应被立即恢复的服务,比如下载一个文件.
三)开始停止service
开始一个Service
你可以从一个activity或从其它应用的组件通过传递一个Intent(指定了要启动的服务)给startService()启动一个服务.Android系统然后调用service的onStartCommand()方法并且把Intent传递给它.(你永远不能直接调用onStartCommand().)
例如,一个activity可以在调用startService()时使用一个明确的intent开始前文的例子中的service(HelloSevice):
Intentintent = new Intent(this, HelloService.class);
startService(intent);
startService()方法会立即返回然后Android系统调用service的onStartCommand()方法.但是如果service尚没有运行,系统会先调用onCreate(),然后调用onStartCommand().
如果service没有提供绑定功能,传给startService()的intent是应用组件与service之间唯一的通讯方式.然而,如果你希望service回发一个结果,那么启动这个service的客户端可以创建一个用于广播(使用getBroadcast())的PendingIntent然后放在intent中传给service,service然后就可以使用广播来回送结果.
不同的启动请求导致对service的onStartCommand()的不同调用,但停止service的请求只有一个(使用stopSelf()或stopService()).
停止一个service
一个"启动的"service必须管理其自己的生命期.这表示,系统不会停止或销毁这种service,除非内存不够用了并且service在onStartCommand()返回后会继续运行.所以,service必须调用stopSelf()停止自己或由另一个组件调用stopService()来停止它.
一旦通过stopSelf()或stopService()发出了停止请求,系统就会尽可能快地销毁service.
然而,如果你的service同时处理多个对onStartCommand()的请求,那么你不应在处理完一个请求之后就停止service,因为你可能已经又收到了新的启动请求(在第个完成后停止将会结束掉第二个).要避免这个问题,你可以使用stopSelf(int)来保证你的停止请求对应于你最近的开始请求.也就是,当你调用stopSelf(int)时,你传递开始请求的ID(传递给onStartCommand()的startId)给service,如果service在你调用stopSelf(int)之前收到一了个新的开始请求,发现ID不同,于是service将不会停止.
注意:你的应用在完成工作后停止它所有的service是非常重要的.这可以避免浪费系统资源和消耗电量.如果需要,其它的组件可以调用stopService()停止service.即使你为service启用了绑定,你也必须自己停止service,甚至它收到了对onStartCommand()的调用也这样.
创建一个绑定的Service
一个绑定的service是允许应用的组件通过调用bindService()来绑定它以创建一个能长期存在的连接(并且一般不允许组件调用startService()来启动它).
当你的activity或其它组件想与service交互或你的应用想基于IPC的向其它应用提供功能时,你应该创建一个绑定的service.
要创建一个绑定的service,你必须实现回调方法onBind(),还要在其中返回一个IBinder,这个IBinder定义了与service通讯的接口.其它应用组件就可以在之后调用bindService()来接收这个接口并开始调用service的方法.service只在有应用组件绑定到它时才活着,所以当没有组件绑定到它时,系统就会宰了它(你不需去停止一个绑定的service,跟用onStartCommand()启动的service不一样).
要创建一个绑定的service,首先要做的就是定义客户端如何与service通讯的接口.这个接口必须是IBinder的一个实现,并且必须被回调方法onBind()返回.一旦客户端接收到IBinder,它就可以开始与service进行交互.
多个客户端可以一起绑定到一个service.当一个客户端完成与service的交互,它调用unbindService()来解除绑定.一旦不再有任何客户端绑定到service,系统就宰了这个service.
有很多方法来实现一个绑定的service并且这些实现要比"开始的"service难懂得多.
发送通知给用户
一旦开始运行,一个service可以通过Toast通知或状态栏通来通知用户一些事件.
一个toast通知是一个出现在当前窗口表面上并过一会就消失的消息.当一个状态栏通知提供一个带有消息的图标到状态栏,用就可以先定它来执行一些动作(比如启动一个activity).
通常,一个状态栏通知是当一些后台工作(比如一个文件下载完成了)完成后通知用户可以对它进行动作的最佳方式.当用户选择这个通知时,它可以开始一个activity(比如可以查看下载的文件).
四)前台运行与生命期
在前台运行Service
一个前台的service是被用户强烈关注的从而不会在内存低时被系统杀死.前台service必须在状态栏上提供一个通知,这个通知被放在"正在进行"区域中,这表示这个通知不能被解除,除非服务停止了或者从前台移除了.
例如,一个从service播放音乐的音乐播放器,应被设置为前台运行,因为用户会明确地注意它的运行.在状态栏中的通知可能会显示当前的歌曲并且允许用户启动一个activity来与音乐播放器交互.
- 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, notification);
要请求你的service运行于前台,调用startForeground().此方法有两个参数:一个整数唯一的标识一个通知,和这个用于状态栏的通知,例如:
要从前台移除service,调用stopForeground().这个方法有boolean型参数,表明是否也从状态栏删除对应的通知.这个方法不会停掉service.然而,如果你停止了正在前台运行的service,这个通知也会被删除.
注意:方法startForeground()和方法stopForeground()是从Android2.0 (API Level 5)引入的.为了在早期版本是于前台运行你的service,你必须使用以前的那个setForeground()方法—见startForeground()的API文档查看如何提供与旧版本的兼容性.
管理Service的生命期
一个service的生命期比一个activity要简单得多.然而,你依然需要密切关注你的service是如何被创建又是如何被销毁的,因为一个service可以运行于后台而用户看不到它.
service的生命期—从它被创建到它被销毁—有两条路可走:
-
一个"启动的"service
在其它组件调用startService()时创建.然后service就长期运行并且必须调用stopSelf()自己停止自己.另一个组件也可以调用stopService()来停止它.当service停止后,系统就销毁它.
-
一个绑定的service
当另一个组件(一个客户端)调用bindService()时创建.然后客户端通过一个IBinder接口与service通信.客户端可以调用unbindService()停止通信.多个客户端可以绑定到同一个service并且当所有的客户端都解除绑定后,系统就销毁掉这个service.(service不需停止自己.)
这两条路并不是完全分离的.也就是,你是可以绑定到用startService()启动的service的.例如,一个后台音乐service在通过传入指明要播放的音乐的intent来调用startService()后启动.之后,当用户想对播放器进行一些操作或要获取当前歌曲的信息时,一个activity可以通过调用bindService()绑定到service.在此情况下,stopService()或stopSelf()不会真正的停止service,除非所有的客户端都取消绑定了.
实现生命期回调方法
就像activity,service也具有生命期回调方法,用它们你可以监视service的状态的变化并且在合适的时机做一些工作.下面的框架代码演示了每个生命期方法的实现:
- public class ExampleService extends Service {
- int mStartMode; // 表明在service被杀后的行为
- IBinder mBinder; // 客户端绑定到的接口
- boolean mAllowRebind; // 表明onRebind是否应被使用
- @Override
- public void onCreate() {
- // The service is being created
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- // service 正在启动,在调用startService()期间被调用
- return mStartMode;
- }
- @Override
- public IBinder onBind(Intent intent) {
- // 一个客户端通过bindService()绑定到这个service
- return mBinder;
- }
- @Override
- public boolean onUnbind(Intent intent) {
- // 所有的客户端使用unbindService()解除了绑定
- return mAllowRebind;
- }
- @Override
- public void onRebind(Intent intent) {
- // 一个客户端在调用onUnbind()之后,正使用bindService()绑定到service
- }
- @Override
- public void onDestroy() {
- // service不再被使用并将被销毁
- }
- }
图 2.service的生命期.左图显示了用startService()创建的service的生命期,右图显示了用bindService()创建的service的生命期.
通过实现这些方法们,你可以监视service生命期的两个嵌套循环:
-
service的一生介于调用onCreate()的时间和onDestroy()返回的时间.就像activity,service在onCreate()中做初始化工作并且onDestroy()中释放所有资源.例如,一个音乐播放service可以在onCreate()中创建音乐播放的线程,之后在onDestroy()中停止这个线程.
onCreate()和onDestroy()方法被所有的service调用,不管它们通过startService()还是bindService()创建.
-
service的活动生命期开始于onStartCommand()或onBind()被调用时.每个方法各自处理传入的Intent.
如果service是"启动的",活动生命期就结束于整个生命期的结束时(即使onStartCommand()返回后,service依然处于活动状态).如果是一个绑定的service,它的活动生命期在onUnbind()返回后结束.
注:尽管一个"启动的"service在调用stopSelf()或stopService()时结束,但并没有单独的回调对应这些停止方法(没有类似于onStop()的回调).所以,除非service被绑定到一个客户端,系统就会在停止时销毁service—onDestroy()是唯一收到的回调.
图 2演示了service的典型回调.尽管图示分开了通过startService()和bindService()创建的service,但记住任何service,不管它是怎样启动的,都是可能允许绑定的.所以一个从onStartCommand()启动的service(客户端调用了startService())仍可以接收onBind()调用(当客户端调用bindService()时).
五)Android bound service :基础
绑定的service是在一个客户端-服务端接口中的服务.绑定的ervice允许组件(比如activities)绑定到service,发送请求,接收回应,甚至执行进程间通讯(IPC).绑定的service一般只生存于为其它应用组件服务其间并且不会永远行于后台.
本文档向你展示了如何创建一个绑定的service,包括如何从其它应用组件绑定到service.然而,你应该也去参考Service的文档来学习更多通用知识,比如如何从servcie发出通知,如何设置service为前台运行,等等.
绑定到一个"运行的"Service
如同在Service文档中所述,你可以创建一个既是"运行的"又是"绑定的"的service.即,这个service可以通过调用startService()启动,这使得这个service可以永远运行,同时也允许一个客户端通过调用indService()绑定到它.
如果你允许你的service是"运行的"和"绑定的",那么当service启动后,系统不会在所有客户端解除绑定后销毁service,而是,你必须通过调用stopSelf()或stopService()显示地停止这个service
尽管你通常应该实现onBind()或onStartCommand(),但有时需要同时实现两者.例如,一个音乐播放器可能发现允许它的service永久运行同时又能被绑定是很有用的.这样一来,一个activity可以启动这个service来播放一些音乐并且即使用户离开了这个应用,播放也会继续.
基础知识
一个绑定的service是一个允许应用绑定然后进行交互的类Service的实现.要为service提供绑定,你必须实现onBind()回调方法.此方法返回一个IBinder对象,此对象定义了客户端可以用来与service交互的程序接口.
一个客户端可以通过调用bindService()绑定到service.当它这样做时,它必须提供了一个ServiceConnection的实现.这个实现用于监视客户端与service的连接.bindService()方法会立即返回并且不会返回任何值,但当Android系统创建客户端与service之间的连接时,它调用ServiceConnection的onServiceConnected()来传送客户端用来与servcie通信的IBinder.
多个客户端可以同时连接到一个service.然而,系统只在第一个客户端绑定时才调用你的service的onBind()方法来接收IBinder.之后系统把同一个IBinder传给其它客户端,所以不会再调用onBind().
当最后一个客户端取消绑定到service时,系统销毁这个service(除非这个service同时也是一个"运行的"service).
当你实现你的绑定的service,最重要的部分是定义你的onBind()回调方法所返回的接口.有许多不同的方法可以定义你的service的IBinder接口,并且下面的章节会讨论每种技术.
创建一个绑定的Service
当创建一个提供绑定的service时,你必须提供一个客户端用来与service交互的IBinder.有三种方式你可以定义这个接口:
-
从类Binder派生
如果你的service是你自己应用的私有物,并且与客户端运行于同一个进程中(一般都这样),你应该通过从类Binder派生来创建你的接口并且从onBind()返回一它的实例.客户端接收这个Binder然后使用它来直接操作所实现的Binder甚至Service的公共接口.
当你的service仅仅是一个后台工作并且仅服务于自己的应用时,这是最好的选择.唯一使你不能以这种方式创建你的接口的理由就是你的service被其它应用使用或者是跨进程的.
-
使用一个Messenger
如果你需要你的接口跨进程工作,你可以为service创建一个带有Messager的接口.在此方式下,service定义一个Handler来负责不同类型的Message对象.这个Handler是Messenger可以与客户端共享一个IBinder的基础,它允许客户端使用Message对象发送命令给servic.客户端可以定义一个自己的Messenger以使service可以回发消息.
这是执行IPC的最简单的方法,因为Messenger把所有的请求都放在队列中依次送入一个线程中,所以你不必把你的service设计为线程安全的
-
使用AIDL
AIDL(Android接口定义语言)执行把对象分解为操作系统能够理解并能跨进程封送的基本体以执行IPC的所有的工作.上面所讲的使用一个Messenger,实际上就是基于AIDL的.就像上面提到的,Messenger在一个线程中创建一个容纳所有客户端请求的队列,使用service一个时刻只接收一个请求.然而,如果你想要你的service同时处理多个请求,那么你可以直接使用AIDL.在此情况下,你的service必须是多线程安全的.
要直接使用AIDL,你必须创建一个.aidl文件,它定义了程序的接口.AndroidSDK工具使用这个文件来生成一个实现接口和处理IPC的抽象类,之后你在你的service内派生它.
注:大多数应用不应使用AIDL来处理一个绑定的service,因为它可能要求有多线程能力并且导致实现变得更加复杂.同样的,AIDL也不适合于大多数应用并且本文档不会讨论如何在你的service中使用它.如果你确定你需要直接使用AIDL,请看AIDL的文档
如果你的service仅被自己的应用使用并且不需跨进程工作,那么你可以实现你自己的Binder类使得你的客户端能直接使用service的公开接口方法.
注:这只在客户端和service位于同一应用和同一进程中时才能工作,其实大多数都是这种情况.例如,在一个音乐应用需要把它的activity绑定到它自己的播放音乐的后台service时,这种方式就会很好地工作.
下面是如何建立它:
-
在你的service中,创建一个Binder实例,提供以下三种功能之一:
-
Binder包含一些可供客户端调用的公开方法.
-
返回当前的Service实例,它具有一些客户端可以调用的公开方法.
-
或者,返回另一个类的实例,这个类具有客户端可调用的公开方法并托管于service.
-
在回调方法onBind()中返回这个Binder的实例.
-
在客户端,从回调方法onServiceConnected()中接收这个Binder并使用1中所述的公开方法调用绑定service.
注:service和客户端必须位于同一应用的理由是这样可以使客户端正确地转换返回的对象并调用它的公开方法.service和客户端必需要位于同一个进程中,因为这样就不必执行跨进程的封送处理了.
例如,这下面这个service提供让客户端通过一个Binder实现调用service中的方法的功能:
- public class LocalService extends Service {
- // Binder given to clients
- private final IBinder mBinder = new LocalBinder();
- // Random number generator
- private final Random mGenerator = new Random();
- /**
- * 用于客户端Binder的类.因为我们我们知道这个
- * service永远运行在与客户端相同的进程中,所以我们不需要处理IPC.
- */
- public class LocalBinder extends Binder {
- LocalService getService() {
- // 返回本service的实例到客户端,于是客户端可以调用本service的公开方法
- return LocalService.this;
- }
- }
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
- /**客户端所要调用的方法*/
- public int getRandomNumber() {
- return mGenerator.nextInt(100);
- }
- }<span style="color:#000000;">
- </span>
类LocalBinder提供提供了getService()方法使得客户端能取得当前LocalService的实例.于是客户端就可以调用service中的公开方法了.比如,客户端可以调用service的getRandomNumber()方法.
下面是一个绑定到LocalService并且在按钮按下时调用getRandomNumber()的actvity的例子:
- public class BindingActivity extends Activity {
- LocalService mService;
- boolean mBound = false;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- @Override
- protected void onStart() {
- super.onStart();
- // 绑定到类LocalService的实例
- Intent intent = new Intent(this, LocalService.class);
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
- @Override
- protected void onStop() {
- super.onStop();
- // 从service解除绑定
- if (mBound) {
- unbindService(mConnection);
- mBound = false;
- }
- }
- /** 当按钮按下时调用(在layout文件中定义的button并用android:onClick 属性指定响应到本方法) */
- public void onButtonClick(View v) {
- if (mBound) {
- // 调用LocalService的一个方法
- // 然而,如果这个调用中有挂起操作,那么这个请求应发
- // 生在另一个线程来避免拉低activity的性能.
- int num = mService.getRandomNumber();
- Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
- }
- }
- /** 定义service绑定的回调,传给bindService() 的*/
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className,
- IBinder service) {
- //我们已经绑定到了LocalService,把IBinder进行强制类型转换并且获取LocalService实例.
- LocalBinder binder = (LocalBinder) service;
- mService = binder.getService();
- mBound = true;
- }
- @Override
- public void onServiceDisconnected(ComponentName arg0) {
- mBound = false;
- }
- };
- }
上面的例子展示了客户端如何使用一个ServiceConnection的实例和onServiceConnected()方法绑定到service.
注:上面的例子没有明确地从service解除绑定.但是所有的客户端都应该在合适的时候解除绑定(比如当activity暂停时).
Android bound service :使用Messenger
如果你需要你的service与远程进程通信,那么你可以使用一个Messenger来为你的service提供接口.此技术使用不必使用AIDL就能执行进程间通信(IPC).
下面是如何使用Messenger的概要:
-
service实现一个接收从客户端的每个调用引起的回调的Handler.
-
Handler被用来创建一个Messenger对象(它是Handler的一个引用).
-
Messenger创建一个从service的onBind()返回给客户端的IBinder.
-
客户端使用IBinder来实例化这个Messenger(它引用到service的Handler),客户端用它来向service发送Message.
-
service在它的Handler中接收每个消息—具体的,是在handleMessage()方法中.
这此方式下,service中没有能让客户端调用的方法,客户端传送的是service在它的Handler中接收的"消息"(Message对象).
下面是一个service使用Messenger接口的例子:
- public class MessengerService extends Service {
- /** 让service 显示一个消息的命令 */
- static final int MSG_SAY_HELLO = 1;
- /**
- * 处理从客户端来的消息.
- */
- class IncomingHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SAY_HELLO:
- Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
- break;
- default:
- super.handleMessage(msg);
- }
- }
- }
- /**
- * 我们发布给客户端使它能向IncomingHandler 发送消息的的对象
- */
- final Messenger mMessenger = new Messenger(new IncomingHandler());
- /**
- * 当绑定到service,我们返回指向我们的messenger的接口
- */
- @Override
- public IBinder onBind(Intent intent) {
- Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
- return mMessenger.getBinder();
- }
- }
注意Handler中的handleMessage()方法是service接收消息并进行处理的地方.
客户端所有要做的事就是创建一个基于service返回的IBinder的Messenger对象并且使用它的send()发送一个消息.如,下面是一个actvity绑定到service并且传送MSG_SAY_HELLO命令给service的例子:
- public class ActivityMessenger extends Activity {
- /** 与service通信的Messenger */
- Messenger mService = null;
- /** 表明我们是否已绑定到service的标记 */
- boolean mBound;
- /**
- *与service的主接口交互的类
- */
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- // 当与service的连接已经建立时被调用.给了我们可以用来
- //与service交互的对象.我们正在使用一个Messenger与service通信,
- // 所以在这里我们从原始IBinder 对象获取一个客户端的Messenger的代表
- mService = new Messenger(service);
- mBound = true;
- }
- public void onServiceDisconnected(ComponentName className) {
- // 当与service的连接意外断开时被调用-- 也就是,service的进程崩溃了
- mService = null;
- mBound = false;
- }
- };
- public void sayHello(View v) {
- if (!mBound) return;
- // 创建并发送一个消息给service
- Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
- try {
- mService.send(msg);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- @Override
- protected void onStart() {
- super.onStart();
- // 绑定到service
- bindService(new Intent(this, MessengerService.class), mConnection,
- Context.BIND_AUTO_CREATE);
- }
- @Override
- protected void onStop() {
- super.onStop();
- // Unbind from the service
- if (mBound) {
- unbindService(mConnection);
- mBound = false;
- }
- }
- }
注意这个例子没有演示service如何回应客户端.如果你想让service回应客户端,那么你需要在客户端也创建一个Messanger.然后当客户端接收到onServiceConnected()回调时,它发送一个息给service,这个消息包含了客户端的Messenger对象,它作为send()方法的replyTo参数.
比较 AIDL
当你需要执行IPC时,为你的接口使用一个Messenger比使用AIDL实现它简单,因为Messenger把所有对service的调用入队列,一个纯AIDL接口并行发送请求到service,这样就必须用多线程来处理了.
对于大多数应用,service不需使用多线程,所以使用一个Messenger允许service在一个时刻只处理一个请求.如果使用多线程对你的service很重要,那么你应使用AIDL来定义你的接口.
Android bound service 详解四:service绑定及生命期
绑定到一个Service
应用组件(客户端)可以调用bindService()绑定到一个service.Android系统之后调用service的onBind()方法,它返回一个用来与service交互的IBinder.
绑定是异步的.bindService()会立即返回,它不会返回IBinder给客户端.要接收IBinder,客户端必须创建一个ServiceConnection的实例并传给bindService().ServiceConnection包含一个回调方法,系统调用这个方法来传递要返回的IBinder.注:只有activities,services,和contentproviders可以绑定到一个service—你不能从一个broadcastreceiver绑定到service.
所以,从你的客户端绑定到一个service,你必须:
-
1实现ServiceConnection.
你的实现必须重写两个回调方法:
-
onServiceConnected()
系统调用这个来传送在service的onBind()中返回的IBinder.
-
OnServiceDisconnected()
Android系统在同service的连接意外丢失时调用这个.比如当service崩溃了或被强杀了.当客户端解除绑定时,这个方法不会被调用.
-
-
2调用bindService(),传给它ServiceConnection的实现.
-
3当系统调用你的onServiceConnected()方法时,你就可以使用接口定义的方法们开始调用service了.
-
4要与service断开连接,调用unbindService().
当你的客户端被销毁,它将从service解除绑定,但是你必须总是在你完成与service的交互时或当你的activity暂停于是service在不被使用时可以关闭此两种情况下解除绑定.(下面会讨论更多在适当的时候绑定和解除绑定的问题.)
例如,下面是前一篇"派生Binder类"中创建的代码片段,它把客户端连接到了service.所有需要做的就是把返回的IBinder强制转换到LocalBinder类并且请求LocalService实例:
- LocalService mService;
- private ServiceConnection mConnection = new ServiceConnection() {
- // 当与service的连接建立后被调用
- public void onServiceConnected(ComponentName className, IBinder service) {
- // Because we have bound to an explicit
- // service that is running in our own process, we can
- // cast its IBinder to a concrete class and directly access it.
- LocalBinder binder = (LocalBinder) service;
- mService = binder.getService();
- mBound = true;
- }
- // 当与service的连接意外断开时被调用
- public void onServiceDisconnected(ComponentName className) {
- Log.e(TAG, "onServiceDisconnected");
- mBound = false;
- }
- };
使用这个ServiceConnection,客户端可以绑定到一个service,通过把它传给bindService().例如:
Intentintent = new Intent(this, LocalService.class);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
-
第一个bindService()的参数是一个明确指定了要绑定的service的Intent.
-
第二个参数是ServiceConnection对象.
-
第三个参数是一个标志,它表明绑定中的操作.它一般应是BIND_AUTO_CREATE,这样就会在service不存在时创建一个.其它可选的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,不想指定时设为0即可.
补充事项
下面是一些关于绑定到service的重要事项:
-
你总是需要捕获DeadObjectException异常.它会在连接被打断时抛出.这是被远程方法抛出的唯一异常.
-
对象引用计数是跨进程的作用的.
-
你应该在客户端的生命期内使绑定和解除绑定配对进行,例如:
-
如果你需要在你的activity可见时与service交互,你应该在onStart()绑定并在onStop()中解除绑定.
-
如果你想让你的activity即使在它停止时也能接收回应,那么你可以在onCreate()中绑定并在onDestroy()中解除绑定.注意这意味着你的activity需要使用在自己整个运行期间使用service(即使位于后台),所以如果service在另一个进程中,那么你增加了这个进程的负担而使它变得更容易被系统杀掉.
-
注:你一般不应该在你的activity的onResume()和onPause()中绑定和解除绑定到service,因为这些回调方法,出现在每个生命期变化中,并且你需要使发生在这些变化中的处理最小化.还有,如果你应用中的多个activity绑定到同一个service,并且有一个变化发生在其中两个activity之间,service可能在当前activity解除绑定(pause中)和下一个绑定前(rusume中)被销毁又重建.
管理BoundService的生命期
当一个service的所有客户端都解除绑定,Android系统就销毁它(除非它是从onStartCommand()启动).如果你的service是一个纯boundservice,你不需管理它的生命期—Android系统会为你管理它.
然而,如果你选择了实现onStartCommand()回调方法,那么你必须明确地停止service,因为service现在被认为是"开始的".在此情况下,service会一直运行,直到service使用stopSelf()停止它自己或另外的组件调用了stopService()停止了它,不管是否有客户端绑定了它.
另外,如果你的service已经启动并且接受绑定,那么当系统调用你的onUnbind()方法,你可以选择返回true表示你想在客户端下一次绑定到service时接受一个对onRebind()的调用(而不是一个对onBind()的调用).onRebind()返回void,但是客户端依然会在它的onServiceConnected()回调中接收到IBinder.下图演示了这种生命期的逻辑:

一个已开始并已绑定的service的生命期
转自:http://blog.youkuaiyun.com/nkmnkm/article/details/7367227