被绑定的服务

581 阅读11分钟

被绑定的service时客户端-服务端体系中的服务端。它允许app组件绑定到自己身上,然后发送请求,接收响应,甚至进行IPC。一个被绑定的服务通常只有在它仍然被app组件绑定的时候才会继续运行(如果所有客户端都解绑了就会被销毁),所以并不会在后台无限运行。

这篇文章会教你如何创建一个被绑定的服务,包括如何将另一个app的组件绑定到这个service上。

什么是被绑定的服务

被绑定的service继承Service类,允许其他的app与它绑定和交互。创建被绑定的服务,你需要实现onBind()这个回调方法。这个方法返回一个IBinder对象,这个对象定义了客户端是如何与服务端进行交互的。

绑定到一个已经被开启的service

众所周知,一个service可以同时被开启和绑定。

  • 先开启service,再绑定service(这也是更典型的情况) 这种情况下,如果先解绑,在stopSelf()或者stopService(),毫无疑问是可以销毁service的,那先stop再解绑呢?经过试验证明,也是可以的。

  • 先绑定service,再开启service 这种情况下,如果先stop,再解绑,是可以销毁service的,那反过来呢?也是没有问题的

所以我们得出结论,开启和绑定可以看作两种状态,这两种状态是不会相互影响的。一个service只要存在任意一种状态,就不会被销毁。换句话说,要同时start和bind一个service,start和bind的顺序没有什么影响;要销毁一个同时start和bind的service,stop和unbind的顺序也没有影响。

虽然一般情况下你只需要实现onBind()或者onStartCommand()两者中其中一个,有些时候也需要同时实现它们。比如,一个音乐app播放音乐的service不仅需要在后台无限运行,也需要让app组件(比如音乐播放详情页的activity)来绑定。这样,一个页面可以开启播放音乐的service,即便用户离开了app音乐也会继续播放。然后,当用户重新回到app时,activity可以绑定到service上面从而重新获取播放音乐的信息(比如歌曲名,播放进度等等)。

客户端通过调用bindService()绑定到service上面。调用这个方法时需要传入一个ServiceConnection的实现类,他可以监控客户端与服务端的连接。方法的返回值代表客户端是否成功连接到了服务端。当android系统创建一个客户端与服务端的连接时,会调用ServiceConnection对象的onServiceConnected()方法。这个方法包含一个Ibinder对象作为参数,客户端使用这个IBinder对象与被绑定的service通信。

你可以同时将多个客户端绑定到一个service上。然而,系统缓存了service用来通信的频道(也就是IBinder对象)。换句话说,只有第一个客户端绑定的时候才会调用service的onBind()方法来生成IBinder对象。之后,有其他的客户端绑定的时候,系统会把之前生成的IBinder对象传过去而不会再次调用onBind()方法。

当最后一个客户端解绑的时候,系统会销毁service,除非这个时候service还处于开启状态。

实现你的被绑定的service时最重要的部分是如何定义onBind()方法返回的IBinder()对象。下面我们将介绍几种定义IBinder接口的方法

创建一个被绑定的Service类

当创建一个可以被绑定的service时,你必须创建一个可以IBinder对象,这个对象定义了客户端和服务端如何交互。定义的方法有如下三种:

  • 继承Binder类 如果你的service只在自己的app中使用并且与绑定的客户端在同一个进程(这种情况是最多的),你应该通过继承Binder类来创建这个用来通信的接口。客户端接收到了这个Binder对象并且用它直接访问Binder对象或者Service中的公开方法。

  • 使用Messenger 如果你想让这个接口跨进程工作,你可以使用Messenger来给service创建接口。这样,service定义一个Handler来响应不同类型的Message对象。这个Handler是接下来可以与客户端共享一个IBinder的Messenger的基础,允许客户端使用Message对象向service发送消息。另外,客户端可以定义自己的Messenger,这样服务端可以将消息发送回来。这是跨进程通信最简单的方法,因为Messenger将所有的请求放入一个线程并且排成队列所以你不必担心线程安全问题。

  • 使用AIDL AIDL将对象分解成操作系统可以理解的原始数据并且把这些原始数据在进程间传递从而完成IPC。前面说到的方法,使用Messenger,实际上也是基于AIDL的。就像上面提到的,Messenger在一个单独的线程中将所有的客户端请求排成队列,所以service一次只能收到一个请求。如果,你想让你的service同时处理多个请求,那么就可以直接使用AIDL。在这种情况下,你的service必须能够处理多线程,而且保证线程安全。想要直接使用AIDL,你需要创建一个.aidl的文件,这个文件定义了编程接口。然后Android SDK tools使用这个文件来生成一个实现了接口并且处理了IPC的抽象类,然后你就可以在你的service中继承这个类了。

继承Binder类

如果你的service只给自己的app使用并且并不需要跨进程,那么你可以自己实现Binder类来让客户端直接访问service中的公共方法。

  1. 在你的service中,创建一个Binder的对象,这个对象需要满足以下条件中的一个:
  • 包含客户端可以调用的公共方法
  • 返回当前的service对象,这个service对象包含客户端可以调用的公共方法。
  • 返回另一个service中的包含客户端可以调用的公共方法的对象
  1. onBind()方法中返回Binder的对象

  2. 在客户端中,从onServiceConnected()方法接收Binder对象,通过这个对象来与service通信

     public class LocalService extends Service {
         // Binder given to clients
         private final IBinder binder = 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 binder;
         }
     
         /** method for clients */
         public int getRandomNumber() {
           return mGenerator.nextInt(100);
         }
     }
    

使用Messenger

如果你的service需要与远程进程通信,那么你可以使用Messenger来为你的service提供接口。这样你可以在不直接使用AIDL的情况下进行IPC。

使用Messenger比直接使用AIDL简单因为Messenger把所有发送到servcie的请求排成队列。而直接使用AIDL会同时发送多个请求给service,所以你必须进行多线程处理。

对于大多数app来说,service并不需要多线程处理请求,所以使用Messenger可以使service串行处理请求。如果你有并行的需求,那么直接使用AIDL。

下面是Messenger的使用总结:

  1. service需要实现一个从客户端接收回调的Handler。
  2. service使用Handler来创建一个Messenger对象。
  3. Messenger创建一个IBinder对象,onBind()方法返回这个对象。
  4. 客户端利用这个IBinder对象来初始化Messenger(指向service的Handler),然后利用这个Messeenger来向service发送Message。
  5. service则会在Handler的handlerMessage()方法中收到每个Message。

这样,客户端并没有调用服务端中的方法。取而代之,客户端向服务端的Handler中发送消息。

下面是服务端的代码

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.
     */
    static class IncomingHandler extends Handler {
        private Context applicationContext;

        IncomingHandler(Context context) {
            applicationContext = context.getApplicationContext();
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    Messenger mMessenger;

    /**
     * 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();
        mMessenger = new Messenger(new IncomingHandler(this));
        return mMessenger.getBinder();
    }
}

注意service中Handler的handleMessage()方法是它接收发送的Message对象的地方,在这里我们可以根据Message的what参数决定具体怎么处理。

客户端要做的事情就是用服务端返回的IBinder对象创建一个MEssenger对象,然后用这个Messenger发送消息,下面是一个简单的例子

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 bound;

    /**
     * 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);
            bound = 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;
            bound = false;
        }
    };

    public void sayHello(View v) {
        if (!bound) 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 (bound) {
            unbindService(mConnection);
            bound = false;
        }
    }
}

注意这个例子并没有展示服务端如何回应客户端(给客户端发消息)。如果你想让服务端给客户端发消息,你需要在客户端中创建一个Messenger对象。当客户端在收到onServiceConnected()方法的回调时,你应该把客户端的Messenger对象放在Message对象的replyTo参数中,然后再把这个Message对象发送过去。

敲黑板,无论是从哪端向另一端发消息,传递数据时一定要放在Message对象的一个名为data,类型为Bundle的字段中,否则会出现各种跨进程的问题。

绑定到一个service

app组件(客户端)可以通过调用bindService()方法绑定到一个service。随后android系统会调用service的onBind()方法,这个方法返回一个IBinder对象用来与service交互。

绑定过程是异步的,bindService()方法是立刻执行完毕而不需要马上返回一个IBinder对象给客户端的。那么为了接收这个IBinder对象,客户端需要创建一个ServiceConnection的实例并且传递给bindService()方法。ServiceConnection包含了一个回调方法可以用来传递IBinder对象。

要完成客户端与服务端的绑定过程,看下面的步骤:

  1. 实现ServiceConnection接口。 你的实现需要重写两个回调方法:
  • onServiceConnected() 系统调用这个方法来传递onBind()方法返回的IBinder对象
  • onServiceDisconnected() 当客户端与服务端的连接意外断开时系统会调用这个方法,比如当service崩溃或者被系统杀掉。并不是在解绑的时候被调用
  1. 调用bindService()方法,将第一步中实现的接口对象传进去。
  2. 当系统调用onServiceConnected()回调方法时,你就可以通过接口中定义的方法向服务端发送请求了。
  3. 想断开与服务端的连接,调用unbindService()。 如果app销毁客户端时客户端仍然绑定在service上,绑定会被动解除。当然最好在客户端不需要再与服务端交互之后就立即解除绑定。这样空闲的service就可以及时关闭了。

一些需要注意的点:

  • 代码中应该注意捕获DeadObjectException,当连接被破坏时会抛出这个异常。这是远程方法会出现的唯一异常。
  • 对象可以看作是跨进程的引用
  • 客户端的绑定与解绑应该与它特定的生命周期对应起来,具体可以看下面的例子
    • 如果你只想在你的activity可见的时候与服务端交互,你应该在onStart()中绑定,在onStop()中解绑。
    • 如果你想让你的activity在整个生命周期中与服务端交互,你应该在onCreate()中绑定,在onDestroy()中解绑。

管理一个被绑定的service的生命周期

关于service的生命周期前面已经讲的很清楚了,此处不再赘述。但是有一个点需要注意:

如果你的service是被开启并且可以被绑定的,那么当onUnbind()方法被调用的时候,如果下一次有客户端绑定到service上你想收到onRebind()方法的回调,那么onUnbind()方法应该返回true。