App Components.Services.Bound Service

App Components.Services.Bound Service

被绑定的服务在“客户端-服务器”模式中是服务器的角色。它允许其他的组件(如活动)绑定到服务,发送请求,接受响应,甚至进行进程间通信(IPC)。绑定服务通常只在它可以给某组件提供服务时才存活,而并不是无限在后台运行。
这篇文档告诉你如何创建被绑定的服务,和如何从其他组件中绑定到这个服务。而之前Services那篇文档介绍的服务的整体概览,像从服务中获取通知和使用前台服务等等,这篇文档则专门针对绑定服务。

基础:
绑定服务也是Service类的一个实现,它允许其他应用中的组件绑定到它进行互动。要提供绑定的功能,必须实现onBind()回调方法。这个方法返回一个IBinder对象,这个对象可以作为一个编程接口,客户通过此接口来和服务进行交互。
客户端可以使用bindService()方法绑定到服务。不光如此,客户端还需要实现一个ServiceConnection,这个接口可以监测客户端与服务之间的连接。bindSerivice()调用会马上返回(没有返回值),然后当系统在客户端和服务之间建立起连接的时候,会调用ServiceConnection下的onServiceConnected()方法,给这个方法传入一个IBinder对象(就是服务的onBind()返回的那个东西),客户通过此对象和服务通信。
多客户可以同时连接(就是绑定)到同一服务。只有在第一个客户绑定到服务的时候,系统才会调用onBind()方法,得到IBinder对象。对之后连接的所有客户,直接把这个对象给到客户端,而不会再调onBind()了。
当最后一个客户端解绑服务的时候,系统就会销毁此服务(除非它之前被startService()启动,那就需要等那两个stop方法了。)
实现绑定服务的最重要的环节就是要实现IBinder编程接口,用来作为服务onBind()方法的返回值,让客户端用这个对象和服务进行通信。有几种方法可以实现,下面讨论。

创建一个被绑定的服务:
创建一个提供绑定功能的服务,你必须提供一个IBinder给客户端来进行通信。有以下几种方法可以实现此功能:
继承Binder类:
如果你这个服务是个私有服务,只和应用内部的客户端连接(多数服务都是这样的),那就创建一个继承Binder类的类然后在onBind()方法中返回这个类的对象。然后客户端收到此对象就可以直接调用对象中的public方法甚至可以调服务里的public方法。
通常情况你的服务都是给自己应用里的组件提供服务的,这时我们更推荐此方式。如果你需要跨应用/跨进程提供服务,那就需要换一种方法了。

使用Messenger:
如果你希望你提供的这个交互接口可以跨进程使用,可以在服务内部创建一个Messenger对象。这种方式下,服务中还需定义一个Handler去处理不同的Message对象。以Handler为基础,Messenger实现与客户端共享IBinder的功能,允许客户端给服务发送命令,命令即是通过Message对象来传输的。另外,客户端也可以自己定义一个Messenger对象来让服务回应一些消息。
这是实现IPC的最简单的方式。而且Messenge在单个线程里把所有的请求排成队列,所以你不用担心多线程的线程安全问题。

使用AIDL:
AIDL(Android Interface Definition Language)实现把对象分解成为系统能理解的基本类型并且实现了将这些基本类型在进程间序列化传输,进而实现了进程间通信(IPC)。前面提到的Messenger,其实就是基于AIDL并且作为AIDL的底层架构。之前说过,Messenger会把所有的客户端请求在一个线程里建成队列,让服务一次一个的收到请求。那如果你想让服务同时处理多个请求呢,这是就要用到AIDL。这种情况下,你的服务必须可以实现多线程操作并且保证线程安全。
想要直接使用AIDL,需要创建一个.aidl文件定义一个编程接口。SDK工具会使用你这个文件去生成一个实现了编程接口的抽象类,并且处理IPC。之后在你的服务里你就可以继承那个抽象类了。

注意:多数应用最好不要用AIDL创建绑定服务,因为这东西需要多线程操作导致实现起来太麻烦。所以,AIDL对大多数的应用都不太合适,这篇文档也不会讨论AIDL的具体使用方法。如果你实在想用,且听下回分解。

继承Binder类:
如果你的服务仅希望同应用内使用并且不需要跨进程服务,那就直接实现一个Binder类让客户访问服务中的public方法好了。
注意:此方法仅在同应用同进程内可用。比如某音乐播放器需要绑定一个活动到它的放音乐的服务,来实现控制,用这个方法就很合适。
步骤如下:
1. 在服务内部,创建一个Binder类的实例对象,要求如下条件至少满足一个:
~要有public方法让客户来调用
~返回一个Service实例,Service中要有public方法让用户来调用
~返回一个服务内部的其他类,同样要有public方法。
2. 在onBinder()方法中返回你创建的Binder类实例对象。
3. 在客户端的onServiceConnected()回调方法中接收Binder对象,然后根据提供public方法来对服务进行一些方法调用。
注意:之所以要客户端和服务在同一应用内是因为,只有这样客户端才能够把返回的对象强转成对应的类,然后才能调用对应的方法。当然客户端服务还得在同一进程内,因为这种方式不能提供任何跨进程的序列化传输。

例如,如下是一个服务,它向客户端提供了通过Binder来调用方法的功能:

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.给同进程的客户用的,所以不用管IPC的问题。
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods.LoaclBinder就是onBind返回的对象,返回之后客户就可以调里面的getService()方法,可以给客户返回一个Service实例,进而客户就可以调服务里面的public方法了~
return LocalService.this;
}
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}

LocalBinder提供了getService()方法,向用户提供了LocalService的当前实例。然后用户就可以调用服务里的public方法了,比如可以调getRandomNumber()方法。

下面是一个绑定到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) 按钮在xml中定义了onclick属性,可以直接找到这个方法。*/
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()用来给bindService()方法传入的参数,并且在建立起连接的时候会自动回调onServiceConnected方法,传回一个IBinder */
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注意传回的是IBinder,我们需要将其强转为LocalBinder,这就是上面提过的为什么客户端和服务要在统一应用的原因,当然,跨应用的相关问题之后会讨论。
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}

@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}

上述的例子展示了如何实现ServiceConnection以及onServiceConnected()方法,进而实现绑定服务。

使用Messenger:
如果你的线程需要和远程进程通信,那就需要用Messenger来提供到服务的接口。使用这种方式可以进行进程间通信IPC,并不需要AIDL。
关于如何使用Messenger的总结:
~服务要实现Handler接口,然后对每一个来自客户端的请求,服务都会收到一个回调。
~Handler用来创建Messenger对象(该对象同时也是指向Handler的引用)。
~Messenger可以创建一个IBinder用来在onBind()中给客户端返回。
~客户端使用这个返回的IBinder来实例化Messenger(记住Messenger指向服务的Handler哦),然后使用Messenger给服务发送Message对象。
~服务收到Message对象(通过Handler,具体点就是Handler里有个回调方法叫handleMessage(),通过这个方法可以收到Message)
这种方式下,客户端不能调用服务的方法,而是给服务发送消息(即Message对象)然后服务在Handler中接收之。
Tip:
Messenger和AIDL的对比
当你需要实现IPC的时候,使用Messenger要比AIDL简单,因为Messenger是把请求以一个队列的形式传给服务,而AIDL是把请求同时发给了服务,这样的话服务端还需要处理多线程的问题。
对大多数应用来说,服务都不需要搞多线程,所以使用Messenger串行处理请求就OK了。如果你实在需要多线程,那就用AIDL吧。
下面是关于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();
}
}

看到handlemessage()了么,就是在这个方法里服务收到了Message对象然后做对应的处理。

客户端需要做的就是用返回来的IBinder创建一个Messenger对象,然后调用send()发送消息(就是Message对象)。比如,下面就是一个绑定到服务的活动,并且给服务发送一个MSG_SAY_HELLO的消息:

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.注意这个方法不是在断开连接的时候调的,而是在“unexpected”断开的时候调的。属于比较特殊的情况
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);//似乎Message.obtain是个不错的选择。放弃handler.obtain吧
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;
}
}
}

注意到这个例子里并没有展示服务如何响应客户端。如果你想让服务响应你,那就在客户端也创建一个Messenger对象。然后在客户端收到onServiceConnected()的回调的时候,可以使用send()方法给服务传一个Message对象,将客户的Messenger对象作为Message对象的replyTo成员即可。

绑定到服务:
应用组件们(即客户端)可以通过调用bindService()来绑定到服务。然后android系统会调用服务的onBind()方法,返回一个IBinder对象用来与服务交互。
绑定是一个异步过程。bindService()一经调用便会立即返回,而且不会给客户端返回IBinder。想要接收到IBinder,客户端必须创建一个ServiceConnection的实例将其传给bindService()方法。ServiceConnection接口中包含一个回调方法,系统会调用这个方法并把IBinder传入此方法。
注意:只有活动、服务、内容提供者可以绑定到服务,广播接收者是不可以的。

所以,要实现从客户端绑定到服务,必须:
1. 实现ServiceConnection接口。
并且必须重写其中的两个回调方法:
onServiceConnected()
系统将服务的onBind()方法返回的IBinder传入onServiceConnected()方法。
onServiceDisconnected()
在客户端与服务间的连接意外地断开的时候,系统会调用这个方法,比如服务突然崩溃了,或者服务被杀掉了。而当客户端正常解绑的时候,不会调这个方法。
2. 调用bindSerivce(),传入你实现的ServiceConnection。
3. 当系统调到你的onServiceConnected()方法时,你就可以调用服务那边暴露的一些方法了。
4. 要断开同服务的连接,直接调unbindSerivce()就好。
下面这个代码片段,就是将客户端连接到服务(服务就是上面那个“继承Binder类”标题下创建的服务)。所以需要做的事情就是把返回的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;
}
};

有了这个ServiceConnection,客户端就可以通过bindService()方法绑定到服务,只需把ServiceConnection作为参数传入到bindService()方法中。如:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

~bindService()第一个参数是一个Intent对象,该对象显式指明了要绑定到的服务(尽管意图本身可以是隐式的?)
~第二个参数是ServiceConnection对象。
~第三个参数是一个flag,指明绑定的一些选项参数。通常都是BIND_AUTO_CREATE也就是如果要绑定的服务不在活动状态就直接创建服务。其他可选值有BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或者传入0表示什么都不设置。

其他注意事项:
下面是一些关于绑定服务的一些重要的注意事项:
~一定要捕获DeadObjectException异常,在连接崩溃的时候会抛出此异常,而且这也是远程方法抛出的唯一一个异常。
~跨进程的对象是以引用来计数的。(在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。)
~绑定与解绑服务最好能够和客户端的生命周期对应起来。比如:
如果你只在活动的可见期间需要与服务交互,那最好在onStart()绑定服务,在onStop()解绑服务。
如果你希望活动即便被Stop掉依然接受服务的响应,那就在onCreate()里绑定,onDestroy()解绑。当然,这意味着你的活动在整个完整生命周期里都要用到服务(即使在后台),如果这个服务是在另外一个进程,那此举无疑会增加进程的资源占用率,这样的话,进程被系统杀掉的可能性会增加不少。
注意:不要在onResume()和onPause()方法里解绑服务,因为这两个方法在活动的生命状态每有变化的时候都会调到,所以这两个方法中的任务要尽量最少化。另外,如果你应用当中的多个活动绑定到了同一个服务,然后某两个活动之间要来回切换,那很可能在前一个活动的onPasue()解绑完之后,后一个活动onResume()绑定之前会有一个空档期,此时服务没有任何客户端,然后系统就把它销毁了,那么后一个活动要绑定到服务那就得重新创建,造成性能的不必要的浪费。

管理绑定服务的生命周期:
当所有的客户端都从服务解绑的时候,系统会销毁服务(除非它同时也经onStartCommand()启动)。所以,如果你的服务是一个纯粹的绑定服务,那么就不需要担心它的声明周期问题,因为安卓系统会帮你管理的(根据它是否被绑定到客户端)。
然而,如果你实现了onStartCommand()回调方法,那就必须显式的停止服务,因为经过这个方法,服务是被“启动”的,那么直到它自己调用stopSelf()或者别人调用stopService(),它才会被中止。而跟绑定与否无关。
另外,如果你的服务是被“启动”的同时也接受绑定,那么当系统调用服务的onUnbind()方法时,作为可选项,你可以返回true,如果你想要让下个客户端绑定的时候调用onRebind()而不是onBind()。onRebind()返回void,但是客户端依旧会在onServiceConnected()方法中接收到IBinder对象。下图展示了启动和绑定并存的服务的生命周期。
 posted on 2015-07-29 16:16  絮状颗粒  阅读(158)  评论(0)    收藏  举报