一、绑定服务
绑定服务是客户端-服务器接口中的服务器。绑定服务可让组件绑定到服务、发送请求、接受响应、执行进程间通信(IPC)。绑定服务只在为其他应用组件服务时处于活动状态,不会一直在后台运行。
绑定服务仍是Service类的实现,可让其他应用绑定,通过实现onBind()回调方法,返回IBinder对象作为客户端与服务进行交互的接口。
客户端通过bindService()绑定到服务。调用时,其必须提供ServiceConnection的实现,后者会监控与服务的连接。bindService()方法会立即无值返回,但当Android系统创建客户端与服务之间的连接时,会对ServiceConnection调用onServiceConnected(),向客户端传递用来与服务通信的IBinder。
多个客户端可以同时连接到一个服务。只有在第一个客户端绑定时,系统才会调用服务的onBind()方法来检索IBinder。系统随后无需再次调用onBind(),便可将同一IBinder传递至任何其他绑定的客户端。当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非startService()也启动了该服务)。
当实现绑定服务时,最重要是定义onBind()回调方法返回的接口。可以通过下面介绍的几种方法定义服务的IBinder接口。
二、创建绑定服务
1.扩展Binder类
如果服务仅供本地使用,且与客户端在相同的进程中运行(常见情况),则应优先扩展Binder类。客户端收到Binder之后,利用它直接访问Binder实现中乃至Service中可用的公共方法。
具体步骤:
1).创建一个可以满足下列任一要求的Binder实例:
a.包含客户端可调用的公共方法;
b.返回当前Service实例,其中包含客户端可调用的公共方法;
c.返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法。
2).从onBind()回调方法返回此Binder实例。
3).在客户端,从onServiceConncted()回调方法接收Binder,并使用提供的方法调用绑定服务。
注意:要求在同一应用内,是为了便于客户端转换返回的对象和正确调用API。要求在同一进程内,则此方法不执行任何跨进程编组。
服务端示例:
public class LocalService extends Service {
private final IBinder mBinder = new LocalBinder();
private final Random mGenerator = new Random();
/**
* 用于返回给客户端的Binder。因为服务在本地运行,因此不用处理IPC操作
*/
public class LocalBinder extends Binder {
LocalService getService() {
// 返回LocalService实例,以便方法调用
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** 供客户端调用的方法 */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
LocalBinder为客户端提供getService()方法,用以检索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();
// 绑定到LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 解绑
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
public void onButtonClick(View v) {
if (mBound) {
// 从 LocalService 调用方法.
// 如果调用会阻塞线程,则应在子线程中进行调用
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** 为绑定服务定义回调,传递给bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// 连接成功,将IBinder转换成指定的Binder
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
上述代码说明了客户端如何使用ServiceConnection的实现和onServiceConnected回调绑定到服务。
2.使用Messenger
如需让接口跨进程工作,则可使用Messenger为服务创建接口。服务可以这种方式定义对应于不同类型Message对象的Handler。此Handler是Messenger的基础,后者随后可与客户端分享一个IBinder,从而让客户端能利用Message对象向服务发送命令。此外,客户端还可以定义自有Messenger,以便服务传回消息。
这是执行IPC最简单的方法,因为Messenger会在单一线程中创建包含所有请求的队列(串行执行请求),这样就不必对服务进行线程安全设计。
具体步骤:
1)服务实现一个Handler,由其接收来自客户端的每个调用的回调;
2)Handler用于创建Messenger对象(对Handler的引用);
3)Messenger创建一个IBinder,通过onBind()使其返回客户端;
4)客户端使用IBinder将Messenger实例化,然后使用后者将Message对象发送给服务;
5)服务在其Handler中(具体讲,是在handleMessage()方法中)接收每个Message。
注意:客户端并没有调用服务的“方法”。而客户端传递的消息(Message对象)是服务在Handler中接收的。
服务端示例:
public class MessengerService extends Service {
static final int MSG_SAY_HELLO = 1;
/**
* 接收客户端请求的handler
*/
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);
}
}
}
/**
* 公开给客户端用于发送Message给Handler的Messenger
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* 当绑定到服务时,返回一个接口给messenger用于发送Message给服务
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
服务在Handler的handleMessage()方法中接收传入的Message,并根据what决定下一步操作。
客户端只需根据服务返回的IBinder创建一个Messenger,然后利用send()发送消息。
客户端示例:
public class ActivityMessenger extends Activity {
/** 用于与服务交互的Messenger */
Messenger mService = null;
boolean mBound;
/**
* 与服务接口交互的连接类
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// 当与服务的连接已经建立时调用,给出这个对象,用于与服务交互。
// 我们将使用Messenger与Service交互,这里从原始的IBinder对象中获取客户端
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// 断开时调用
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// 创建并发送一个Message给服务,使用what传递值
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();
// 绑定
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 解除绑定
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
注意,如果想让服务做出响应,则需要在客户端内创建一个Messenger。当客户端收到onServiceConnected()回调时,会向服务发送一条Message,并在其send()方法的replyTo参数中包含客户端的Messenger.
3.使用AIDL
AIDL(Android Interface Definition Language,Android 接口定义语言)执行所有将对象分解为原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行IPC。上述的Messenger实际上是以AIDL作为其底层结构。Messenger会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果想让服务同时处理多个请求,则可直接使用AIDL。此情况下,服务必须具备多线程处理能力,采用线程安全的设计。
如需直接使用AIDL,必须创建一个定义编程接口的.aidl文件。Android SDK利用该文件生成一个实现接口并处理IPC的抽象类,之后便可在服务内对其进行扩展。
注意:大多数应用“都不会”使用AIDL来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL并不适合大多数应用。
具体请见:AIDL详解
三、绑定到服务
客户端通过bindService绑定到服务。Android系统会调用服务的onBind()方法,该方法返回用于与服务交互的IBinder。绑定是异步的。bindService()会立即返回,但不会返回IBinder。要接收IBinder,客户端必须创建一个ServiceConnection实例,并将其传递给bindService()。ServiceConnection包括一个回调方法,系统通过调用它来传递IBinder。
注意:只有Activity、Service与ContentProvider可以绑定到服务,广播不行。
绑定步骤:
1.实现ServiceConnection。
实现回调方法:
onServiceConnected() :系统会调用该方法以传递服务onBind()返回的IBinder。
onServiceDisconnected(): Android 系统会在与服务连接意外中断时(服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统不会调用该方法。
2.调用bindService(),传递ServiceConnection实现。
3.当系统调用您的onServiceConnected()回调方法时,您可以使用接口定义的方法开始调用服务。
4.要断开与服务的连接,请调用unbindService()。
注意:如果应用在客户端绑定到服务时销毁客户端,则销毁会导致客户端取消绑定。更好的做法是在客户端与服务交互完成后,立即取消绑定客户端。这样可以关闭空闲服务。
ServiceConnection示例:
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// 与服务连接建立时调用
public void onServiceConnected(ComponentName className, IBinder service) {
// 已经绑定到运行在我们自己进程中的明确的服务,所以可以将IBinder转换为具体的类并访问
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
// 在意外断开时调用
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
客户端通过此ServiceConnection传递至bindService()绑定到服务。
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
intent:要绑定到的服务
Context.BIND_AUTO_CREATE:指示绑定选项的标志。通常是BIND_AUTO_CREATE,以便创建尚未激活的服务。也可能是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或为0。
重点说明:
1.应始终捕获DeadObjectException异常(RemoteException子类),连接中断时引发的。是远程方法引发的唯一异常。
2.对象是跨进程计数的引用。(这句还不太理解,记下来再说)
3. 通常应在生命周期的引入和退出时绑定和取消绑定。一般切勿在onResume()和onPause()绑定和取消绑定。
四、绑定服务的生命周期
当所有客户端都取消绑定时,服务被销毁(除非使用了onStartCommond()启动该服务)。
如果是以onStartCommond()启动服务,则服务会一直运行,直到通过stopSelf()自行停止,或其他组件调用stopService()位置,无论是否绑定到任何客户端。
此外,如果服务启动并接受绑定,当系统调用onUnbind()方法时,如果想在客户端下一次绑定到服务时接收onRebind()调用,则可选则返回true。onRebind()返回空值,但客户端仍在其onServiceConnected()回调中接收IBinder。