[安卓API guides翻译]2.3.1 Bound Services

bound service是客户端-服务器接口的服务端。这种service允许组件(如activity)与之绑定,发送请求,接收回应,甚至进行进程间通信(IPC)。bound service仅在为其他组件提供服务时运行,而不是在后台一直运行。

 

本文将向你介绍如何创建一个bound service,包括其他组件如何绑定到该service。不过,你也应该参看上节Services一文,了解service的其他知识,例如从service发送通知的方法,还有设置service在前台运行,等等。

 

The Basics

 

bound service是Service类的实现类,这类service允许其他程序与之绑定和交互。要提供这种绑定功能,必须实现onBind()回调方法。该方法返回一个IBinder对象,这个对象定义了客户端与service交互的程序接口。

 

客户端可以调用bindService()来与service绑定。如果这么做,bindService()必须提供一个ServiceConnection对象来监听与service的连接。bindService()无返回值,并且会立即返回,当android系统创建客户端与service之间的连接时,系统将调用ServiceConnection对象的onServiceConnected()方法来传递IBinder对象,客户端就是使用该对象与service通信。

 

多个客户端可同时与一个service建立连接。然而,只有第一个客户端绑定service时,系统才调用service的onBind()方法来索取IBinder对象。然后,系统会把相同的IBinder对象传递给其他绑定的客户端,并不会再次调用onBind()方法。

 

当最后一个客户端与service解除绑定,系统就销毁service(除非该service是被startService()方法启动的)。

 

实现bound service时,最重要的部分是定义onBind()回调方法返回的接口。有几种不同的方式来定义service的IBinder接口,下面将讨论每种方式。

 

Creating a Bound Service

 

要创建一个service并提供绑定功能,必须提供一个IBinder对象,这个对象定义了客户端与service交互的程序接口。有三种方式来定义这个接口。

 

继承Binder类

  如果service是你的程序私有(private)并且与客户端在同一进程运行(这是最常见的情况),你应该继承Binder类来创建接口,并从onBind()方法返回该接口的实例。客户端接收该Binder对象,并通过它来直接使用Binder对象甚至Service的公共方法。

  当你的service仅仅在后台为你自己的程序工作,这是最优先的方式。只有当你的service被别的程序使用或者被跨进程使用,你才不使用这种方式创建接口。

 

使用Messager

  如果希望接口被跨进程使用,你可以为service创建一个带Messager的接口。通过这种方式,service定义一个Handler来处理不同的Message对象。这个Handler是Messager的基础,可以给客户端一个IBinder对象,允许客户端使用Message对象来向service发送请求。另外,客户端可以自己定义一个Messager,这样service就可以把消息传回来。

  这是执行进程间通信(IPC)最简单的方式,由于Messager队列在一个单独的线程里,所以不用把service设计成线程安全。

 

使用AIDL

  AIDL执行所有的工作,把对象分解成操作系统能理解的原语,并把它们编组以跨进程通信。前面提到的使用Messenger的技术,实际上是基于AIDL做为它的底层架构的。就上面所提到的,Messenger在一个线程中,为他的客户端所有的请求创建一个队列,所以服务一次只能接收一个请求。然而,如果你想要你的服务同时处理多个服务,那么你可以直接使用AIDL.这样的话,你的服务必须能同时处理多线程并且被设计成多线程安全的。

  要直接使用AIDL,必须创建一个.aidl文件,该文件定义了程序接口。android SDK tools使用该文件生成一个抽象类,这个抽象类继承了该接口并控制IPC,你可以在service中继承这个抽象类。

 

注意:大多数程序不应该使用AIDL来创建bound service,因为它要求实现多线程,这将导致程序变得复杂。同样,AIDL并不适合大多数程序,本文也不讨论如何为service使用AIDL。如果你确定你需要直接使用AIDL,参看AIDL指导。

 

(额外)Binding to a Started Service

像Service一文中讨论的那样,可以创建一个既是started又是bound的service。就是说,service是被startService()方法启动的,它可以一直运行,并且允许客户端调用bindService()方法来绑定它。

 

如果你允许你的service既是started又是bound,那么当service被启动,系统不会在所有客户端解除绑定之后销毁service。因此,必须通过调用stopSelf()或stopService()显示地终止service。

 

尽管通常情况下,你应该仅实现onBind()或onStartCommand()其中之一,但有时同时实现这两个方法是必要的。例如,音乐播放器需要service在后台一直运行,也需要提供绑定。这样,activity才能启动service来播放音乐,而当用户离开该应用音乐也能继续播放。然后,当用户回到该应用,activity可以绑定到service来取得控制权。

 

请阅读Managing the Lifecycle of a Bound Service,来获取更多关于started service增加绑定时的service生命周期的信息。

 

Extending the Binder class

如果service仅被本地程序使用且不需要做跨进程的工作,就可以通过实现Binder类来使客户端能够直接使用service的公共方法。

 

注意:这仅仅在客户端和service在同一程序和进程中才有效,也最常见。例如,音乐应用程序需要绑定一个activity到它的service来后台播放音乐,这种方式就很合适。

 

下面讲如何设置:

1.在service中创建一个Binder实例,且满足下面之一:

  包含客户端能调用的公共方法;

  返回当前Service类的实例,该实例包含客户端能调用的公共方法;

  或者,返回service内部类的实例,该实例包含客户端能调用的公共方法。

2.在onBind()回调方法中返回该Binder实例。

3.在客户端,从onServiceConnected()回调中接收Binder实例,并使用bound service提供的方法。

 

注意:service和客户端必须在同一程序中,只有这样客户端才能转型返回的对象并正确调用其接口。service和客户端也必须在同一进程中,因为这种方式并不执行任何跨进程的工作。

 

例如,下面的service通过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的实例。这将使客户端调用service的公共方法。例如,客户端可以调用service的getRandomNumber()方法。

 

下面是一个activity绑定了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) */
    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;
        }
    };
}

 上面例子展示了客户端如何使用ServiceConnection类以及onServiceConnected()回调函数来绑定service。下一部分将提供更多关于绑定service的信息。

 

注意:上例并没有明确地从service解除绑定,但是,所有客户端都应该在适当的时候(例如activity进入pauses状态时)与service解除绑定。

 

Using a Messager

如果需要service与外部进程通信,可以使用Messager类为你的service提供接口。这种方式允许你在不使用AIDL的情况下完成进程间通信。

 

Compared to AIDL

需要执行IPC时,使用Messenger比使用AIDL更简单,因为Messenger把所有对service的调用排成队列,与此相反,纯AIDL接口向service同时发送多个请求,所以必须控制多线程。

 

对于大多数程序,service不需要执行多线程工作,所以使用Messenger,让service一次处理一个请求。如果service必须实现多线程,那就应该使用AIDL来定义你的接口了。

 

下面概述如何使用Messager:

  * 在service里创建一个Handler实例,该Handler实例从各个客户端接收回调。

  * 该Handler被用来创建一个Massenger对象(是Handler的引用)。

  * Massager对象创建一个IBinder对象,该IBinder对象被service的onBind()方法返回给客户端。

  * 客户端使用IBinder对象来初始化Messager(that references the service's Handler),客户端使用该Messager来发送Message对象给service。

  * service在它的Handler中接收每个Message——确切地说,是在handleMessage()方法中。

 

在这种方式中,service里并没有可供客户端调用的方法。取而代之的是,客户端传递“消息”(Message对象)而service在它的Handler里接收这些消息。

 

下面是使用Messager接口的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()方法正式service接收传来的Message对象并根据what参数作出相应处理的地方。

 

客户端要做的一切仅仅是基于service返回的IBinder对象创建一个Messager,并使用send()方法发送消息。例如,下面是一个简单的activity,它绑定了service并发送MSG_SAY_HELLO消息给service。

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

注意,本例并没有展示service如何回应客户端。如果希望service作出回应,就在客户端也创建一个Messager。然后当客户端接收到onServiceConnected()回调,它就发送一个Massage给service,该Massage包含了客户端的Massenger——在send()方法的replyTo参数中。

 

关于双向消息通信的例子可以参看MessengerService.java(service)和MessengerServiceActivity.java(client)的例子。

 

Binding to a Service

程序组件(客户端)可以调用bindService()来绑定到某个service。此时Android系统调用service的onBind()方法,该方法返回一个IBinder对象来与service交互。

 

绑定是一个异步的过程。bindService()方法立即返回,但是并不会把IBinder对象返回给客户端。要接收IBinder对象,客户端必须创建一个ServiceConnection对象并把它传给bindService()方法。ServiceConnection对象包含一个回调方法,系统可以通过它来传递IBinder对象。

 

注意:只有activity,service和content provider能够绑定service——你不能把一个broadcast receiver绑定到service。

 

所以,要为你的客户端绑定service,你必须:

1.创建ServiceConnection对象。

  创建ServiceConnection对象必须重写两个方法:

  onServiceConnected():系统调用该方法来传递onBind()方法返回的IBinder对象。

  onServiceDisconnected():Android系统在service意外失去连接时调用该方法,例如service已经crashed或者被kill掉。当客户端解除绑定时该方法不会被调用。

2.调用bindService(),传递ServiceConnection对象。

3.当系统调用你的onServiceConnected()回调方法,就可以使用定义在接口中的方法向service发送请求。

4.要解除绑定,调用unbindService()。

当你的客户端被销毁,它会与service解除绑定,但是你还是应该在与service完成交互时,或者你的activity进入暂停状态(pauses)就解除绑定,这样service就能在它不被使用时及时停止。(绑定或解除绑定的时机将在下面作更多讨论。)

 

例如,下面的程序片段把客户端与service连接(该service是extending the Binder class部分的那个service),该程序所做的仅仅是返回IBinder对象给LocalService类并请求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()方法来绑定service。例如:

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

  * bindService()方法的第一个参数是一个Intent对象,显示指定要绑定的service名字。

  * 第二个参数是ServiceConnection对象。

  * 第三个参数是一个flag,指定绑定的方式。通常情况下该参数的值应该是BIND_AUTO_CREATE,表示service没存活时自动创建它。其他可能的值为BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或者0。

 

Additional notes

下面是当绑定service时一些需要注意的地方:

  * 要时刻追踪DeadObjectException异常,该异常在连接被破坏时抛出。只是运程方法能抛出的唯一异常。

  * 总是应将邦定和解除邦定,成对的出现在客户端生命周期相对应的时刻。比如:

    * 如果只有当你的activity可见时才需要与service交互,那么你应该在onStart()方法中邦定,在onStop()方法中解除邦定。

    * 如果你希望你的activity即使停止在后台,也能接收响应,那么你可以在onCreate()方法中邦定,在onDestroy()方法中解除绑定。注意,这意味着,在你的activity的整个运行时间都要使用该service(即使activity在后台),所以如果service在其他的进程中,那么将增加你进程的比重(weight),并且它可能变得极有可能被杀掉.

 

注意:不应该在activity的onResume()和onPause()方法中执行绑定和解绑的工作,因为这些回调方法经常在activity的生命周期转换时调用,并且你应保持这些转换使用的时间最少化.还有,如果在你的应用中有多个activity,邦定到这一个service上,并在某两个activity之间有转换,该service可能在当前activity解除邦定,下一个邦定前,销毁并重创建了。(activity是怎样在转换中定位生命周期,在Activities文档中描述)

 

Managing the Lifecycle of a Bound Service

 

当一个服务从所有客户端解除邦定,Android系统销毁它(除非它也是用onStartCommand()方法启动的)。因此,如果你的服务纯粹是一个邦定服务,你不需要管理它的生命周期---系统根据它是否邦定到任何客户端来管理它的生命周期.

 

但是,如果你实现了onStartCommand()回调方法,那么你必须显式的停止服务,因为该服务是经过考虑被开启的。在这种情况下,服务一直运行,除非它调用stopSelf()停止自己,或者其他组件调用stopService()停止它,而不管它是否邦定到客户端无关。

 

另外,如果你的服务已开启并且接收了邦定,如你想客户下次邦定到服务时,接收到onRebind()调用,那么当系统调用你的onUnbind()方法时,你可以选择返回true.(而不是从onBind()接收一个调用)onRebind()方法返回void.但客户端仍在它的回调中onServiceConnected()接收IBinder。下图展示了这种生命周期的逻辑。

 

关于启动服务的生命周期更多信息,看Services文档

posted @ 2014-03-25 16:06  沙发土豆  阅读(142)  评论(0)    收藏  举报