标签:
英文原文:http://developer.android.com/guide/components/bound-services.html
采集(更新)日期:2014-12-24
原博客:http://blog.sina.com.cn/s/blog_48d4913001010696.html
Bound 类型的服务是客户端-服务器模式的服务端。Bound 类型的服务允许组件(比如 Activity)对其进行绑定、发送请求、接收响应、甚至进行进程间通信(IPC)。 Bound 类型的服务一般仅存活于为其他应用程序组件服务期间,而非一直在后台保持运行。
本文展示了如何创建一个 Bound 类型的服务,包括其他应用程序组件如何绑定该服务。不过,通常你还应该参考 服务 文档以获取关于服务的更多信息,比如如何从服务中发送通知、如何将服务设置为前台运行等等。
Bound 类型的服务是 Service 类的一种实现,它允许其他应用程序与其绑定并交互。 要让服务支持绑定,必须实现 onBind() 回调方法。这个方法返回一个 IBinder 对象,此对象定义了客户端与服务进行交互时所需的编程接口。
正如 Services 一文中所述,可以创建一个既是 Started 类型又是 Bound 类型的服务。也就是说,服务能通过调用 startService() 来启动,这使它可以持续运行,同时它也允许客户端通过调用 bindService() 来与之绑定。
如果某个服务确实既是 Started 类型又是 Bound 类型,那么该服务启动后,系统将不会在所有客户端解除绑定时销毁它。 而是必须通过调用 stopSelf() 或 stopService() 显式终止此服务。
虽然通常应该只实现 onBind() 或 onStartCommand() 中的一个,但有时确实两者都需要实现。 比如,音乐播放器的服务也许就需要同时实现后台运行和支持绑定。 这样,某个 Activity 就可以启动服务来播放音乐,并且音乐会一直播放下去,即使用户离开该应用也没关系。 以后用户再返回该应用时,这个 Activity 可以绑定播放服务来重新获得播放控制权。
请务必阅读 管理 Bound 类型服务的生命周期章节,以获取有关绑定 Started 服务时的生命周期的更多信息。
客户端可以通过调用 bindService() 方法来绑定服务。在调用时,必须提供一个 ServiceConnection 的实现代码,用于监控与服务的联接。 bindService() 将会立即返回,且没有返回值。但是 Android 系统在创建客户端与服务之间的联接时,会调用 ServiceConnection 中的 onServiceConnected() 方法,并传递一个 IBinder ,客户端将用它与服务进行通信。
多个客户端可以同时联接到一个服务上。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来获取 IBinder 。然后,系统会向后续请求绑定的客户端发送这个 IBinder ,而不再调用 onBind() 。
当最后一个客户端解除绑定后,系统会销毁服务(除非服务同时是通过 startService() 启动的)。
在实现自己的 Bound 类型的服务时,最重要的工作就是定义 onBind() 回调方法所返回的接口。定义服务的 IBinder 接口的途径有好几种,后续章节将会对每种技术进行介绍。
在 创建支持绑定的服务时,必须提供一个 IBinder ,用作客户端和服务间进行通信的编程接口。定义这类接口的途径有三种:
如果服务只是为某个应用程序执行一些后台工作,那这就是最合适的方式。不采用这种方式来创建接口的理由只有一个,那就是服务要被其他应用程序使用或者要跨多个进程使用。
这是进行进程间通信(IPC)最为简便的方式,因为 Messenger 会把所有的请求放入一个独立线程中的队列,这样就不一定非要把服务设计为线程安全模式了。
要直接使用 AIDL,必须创建一个.aidl 文件,其中定义了编程的接口。 Android SDK 工具将用此文件来生成一个抽象类(abstract class),其中实现了接口及对 IPC 的处理,然后就可以在自己的服务中扩展该类。
注意: 绝大多数应用程序都不应该用 AIDL 来创建 Bound 类型的服务,因为这可能要求多线程处理能力,并会让代码变得更为复杂。 因此,AIDL 对绝大多数应用程序都不适用,本文也不会涉及如何在服务中使用它的内容。 如果确实需要直接使用 AIDL,请参阅 AIDL 文档。
如果服务只用于本地应用程序并且不需要跨进程工作,那只要实现自己的 Binder 类即可,这样自己的客户端就能直接访问服务中的公共方法了。
注意: 仅当客户端和服务位于同一个应用程序和进程中,这也是最常见的情况,这种方式才会生效。 比如,一个音乐应用需要把一个 Activity 绑定到它自己的后台音乐播放服务上,采用这种方式就会很不错。
以下是设置步骤:
注意: 服务和客户端之所以必须位于同一个应用程序中,是为了让客户端能够正确识别(cast)返回的对象并调用其 API。 服务和客户端也必须位于同一个进程中,因为这种方式不能执行任何跨进程的序列化(marshalling)操作。
比如,以下是一个服务的示例,它通过实现一个 Binder 供客户端访问其内部方法:
1 public class LocalService extends Service { 2 // 给客户端的Binder 3 private final IBinder mBinder = new LocalBinder(); 4 // 生成随机数 5 private final Random mGenerator = new Random(); 6 7 /** 8 * 用于客户端Binder的类。 9 * 因为知道本服务总是运行于与客户端相同的进程中,我们就不需要用IPC进行处理。 10 */ 11 public class LocalBinder extends Binder { 12 LocalService getService() { 13   // 返回 LocalService 本身,以便客户端调用内部的公共方法 14 return LocalService.this; 15 } 16 } 17 18 @Override 19 public IBinder onBind(Intent intent) { 20 return mBinder; 21 } 22 23 /** 供客户端调用的方法 */ 24 public int getRandomNumber() { 25 return mGenerator.nextInt(100); 26 } 27 }
LocalBinder 为客户端提供了 getService() 方法,用于返回当前 LocalService 的实例。 这就使得客户端能够调用服务中的公共方法。比如,客户端可以调用服务中的 getRandomNumber() 。
以下是一个绑定到 LocalService 的 Activity,当点击按钮时,它会调用 getRandomNumber():
1 public class BindingActivity extends Activity { 2 LocalService mService; 3 boolean mBound = false; 4 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.main); 9 } 10 11 @Override 12 protected void onStart() { 13 super.onStart(); 14 // 绑定到LocalService 15 Intent intent = new Intent(this, LocalService.class); 16 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 17 } 18 19 @Override 20 protected void onStop() { 21 super.onStop(); 22 // 与服务解除绑定 23 if (mBound) { 24 unbindService(mConnection); 25 mBound = false; 26 } 27 } 28 29 /** 当按下按钮时调用(该按钮在layout文件中利用android:onClick属性与本方法关联 */ 30 public void onButtonClick(View v) { 31 if (mBound) { 32 // 调用LocalService中的方法。 33 // 不过,如果该调用会导致某些操作的挂起,那么调用应该放入单独的线程中进行, 34 // 以免降低activity的性能。 35 int num = mService.getRandomNumber(); 36 Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); 37 } 38 } 39 40 /** 定义服务绑定时的回调方法,用于传给bindService() */ 41 private ServiceConnection mConnection = new ServiceConnection() { 42 43 @Override 44 public void onServiceConnected(ComponentName className, 45 IBinder service) { 46 // 我们已经绑定到LocalService了,对IBinder进行类型转换(cast)并获得LocalService对象的实例 47 LocalBinder binder = (LocalBinder) service; 48 mService = binder.getService(); 49 mBound = true; 50 } 51 52 @Override 53 public void onServiceDisconnected(ComponentName arg0) { 54 mBound = false; 55 } 56 }; 57 }
上述例子展示了客户端如何利用 ServiceConnection 和 onServiceConnected() 回调方法绑定到服务。下一节将给出更多有关服务绑定过程的信息。
注意: 上述例子并没有给出解除绑定的代码,但所有的客户端都应该适时地解除绑定(比如当 Activity 进入暂停状态时)。
更多示例代码,请参阅 ApiDemos 中的 LocalService.java 类和 LocalServiceActivities.java 类。
如果服务需要与远程进程进行通信,可以用一个 Messenger 来提供该服务的接口。这样无需使用 AIDL 就能实现进程间通信(IPC)。
以下概括了 Messenger 的使用方法:
通过这种方式,客户端不需要调用服务息”( Message 对象),服务则接收位于 Handler 中的这个消息。
以下是服务使用 Messenger 做为接口的简单示例:
1 public class MessengerService extends Service { 2 /** 发送给服务的用于显示信息的指令*/ 3 static final int MSG_SAY_HELLO = 1; 4 5 /** 6 * 从客户端接收消息的Handler 7 */ 8 class IncomingHandler extends Handler { 9 @Override 10 public void handleMessage(Message msg) { 11 switch (msg.what) { 12 case MSG_SAY_HELLO: 13 Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show(); 14 break; 15 default: 16 super.handleMessage(msg); 17 } 18 } 19 } 20 21 /** 22 * 向客户端公布的用于向IncomingHandler发送信息的Messager 23 */ 24 final Messenger mMessenger = new Messenger(new IncomingHandler()); 25 26 /** 27 * 当绑定到服务时,我们向Messager返回接口, 28 * 用于向服务发送消息 29 */ 30 @Override 31 public IBinder onBind(Intent intent) { 32 Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show(); 33 return mMessenger.getBinder(); 34 } 35 }
请注意 Handler 中的 handleMessage() 方法,这里是服务接收输入消息 Message 的地方,也是根据数字 what 来决定要执行什么操作的地方。
客户端要做的全部工作就是根据服务返回的 IBinder 创建一个 Messenger ,并用 send() 方法发送一个消息。例如,以下是一个 Activity 示例,它绑定到上述服务,并向服务发送 MSG_SAY_HELLO 消息:
1 public class ActivityMessenger extends Activity { 2 /** 用于和服务通信的Messenger*/ 3 Messenger mService = null; 4 5 /** 标识我们是否已绑定服务的标志 */ 6 boolean mBound; 7 8 /** 9 * 与服务的主接口进行交互的类 10 */ 11 private ServiceConnection mConnection = new ServiceConnection() { 12 public void onServiceConnected(ComponentName className, IBinder service) { 13 // 与服务建立联接后将会调用本方法, 14 // 给出用于和服务交互的对象。 15 // 我们将用一个Messenger来与服务进行通信, 16 // 因此这里我们获取到一个原始IBinder对象的客户端实例。 17 mService = new Messenger(service); 18 mBound = true; 19 } 20 21 public void onServiceDisconnected(ComponentName className) { 22 // 当与服务的联接被意外中断时——也就是说服务的进程崩溃了, 23 // 将会调用本方法。 24 mService = null; 25 mBound = false; 26 } 27 }; 28 29 public void sayHello(View v) { 30 if (!mBound) return; 31 // 创建并向服务发送一个消息,用到了已约定的‘what‘值 32 Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); 33 try { 34 mService.send(msg); 35 } catch (RemoteException e) { 36 e.printStackTrace(); 37 } 38 } 39 40 @Override 41 protected void onCreate(Bundle savedInstanceState) { 42 super.onCreate(savedInstanceState); 43 setContentView(R.layout.main); 44 } 45 46 @Override 47 protected void onStart() { 48 super.onStart(); 49 // Bind to the service 50 bindService(new Intent(this, MessengerService.class), mConnection, 51 Context.BIND_AUTO_CREATE); 52 } 53 54 @Override 55 protected void onStop() { 56 super.onStop(); 57 // Unbind from the service 58 if (mBound) { 59 unbindService(mConnection); 60 mBound = false; 61 } 62 } 63 }
请注意,上述例子中没有给出服务是如何响应客户端的。如果需要服务进行响应,那还要在客户端创建一个 Messenger 。然后,当客户端接收到 onServiceConnected() 回调后,它再发送一个消息 Message 给服务,消息的 send() 方法中的 replyTo 参数里包含了客户端的 Messenger 。
在 MessengerService.java (服务)和 MessengerServiceActivities.java (客户端)例程中,展示了如何双向发送消息示例。
应用程序组件(客户端)可以通过调用 bindService() 来绑定服务。然后 Android 系统会调用服务的 onBind() 方法,返回一个用于和服务进行交互的 IBinder 。
绑定是异步进行的。 bindService() 将立即返回,并不会向客户端返回 IBinder 。为了接收 IBinder ,客户端必须创建一个 ServiceConnection 的实例,并把它传给 bindService() 。 ServiceConnection 包含了一个回调方法,系统会调用该方法来传递客户端所需的那个 IBinder 。
注意: 只有 Activity、服务和 Content Provider 才可以绑定服务——不能从广播接收器(Broadcast Receiver)中绑定服务。
因此,要把客户端绑定到服务上,必须做到:
实现代码必须重写两个回调方法:
当客户端被销毁时,与服务的绑定也将解除。但与服务交互完毕后,或者 Activity 进入暂停状态时,都应该确保解除绑定,以便服务能够在用完后及时关闭。 (绑定和解除绑定的合适时机将在后续章节中继续讨论。)
例如,以下代码段将客户端与前面 扩展Binder类 创建的服务联接,而要做的全部工作就是把返回的 IBinder 转换(cast)为 LocalService 类,并获取 LocalService 的实例:
1 LocalService mService; 2 private ServiceConnection mConnection = new ServiceConnection() { 3 // 与服务的联接建立之后将会调用 4 public void onServiceConnected(ComponentName className, IBinder service) { 5 // 因为我们已经与明显是运行于同一进程中的服务建立了联接, 6 // 我们就可以把它的IBinder转换为一个实体类并直接访问它。 7 LocalBinder binder = (LocalBinder) service; 8 mService = binder.getService(); 9 mBound = true; 10 } 11 12 // 与服务的联接意外中断时将会调用 13 public void onServiceDisconnected(ComponentName className) { 14 Log.e(TAG, "onServiceDisconnected"); 15 mBound = false; 16 } 17 };
利用这个 ServiceConnection ,客户端就能够把它传入 bindService() 完成与服务的绑定。例如:
1 Intent intent = new Intent(this, LocalService.class); 2 bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
以下是有关绑定服务的一些重要注意事项:
注意: 通常不得在 Activity 的 onResume() 和 onPause() 方法中进行绑定和解除绑定,因为这两个回调方法在每次切换生命周期状态时都会发生,方法里的工作应该尽可能少。 而且,如果应用程序中有多个 Activity 都绑定到同一个服务上,则在两个 Activity 间切换时都会发生状态转换, 因为当前 Activity 解除绑定(在暂停时)后,紧接着下一个 Activity 又会进行绑定(恢复时),所以服务也许在销毁后马上就要重建。 (这种 Activity 的状态转换、多个 Activity 间的生命周期协作在 Activity 文档中介绍。)
更多展示绑定服务的示例代码,请参阅 ApiDemos中的 RemoteService.java 类 。
当所有的客户端都与某个服务解除了绑定,Android 系统就会销毁该服务(除非它同时又是用 onStartCommand() 启动的)。因此,如果某个服务就是一个纯粹的 Bound 类服务,那就不需要去管理它的生命周期 —— Android 系统会根据是否存在已绑定的客户端来自动管理。
不过,如果实现了 onStartCommand() 回调方法,那么就必须显式地终止服务,因为此服务现在已经被视为Started类型了。 在这种情况下,无论是否还存在与其绑定的客户端,此服务都会运行下去,直至自行用 stopSelf() 终止或由其他组件调用 stopService() 来终止。
此外,如果某个服务是 Started 类型的且允许被绑定,那么系统调用其 onUnbind() 方法时,可以选择返回 true。这样做的结果就是,下次客户端绑定时将会收到 onRebind() 调用(而不是收到 onBind() 调用)。 onRebind() 的返回值是 void(即无返回值),但客户端仍可以在它的 onServiceConnected() 回调方法中收到 IBinder 。下面的图1展示了这种生命周期的运行逻辑。
关于 Started 类型服务的生命周期的更多信息,请参阅 服务 文档。
标签:
原文地址:http://www.cnblogs.com/popapa/p/android_bound-services.html