【Android API指南】App组件(七) - Services(1) - Bound Services

【Android API指南】App组件(7) - Services(1) - Bound Services
一个bound service是客户端-服务器接口中的服务器。它允许组件去绑定service,发送请求,接收响应,甚至是执行进程间的通信。一个bound service通常在服务其他组件时存在,而不是一直运行在后台。

基础

一个bound service是Service类的实现,为了绑定一个service,你需要实现onBind()回调函数。这个函数返回一个IBinder对象,定义了客户端和服务器间交互的程序接口。

客户端调用bindService()绑定一个service。这样做时,你需要提供一个ServiceConnection的实现,它监控与服务器的链接。bindService()不会返回值,不过当系统创建客户端和服务器链接时,会调用ServiceConnection中的onServiceConnected()方法来传递IBinder。

多个客户端可以同时链接到service。不过,系统只会在第一个客户端绑定时调用onBind()方法取得IBinder。其他客户端是传递的相同的IBinder,而不重新调用onBind()。

当最后一个客户端解除绑定时,系统就会销毁这个service,除非service是通过startService()启动的。

创建一个Bound Service

要能提供绑定,你就需要提供一个IBinder来和service交互,下面有三种定义通信接口的方法:

扩展Binder类
如果你的service是私有的,并且和客户端运行在同一个进程中,那么你可以扩展Binder类来实现自己的接口,onBind()返回一个这个类的实例。客户端接收到这个Binder,然后直接调用它的公共函数。

如果service仅仅为你的程序工作,那么这个是一个好的选择。

使用一个Messenger
如果你需要接口能在不同的进程中工作,你需要使用Messenger来创建接口。service定义一个Handler来响应不同的Message对象,Handler是Messenger的基础,可以分享一个IBinder给客户端,运行客户端使用Message对象发送命令到服务器。另外,客户端可以定义自己的Messenger来让service发送信息回来。

这是进程间通信最简单的方法,因为Messenger使用单线程管理请求队列,所以你不需要设计你的service为线程安全的。

使用AIDL
AIDL(Android接口定义语言)把对象分解成原始部分,让系统能够理解并安排他们进行进程间通信。前面说的使用Messenger的方法就是一个简单的AIDL实现,使用Messenger的话,service一次只会接收到一个请求,如果你想同时处理多个请求,那么你可以直接使用AIDL,这种情况下,你的service必须支持多线程,并且要实现线程安全,更多AIDL使用看下一个教程。

扩展Binder类
如果service只是本地使用,不需要跨进程工作,那么使用自定义的Binder类就可以了。

提示:这种方式只能在客户端和服务器在一个程序和进程中才能使用,例如,一个音乐程序绑定一个activity到自己的service然后在后台播放音乐。

那么是实现的步骤:
  1. 在service中创建一个Binder的实例:
    a. 包含客户端可以调用的公共函数。
    b. 返回当前Service实例,实例包含客户端可以调用的公共函数。
    c. 或者返回service内部类的实例,包含可调用的公共函数。
  2. 从onBind()中返回Binder的实例。
  3. 在客户端的OnServiceConnected()取得Binder,调用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()方法来让客户端取得当前LocalService的实例。然后用这个实例就可以调用它的getRandomNumber()方法。

下面的客户端activity的实现:
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
如果要实现进程间通信,就需要使用Messenger了。

下面是使用Messenger的概述:
  • service实现一个Handler接收客户端的回调。
  • Handler被用来创建一个Messenger对象。
  • Messenger创建一个IBinder,在onBind()方法中返回给客户端。
  • 客户端使用IBinder初始化Messenger,然后使用它发送Message到服务器。
  • 服务器接收到Message,在Handler中处理这些Message。
这种方法中客户端不需要调用service的方法,而是让Handler接收信息,然后处理。

下面是service中的实现:
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();
    }
}
Handler中的handleMessage()方法接收客户端传送过来的Message,然后基于what参数做比较,从而执行对应的工作。

下面是简单的客户端activity的实现:
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;
        }
    }
}
上面的例子没有实现服务器返回信息给客户端的功能,如果你想收到服务器的响应的话,你需要在客户端创建一耳光Messenger,然后在send()方法中使用replyTo参数传递Messenger到服务器。

绑定到一个Service

客户端可以使用bindService()来绑定到一个service。系统就会调用onBind()方法来响应bindService()的调用。然后返回一个IBinder给客户端。

绑定时异步的,bindService()会立即返回,当时并没有返回IBinder给客户端,要接收到IBinder,客户端必须创建一个ServiceConnection的实例,然后传递给bindService()方法。ServiceConnection包含接收IBinder的回调函数。

提示:只有activity,service,content provider可以绑定一个service,你不能使用broadcast receiver绑定一个service。

那么,绑定一个service你需要做:
  1. 实现ServiceConnection。
    包括重写里面的两个方法:
    onServiceConnected()
            系统调用它来取得onBind()返回的IBinder。
    onServiceDisconnected()
            在service被意外丢失时调用,比如service自己崩溃了,或者被系统杀掉了。这个方法在客户端解除绑定时不会被调用。
  2. 调用bindService()方法传递ServiceConnection的实现。
  3. 当系统调用onServiceConnected方法时,你可以开始使用服务器的方法。
  4. 使用unbindService()来断开service连接。
    当客户端被销毁后,会从服务器解除绑定,不过你应该交互完成或者activity暂停时解除绑定,这样能很好的关闭不需要的service。
例如,下面是绑定的代码片段:
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()绑定到service:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

补充提示
下面是一些重要的提示:
  • 当连接被破坏时需要抛出一个DeadObjectException异常。这是远程函数唯一会抛出的异常。
  • 对象是夸进程引用的。
  • 绑定和解除绑定应该是成对出现的。例如:
    a. 在activity中在onStart()绑定,在onStop解除绑定。
    b. 如果你系统activity在被停止后还能接收响应,你可以在onCreate()中绑定,在onDestroy()中解除绑定。
    提示:你不应该再onResume()中绑定,在onPause()中解除绑定,因为这两个方法发生在生命周期的过渡阶段,时间比较短,很难保证进程的启动。同样的,如果有多个activity绑定到相同的service,那么在第二个activity绑定前,当前的activity解除绑定可能会导致service被销毁和重建。
管理Bound Service的生命周期

如果service仅仅是一个bound service,那么系统自动管理它的生命周期。不过,如果你选择实现onStartCommand()回调方法,你必须显式的停止service,因为这个时候service是被启动的,它一直运行,直到你来停止它。

另外,如果你的service是被启动的,然后接受了绑定,那么当系统调用你的onUnbind()方法时,你可以选择返回true的话,就是说下一次客户端绑定这个service时,是调用onRebind()来替换调用onBind()。onRebind()返回空,但是客户端仍然在onServiceConnected()中接受IBinder。下图展示了这个逻辑关系:
【Android API指南】App组件(七) - Services(1) - Bound Services
【Android API指南】App组件(七) - Services(1) - Bound Services