bound service是CS接口中的服务端。一个bound service允许组件(例如activity)与它绑定,发送请求,接受回应甚至执行IPC。一般情况下,一个bound service只有在服务其余应用组件的时候才存活而不会单独的在后台运行。
基础
bound service实现了service的接口,允许其他组件与它绑定并与它交互。你必须实现onBind回调方法来给service提供绑定。此方法返回一个IBinder对象,该对象定义了客户端可用于与service交互的编程接口。
一个客户端可以调用bindService
来与service绑定。这时客户端必须提供一个ServiceConnection
的实现来监控与service的连接。bindService
方法会直接返回,不带任何值。但是如果Android系统创建了客户端与service之间的连接,它会调用ServiceConnection
的onServiceConnected
方法来传递一个IBinder
对象给客户端,这个IBinder
可以用来与service通讯。
多个客户端可以立刻连接到一个service。然而,系统只会在第一个客户端与service绑定的时候调用onBind方法来检索IBinder。然后系统会返回相同的IBinder给其余的绑定客户端,而不是再次调用onBind。
当最后一个客户端与service解绑的时候,系统会销毁该service。(除非该service已经被startServie方法启动过)
如果你允许你的service可以被start也可以被绑定,那么当它被start后,所有客户端与它解绑的时候,系统是不会销毁它的。你还需要调用stopSelf或者stopService来停止它。
当你实现你的bound service的时候,最重要的部分就是定义onBind回调方法返回的接口。有几种不同的方式来定义你的IBinder接口,下面将讨论每一种技术。
创建一个bound service
当创建一个可以绑定的service的时候,你必须提供一个IBinder,该IBinder提供了客户端可以与service交互的编程接口。这里有三种方式可以定义这个接口:
继承Binder类
如果你的service是你应用私有的,和客户端运行在一个进程的,你应该继承实现Binder类来创建你的接口,并且在onBind方法中返回该接口的实例。客户端接收到Binder后可以用它来直接访问Binder实现中的甚至service中的公开方法。
当你的service仅仅是你应用程序的后台工作人员的时候,这是首选的技术。你不使用这种方式创建你的service的唯一的原因是你的service可以被外部应用使用或者可以跨进程。
使用Messenger
如果你需要你的接口在不同的线程中工作,你可以用Messenger来给service创建一个接口。以这种方式,service定义了一个handler来回应不同类型的Message对象,这个Handler是Messenger的基础,可以和客户端分享一个IBinder,允许客户端用Message向service发送指令。客户端可以定义自己的Messenger,这样service就可以发送Message回来。
这是最简单的方式来进程间通讯,因为Messenger将所有的请求在一个线程中排出队列所以你不需要考虑你的service要是线程安全的。
使用AIDL
AIDL(Android接口定义语言)执行所有的工作,将对象分解成操作系统可以理解的原语,并在进程间编组它们以执行IPC。前面的技术使用了Messenger,这也是基于AIDL作为它的底层结构。像上面提到的,Messenger在单个线程中给所有的客户端请求创建了一个队列,所以service同一时间只能接收到一个请求。如果你想要你的service可以同时处理多个请求,你可以直接使用AIDL。这时候,你的service必须能支持多线程且是线程安全的。
要直接使用AIDL,您必须创建一个定义编程接口的.aidl文件。Android SDK工具使用此文件生成一个抽象类,该类实现接口并处理IPC,然后可以在你的service中继承它。
注意:大多数的应用不需要通过AIDL来创建一个bound service。因为这需要支持多线程且导致更加复杂的实现。因此,AIDL不适合大多数的应用,这篇文章也不会介绍如何给service使用它,如果你确定要直接使用AIDL,请查看专门的AIDL文章。
继承Binder类
如果你的service只在本应用中使用,不需要跨进程工作,那么你可以实现你自己的IBinder来让你的客户端直接访问service中的公开方法。
注意:这只有在service和client在同一个进程中才有效。
以下是设置方法:
-
在你的service中,用以下方法中的一个来创建IBinder实例:
- 包含client可以调用的公开方法
- 返回当前的service实例,该service包含client可以调用的公开方法
- 返回一个service持有的对象,client可以访问该对象的公开方法
-
在
onBind
回调方法中返回该IBinder
的实例。 -
在client中,接受从
onServiceConnected
回调方法中传来的IBinder
,然后可以通过提供的方法调用service。
说明:service和client必须在同一个线程中的原因是client可以将返回的对象转型并正确的调用它的API。服务和客户端也必须处于同一个进程中,因为此技术不会跨进程执行任何编组。
下面有个例子,这个service通过实现Binder来让client可以访问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();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
LocalBinder
提供了getService
方法给client来获取当前LocalService
的实例。这将允许client调用service的公开方法。例如client可以调用service的getRandomNumber
方法。
下面是一个activity与LocalService
绑定并在按钮被点击的时候调用getRandomNumber
方法:
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();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
使用Messenger
如果你的service需要和远程进程通信,你可以使用一个Messenger来给你的service提供接口。这项技术可以让你不使用AIDL就能进行IPC。
以下是如何使用Messenger的摘要:
- 该service实现了一个handler来接受来自client的每个调用的回调。
- Messenger创建了一个IBinder,service将该IBinder通过onBinder方法返回给client。
- 客户端使用IBinder实例化Messenger(引用服务的Handler),客户端用它将Message对象发送到service。
- service在它的Handler中接收到每个Message,明确的说,在handleMessage方法中。
以这种方式,client在service上没有方法可以调用,取而代之的是,client可以发送信息,service可以在Handler中接受信息。
以下是一个service使用Messenger的简单的例子:
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
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);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
client需要做的就是使用service返回的IBinder
创建一个Messenger
并且使用它的send
方法发送信息。下面是一个activity绑定到上面service并发送信息给该service的例子:
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
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();
// Bind to the 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如何对client做出回应。如果你想要你的service做出回应,那么你也需要在client中创建一个Messenger
。然后当client接收到onServiceConnected
回调的时候,client发送了一条Message
给service,这条Message
在它的reply
参数中包含了client的Messenger
。
绑定一个service
应用组件(client)可以 通过调用bindService
来和一个service进行绑定。Android系统会调用service的OnBind
方法,该方法返回一个可以与service交互的IBinder
。
绑定是异步的,bindSercice
会立刻返回但是不会返回IBinder
给client,client必须创建一个ServiceConnection
实例并将它传递给bindService
。ServiceConnection
包含了一个方法,系统会调用这个方法传递IBinder
。
说明:只有activity,service和content provider可以和service绑定-你不能将broadcast receiver和service绑定。
所以,为了能后绑定service,你必须要做到:
- 实现ServiceConnection
你的实现必须重写两个方法:
onServiceConnected()
系统调用这个方法来传送从onBind
方法返回的IBinder
给clientonServiceDisconnected()
Android系统会在client与service的连接异常断开的时候(例如service崩溃或者被杀死)调用该方法。client解绑的时候不会调用该方法。
-
调用bindService,传递ServiceConnection的实现。
-
当系统调用onServiceConnected回调方法时,你可以使用接口定义的方法开始调用service。
-
调用unBindService来解绑。
当client销毁的时候,会与service解绑。但是在你与service交互完成之后或者在activity pause的时候,你应该解绑。以便service可以在不使用的时候关闭。
下面的例子连接了client和一个通过继承Binder对象来创建的service。所有要做的就是将返回的IBinder转型为LocalBinder且获取LocalService的实例。
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
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;
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
client将该ServiceConnection
传递给bindService
来使用它。例如:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
bindService()
的第一个参数是一个Intent
,它明确地命名要绑定的服务(该intent可能是隐式的)。
第二个参数是ServiceConnetction
对象
第三个参数是一个指示绑定选项的标志。它通常应该是BIND_AUTO_CREATE
以便于在service没有存活的时候创建它。其余可能的值为BIND_DEBUG_UNBIND
、BIND_NOT_FOREGROUND
或者0表示没有。
附加说明:
以下有一些关于绑定service的重要说明:
你应该总是捕获DeadObjectException,这会在连接被打断的时候抛出。这是远程方法唯一抛出的一个异常。
对象是跨进程引用计数的
你通常应该将绑定和解绑与对应的client的生命周期中的创建和销毁对应起来。例如:
- 如果你想你的activity在可见的时候与service交互,你应该在onSatrt期间绑定在onStop期间解绑。
- 如果你想你的activity在stop的时候仍然可也接受到回应,那么你可以在onCreate中绑定,在onDestroy中解绑。注意,这意味着你的activity需要在整个生命周期中使用service(即使是在后台),如果该service是在另一个进程。你增加了该进程的负担,系统更有可能杀死它。
在activity的
onResume
和onPause
期间,通常不应该绑定和解除绑定,因为这些回调会在每个生命周期转换时发生,并且应该将在这些转换时发生的处理保持在最低限度。 另外,如果应用程序中的多个activity绑定到相同的service,并且这两个活动之间存在转换,则可能会因为当前activity在下一个activity绑定(在resume期间)之前解除绑定(在pause期间)而被破坏并重新创建。
管理bound service的生命周期
如果一个service被所有的组件解绑,那么Android系统就会销毁它(除非它也被onStartCommand
启动了)。因此,如果你的service是一个单纯的bound service,那么你不用管理它的生命周期-Android系统会根据是否有client与之绑定来为你管理。
然而,如果你选择了实现onSatrtCommand
回调方法,那么你必须明确的停止service,因为现在该service被认为是started。这种情况下,该service会一直运行到自己调用stopSelf
或者其余组件调用stopService
来停止它,而不管它是否与所有的client解绑。
除此之外,如果你的service是started且接受绑定,那么当系统调用onUnbind方法的时候,你可选择返回true,如果你想在下次一个client与这个service绑定的时候接受到一个onRebind
的回调。onRebind
返回void,但是client还是会在onServiceConnected
回调中接收到IBinder
。下面的图说明了这种生命周期的逻辑:
网友评论