本文翻译自Android官方文档,经过本人测试整理如下。
这是service的第二部分bound service。若第一部分没看的,请参考:解读Android之Service(1)基础知识 。
bound service相当于客户-服务器接口中的服务器。bound service允许其它组件(除了broadcast receiver)绑定该service,然后进一步操作:发送请求,接收响应,甚至IPC。bound service 只有在其他组件绑定它时才处于存活状态,且会受到绑定它的组件影响。
下面将具体介绍如何创建bound service等相关内容。
若service允许被别的组件绑定的话,我们必须实现onBind()
方法,该方法将返回一个IBinder对象(在启动方式的service中该方法返回null),以便实现和客户端进行交互。
客户端可以通过调用bindService()
方法实现绑定,系统会调用服务的onBind(Intent)
回调方法,返回一个用于和服务进行交互的IBinder 对象。之后我们必须实现ServiceConnection接口接收IBinder对象,来监控客户端和服务器之间的连接。绑定是异步进行的,bindService()
将立即返回,并不会立即向客户端返回IBinder对象。但是我们可以通过ServiceConnection接口中的方法接收IBinder对象,完成通信。
多个客户端可以绑定同一个service,但是只有第一个才会调用onBind()
并检索IBinder对象。其它客户端直接接收相同的IBinder对象,而不再调用该方法。
当最后一个客户端解绑service时,系统销毁该service,除非startService()
方式启动的service。
一个service可以同时started和绑定。若一个service可以同时被started和绑定,那么当service以started方式启动时,当所有客户端解绑时,系统不会销毁该service。而是必须通过stopSelf()
或stopService()
。
尽管通常情况下只需要实现onBind()
和onStartCommand()
两者中的一个,但是有时候需要实现这两种方式。例如一个音乐播放器,当activity启动service时可以播放音乐,即使用户离开应用;当用户又回来时activity又可以绑定service,重新控制service。
更多信息请看管理绑定的service生命周期(下面)。
当创建bound Serivce时,我们必须提供一个IBinder对象,该对象提供了一个编程接口用于和客户端交互。有下列三种方式定义该接口:
.aidl
文件,其中定义了编程接口。 Android SDK 工具使用此文件来自动生成一个抽象类,其中实现了接口及对IPC的处理,然后我们就可以在自己的服务中继承该类。注意:绝大多数应用程序都不应该用AIDL来创建Bound Service,因为这可能需要多线程处理能力并且会让代码变得更为复杂。因此,AIDL对绝大多数应用程序都不适用。
注:关于marshalling维基上是这样解释的:In computer science, marshalling or marshaling is the process of transforming the memory representation of an object to a data format suitable for storage or transmission, and it is typically used when data must be moved between different parts of a computer program or from one program to another. Marshalling is similar to serialization and is used to communicate to remote objects with an object, in this case a serialized object. It simplifies complex communication, using custom/complex objects to communicate instead of primitives. The opposite, or reverse, of marshalling is called unmarshalling (or demarshalling, similar to deserialization).
注:在四大基本组件中,BroadcastReceiver不能作为Bound Service的客户端。因为BroadcastReceiver的生命周期很短,当执行完onReceive()
回调时,BroadcastReceiver生命周期完结。而Bound Service又与Client本身的生命周期相关,因此,Android中不允许BroadcastReceiver去bindService()
,当有此类需求时,可以考虑通过startService()
(四大组件Started Service都是通过startService()
)或者再绑定。
下面详细讲解除AIDL以外的方法,关于AIDL则请参考另一篇翻译文章:解读Android之Service(3)AIDL。
如果只是在应用程序内部使用,并且不需要跨进程,我们可以通过这种方式直接进行交互。这种是最常见的方式。
继承Binder类需要完成如下步骤:
onBind()
方法中返回该Binder对象。onServiceConnected()
方法接收该Binder对象,然后就可以调用它的相关方法了。注意:service和客户端必须在同一个应用程序的原因在于,客户端可以转换返回的Binder对象,然后调用相关方法;service和客户端必须在一个进程中的原因在于,这种方式不执行任何跨进程的序列化(marshalling)。
根据官方文档中的示例代码修改代码如下:
/** * bound service * 通过继承Binder类.在同一进程中通信,并非IPC * @author sywyg * @since 2015.7.15 */
public class LocalService extends Service {
private final String TAG = "result";
private final IBinder mBinder = new LocalBinder();
private final Random mGenerator = new Random();
public LocalService() {}
public class LocalBinder extends Binder {
//获得本地service对象,以便调用service中public方法
public LocalService getLocalService(){
Log.d(TAG,"getLocalService is executed ...");
return LocalService.this;
}
/** * 模拟客户端要处理的LocalBinder中的public方法 * @return */
public void sayHello(){
Toast.makeText(getApplicationContext(),"hello service",Toast.LENGTH_LONG).show();
}
}
@Override
public void onCreate() {
super.onCreate();
Log.d("result","onCreate executed");
}
/** * 绑定时调用 * @param intent 传递过来的intent * @return 返回IBinder对象,传递给ServiceConnection实例处理 */
@Override
public IBinder onBind(Intent intent) {
Log.d("result","onBind executed");
return mBinder;
}
/** * 解绑时调用 * @param intent * @return */
@Override
public boolean onUnbind(Intent intent) {
Log.d("result","onUnbind executed");
return super.onUnbind(intent);
}
@Override
public void onDestroy(){
super.onDestroy();
Log.d("result","onDestroy executed");
}
/** * 模拟客户端要处理的service中的public方法 * @return */
public int getRandomNumber(){
return mGenerator.nextInt(100);
}
}
下面是一个绑定LocalService的activity,当点击按钮时回调getRandomNumber()
:
/** * 绑定service练习 * @author sywyg * @since 2015.7.15 */
public class BindingActivity extends Activity {
private final String TAG = "result";
private boolean mBound = false;
private LocalService mLocalService;
private LocalService.LocalBinder binder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bound_service);
}
@Override
protected void onStart() {
super.onStart();
// 绑定服务
Intent intent = new Intent(this,LocalService.class);
bindService(intent,mConnection,BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection() {
// 执行该方法表示绑定成功,并可以调用服务了。
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 获取LocalBinder对象,同时获取LocalService
binder= (LocalService.LocalBinder)service;
mLocalService = binder.getLocalService();
mBound = true;
}
// 绑定失败调用下面的方法
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected...");
mBound = false;
}
};
public void onButtonClick(View v){
switch (v.getId()){
// 调用service方法
case R.id.btn_call_method:
// 若耗时则应在一个新的线程中运行
if(mBound){
Toast.makeText(this,
mLocalService.getRandomNumber() + "", Toast.LENGTH_LONG).show();
binder.sayHello();
}
break;
// 解绑
case R.id.btn_bind:
if(mBound){
unbindService(mConnection);
mBound = false;
}
break;
}
}
@Override
protected void onStop() {
super.onStop();
// 解绑
if(mBound){
unbindService(mConnection);
mBound = false;
}
}
}
测试结果为:
unBindService()
之后,service就会执行onDestroy()
方法。onStartCommand()
。onBind()
;多个客户端绑定一个service只在第一次调用onBind()
之后直接使用IBinder实例。可以看到通过上面这种方式可以实现同一进程间客户端(通常是指activity等)和Bound Service之间的通信。
局限:客户端与Service必须属于同一个进程,不能实现进程间通信(IPC)。否则会出现类似于android.os.BinderProxy cannot be cast to xxx
错误。
如果需要远程通信,可以使用一个Messenger来提供服务的接口。这种技术无须自定义AIDL就能进行进程间通信(IPC)。
Messenger具体使用步骤如下:
onBind()
方法返回给客户端;handleMessage()
方法中处理;这种方式service没有提供给客户端任何方法,而是客户端传递消息(Message对象)给service。
下面是一个简单的使用Messenger的serivce例子:
/** * 使用Messenger进行IPC * @author sywyg * @since 2015.7.15 */
public class MessengerService extends Service {
private final String TAG = "result";
// 消息标记
private static final int MSG_SAY_HELLO = 1;
// 利用Handler对象创建messenger
private Messenger messenger = new Messenger(new IncomingHandler());
public MessengerService() {
}
/** * 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);
}
}
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind is executed ...");
return messenger.getBinder();
}
}
为了测试,可以在AndroidManifest.xml文件中设置MessengerService开启新的进程。
所有的客户端都需要根据IBinder对象(service传递过来的)创建一个Messenger实例,然后可以通过send()
发送Message对象。例如下面是一个简单的实现绑定service的例子:
/** * @author sywyg * @since 2015.9.10 */
public class NewMessengerActivity extends Activity {
private final String TAG = "result";
// 消息标记
public static final int MSG_SAY_HELLO_TOO = 1;
private boolean mBound;
private Messenger mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new_messenger);
// 绑定service
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection,BIND_AUTO_CREATE);
}
public void onButtonClick(View view){
// 获取消息
Message msg = Message.obtain(null,MSG_SAY_HELLO_TOO);
try {
// 向service发送消息
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private ServiceConnection mConnection = new ServiceConnection(){
// 连接成功时调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 通过service传递的IBinder对象创建Messenger
mService = new Messenger(service);
mBound = true;
}
// 连接失败时调用
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG,"onServiceDisconnected");
mBound = false;
}
};
@Override
protected void onStop() {
super.onStop();
if(mBound){
unbindService(mConnection);
}
}
}
上面的例子实现了通过Messenger,客户端可以向service发送消息。但是service不能向客户端发送消息。
若要实现service向客户端发送消息,则可以通过Message中的ReplyTo参数(Messenger类型)。上面的例子已经建立连接,因此可以通过相同的办法从service向客户端发送消息:
send()
方法发送消息);send()
方法给客户端发送消息;代码和上面的类似,不再给出。
当需要执行IPC时,Messenger方法比AIDL方法简单的多,这是因为Messenger方法将所有的请求排成一列,而AIDL则是同时处理多个请求(能够处理多线程问题)。
对于大多数应用不需要执行多线程,因此使用Messenger同一时刻只执行一个请求。当要执行多线程时,需要使用AIDL定义接口。
应用程序组件(客户端)能够通过bindService()
绑定service。然后系统调用service的onBind()
方法,该方法返回一个IBinder对象用于和service进行交互。
绑定是异步的。bindService()
会立刻返回,不会返回IBinder对象给客户端。为了接收IBinder对象,客户端必须创建一个ServiceConnection的实例并传递给bindService()
。ServiceConnection包括一个回调方法,用于接收IBinder对象。
注意:只有activities,services和content providers能够绑定service,broadcast receiver不能绑定。
为了绑定serivce,我们必须完成以下内容:
onServiceConnected()
onBind()
传递的IBinder对象。 onServiceDisconnected()
bindService()
,并且传递ServiceConnection实例;onServiceConnected()
方法时,可以使用接口中定义的方法(或客户端中的方法)和service进行通信;unBindService()
解除绑定。 上面的例子都给出了具体的实现,这里不再赘述。
有了ServiceConnection对象之后,客户端可以通过下列方法绑定service:
Intent intent = new Intent(this,LocalService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
bindService()
方法参数分别为:
关于绑定还有下面一些重要的提示:
绑定和解绑要在合适的位置进行:
onStart()
方法中绑定,在onStop()
中解绑;onCreate()
中绑定,在onDestroy()
中解绑。此时就要多加小心:当前activity需要在整个生命周期中使用service,因此若service在另一个进程中,这种方式增加了该进程被系统杀死的可能性。关于第二点,stackoverflow上有这样的回答:
if you use this type of interprocess communication you can never be sure that your objects will be garbage collected, even when your application shuts down, unless the application you are communicating with also shuts down and that application has not communicated in the same way with any other application (which again you can never be sure of unless its yours).
若按照他的意思应该是内存泄漏,但是作者也说了他不确定。
注意:不要在activity中的onResume()
中绑定和在onPause()
中解绑,这是因为两个方法会在每次生命周期转换的过程中,应该将这些处理在转换时降到最低。同时,若多个activities中绑定了同一个service,并且在这些acitivities中相互转换的话,service会被频繁地销毁重建(在停止时解绑然后被销毁,该动作发生在另一个activity绑定它之前)。
当service被所有客户端解绑时,系统销毁该service,除非该service先是被started方法的。因此对于存粹绑定的service我们无需管理它的生命周期–系统基于是否绑定客户端来管理。
若先绑定再启动,会执行onStartCommand()
方法,不会执行onCreate()
方法(绑定时已执行过),关于同时绑定和启动的service何时销毁有下面几种:
stopSelf()
或stopService()
不会销毁service;在调用stopSelf()
或stopService()
之后再解绑会销毁service。stopSelf()
或stopService()
会销毁service。一句话总结:要开始就得结束,要绑定就得解绑,这样才能销毁service。
官网上有下面这样一段:
However, if you choose to implement the onStartCommand() callback method, then you must explicitly stop the service, because the service is now considered to be started. In this case, the service runs until the service stops itself with stopSelf() or another component calls stopService(), regardless of whether it is bound to any clients.
若你实现了onStartCommand()
方法,则此时service被认为是started方式的,因此我们必须管理它的生命周期。在这种情况下,无论是否绑定了客户端,只要没有调用了stop方法就不会service停止。
经过我的测试,我发现不存在这种情况。是不是我的理解问题,请大神帮忙看一下。。。
此外,如果service是started方式,然后被绑定,那么当系统调用onUnbind()
方法时,如果我们想要接收onRebind()
方法的回调(下一次客户端绑定该service)而不是接收onBind()
方法的回调,则我们可以onUnbind()
返回true。onRebind()
方法没有返回值,但是客户端仍然可以接受IBinder对象。这种生命周期的逻辑如下: