API Guides之Bound Services

原文地址 http://developer.android.com/guide/components/bound-services.html

(按照个人理解进行翻译,方便以后查找资料,水平有限,如有错误,还请谅解)

翻译:

Bound Services

一个bound service是客户端——服务器端接口中的服务器端。一个bound service允许组件(例如activity)绑定到service上,发送请求,接收回复,甚至是进程间通信(IPC)。一个典型地bound service,只存在于它为其它组件提供服务的期间,并不会在后台一直运行。

这篇文档向我们展示了如何创建一个bound service,包含其他组件如何绑定到service上。然而,我们也应该参考Services文档,获取一般情况下的service的信息,例如如何从一个service发送通知信息,使service在前台运行,等等。

The Basics

一个bound service是一个Service类的实现,它允许其他应用程序组件绑定到它身上并和它交互。为了使一个service提供绑定功能,我们必须实现onBind()回调方法。这个方法返回一个IBinder对象,它定义了客户端用来与服务器端交互的程序接口。

一个客户端可以通过调用bindService()绑定到service上。当客户端这么做了,它必须提供一个ServiceConnection的实现,用来监视与service的连接状况。bindService()将会立即返回,无返回值,但是当Android系统创建客户端与服务器端的连接时,系统会调用ServiceConnection中onServiceConnected()方法,并传递一个客户端用来与服务器端通信的IBinder。

多个客户端可以同时连接到相同的service上。然而,只有第一个客户端绑定时,系统才会调用onBind()方法返回一个IBinder。接下来,系统会将相同的IBinder发送给其他绑定的客户端,不会再次调用onBind()。

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

当我们实现bound service时,最重要的部分是定义onBind()回调方法返回的接口。有几种不同方式可以定义我们的service的IBinder接口,下面的部分将会讨论每一种技术。

Binding to a started Service

正如在Services文档中讨论的一样,我们可以同时以started和bound两种方式创建一个service。那就是说,这个service可以被startService()调用启动,使它一直运行,并且也允许客户端通过调用bindService()绑定到它身上。

如果我们允许service成为started和bound,那么当所有客户端解绑定时,如果这个service曾经启动过(started),系统将不会销毁它。反而,我们必须调用stopSelf()或stopService()显示地停止这个service。

尽管我们通常只实现onBind()或onStartCommand(),但有时候必须实现两者。例如,我们可能会发现,允许一个音乐播放器的service一直运行并提供绑定,非常有用。这种情况下,一个activity可以启动service播放音乐,甚至当用户离开应用程序时,音乐也继续播放。接下来,当用户回到应用程序时,这个activity重新绑定到service上,并获取播放的控制权。

一定要读Managing the Lifecycle of Bound Service部分,获取当添加绑定到一个started service上,关于这个service生命周期的更多信息。

Creating a Bound Service

当创建一个提供绑定的service时,我们必须提供一个IBinder,它规定了客户端与服务器端交互的程序接口。这里有三种定义接口的方式:

Extending the Binder class

如果我们的service为应用程序私有,并与客户端运行在相同的进程中(大多数情况),我们应该继承Binder类来创建接口并在onBind()方法中返回一个实例。客户端接收这个Binder实例,并使用它直接访问Binder实现的可用的public方法,或者service的public方法。

当我们的service仅仅是我们的应用程序的后台工作者,这是首选技术。唯一我们不能以这种方式创建接口的原因是,我们的service被其他应用程序使用,或在别的进程中运行。

Using a Messenger

如果我们需要接口工作于不同的进程中,我们可以用一个Messenger(信使)创建一个service的接口。在这种方式下,这个service定义一个Handler处理不同类型的Message对象。这个Handler是Messenger的主要部分,它能与客户端共享IBinder,允许客户端使用Message对象发送一个命令。此外,客户端可以定义它自己的 Messenger,使service也能向它发送消息。

这是执行IPC最简单的方式,因为Messenger队列中的所有请求都在单线程中,因此我们不必考虑service里的线程安全问题。

Using AIDL

AIDL(Android接口定义语言)所做的工作就是,将对象分解成操作系统能理解的原始类型,并通过IPC传递它们。之前使用Messenger的技术,实际上基于AIDL作为它的底层结构。正如上面提到的一样,Messenger在单线程中创建一个队列接收所有客户端的请求,因此service一次接收一个请求。如果我们想要我们的service同时处理多个请求,那么我们可以直接使用AIDL。在这种情况下,我们的service必须是多线程并且线程安全的。

为了直接使用AIDL,我们必须创建一个定义了程序接口的 .aidl 文件。Android SDK工具集使用这个文件自动生成,一个实现了接口并处理IPC的抽象类,可以在我们的service中继承它。

注意:大多数应用程序不应该使用AIDL创建一个bound service,因为它需要多线程,会导致更复杂的实现。正因如此,AIDL不适合大多数应用程序,这篇文档也不会讨论如何在我们的service中使用它。如果我们确定需要使用AIDL,请查看AIDL文档。

Extending the Binder class

如果我们的service只在本地应用程序使用,并且不需要跨进程工作,那么可以实现我们自己的Binder类,它提供客户端直接访问service里面的公共方法。

注意:这项工作只适合客户端和service在相同的应用程序并在相同的进程中,大多数情况都是如此。例如,对于一个音乐类应用,它只需绑定一个activity到它自己的service上,用来在后台播放音乐,这会工作的很好。

下面是编写步骤:

1. 在我们的service里,创建一个Binder实例,要么:

        * 包含客户端可以调用的公共方法

        * 返回当前Service实例,它包含客户端可以调用的公共方法

        * 或者,返回一个托管service的其它类的实例,它包含客户端可以调用的公共方法

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

3. 在客户端里,接收从onServiceConnected()回调方法返回的Binder,使用它调用bound service提供的方法。

注意:service和客户端必须在同一个应用程序的原因是,客户端可以确定返回的对象类型(进行强制类型转换),并恰当的调用它的APIs。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暂停时)。

想要更多的示例代码,请查看ApiDemos里面的LocalService.java类和LocalServiceActivities.java类。

Using a Messenger

如果我们需要我们的service与远程进程通信,那么我们可以使用一个Messenger提供service的接口。这项技术允许我们不通过使用AIDL来执行IPC。

下面概括如何使用一个Messenger:

* service实现一个Handler,处理客户端的每个请求

* 这个Handler被用来创建一个Messenger对象(一个Handler的引用)

* 这个Messenger创建一个IBinder,service在onBind()回调时将其返回给客户端

* 客户端使用这个IBinder实例化Messenger(service中Handler的引用),然后使用这个实例发送Message给service

* 这个service在它的Handler中接收每一个Message——其实,在handleMessage()方法中。

以这种方式,service里面没有方法给客户端调用。取而代之,客户端发送“messages”(Message对象),service在它的Handler中接收。

下面是使用Messenger接口的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的地方,并且根据message里what的值,决定做什么。

客户端所需做的是,基于service返回的IBinder创建一个Messenger,使用send()发送message。例如,下面是简单示例,一个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回复,那么我们也需要在客户端中创建一个Messenger。接下来,当客户端从onServiceConnected()返回时,它发送一个Message给service,在send()方法的replyTo参数中包含客户端的Messenger。

我们可以在MessengerService.java(service)和MessengerServiceActivities.java(client)示例中看到,如何提供两种message的例子。

Compared to AIDL

当我们需要执行IPC时,使用一个Messenger实现接口要比使用AIDL简单,因为Messenger排队所有的service请求,而纯粹的AIDL接口同时发送多个请求给service,导致必须用多线程处理。

对于大多数应用程序而言,service不需要执行多线程操作,因此使用一个Messenger一次处理一个service请求。如果我们的service使用多线程很重要,那么应该使用AIDL定义我们的接口。

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

    我们的实现必须重写两个回调方法:

    onServiceConnected()

            系统调用这个方法接收从service的onBind()方法返回的IBinder。

    onServiceDisconnected()

            当与service的连接异常丢失时,android系统会调用这个方法。例如当service崩溃或被销毁时。当客户端解绑定时,这个方法不会被调用。

2. 调用bindService(),传递ServiceConnection的实例。

3. 当系统调用我们的onServiceConnected()回调方法时,我们可以使用接口中定义的方法,发送请求给service。

4. 调用unbindService()将断开与service的连接。

当我们的客户端被销毁时,它将与service解绑定。但是当完成与service的交互时,或我们的activity暂停,以至于service在它不被使用时可以关闭,我们总是应该主动的解绑定。(下面会更多的讨论,绑定与解绑定的恰当时间。)

例如,下面的代码段连接客户端,与上面使用继承Binder类创建的service。因此,客户端所需做的就是,将返回的IBinder强制类型转换为LocalBinder类(原文为LocalService,这里我认为应该是LocalBinder),并获得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的名字(想象intent也能是隐式的)。

* 第二个参数是一个ServiceConnection对象。

* 第三个参数是一个标志,它指出绑定选项。为了使service还没有活动时也能被创建,通常应该为BIND_AUTO_CREATE。其他可能的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或无意义的0。

Additional notes

下面是绑定到一个service上的一些要点:

* 我们总是应该捕获DeadObjectException异常,当连接无效时抛出。这是远程方法抛出的唯一异常。

* 跨进程的对象引用计数。

* 通常我们应该将绑定和解绑定放在客户端生命周期的开始与结束。例如:

        * 如果我们只需当activity可见时,与service交互,应该在onStart()绑定,并在onStop()解绑定。

        * 如果想要我们的activity在后台停止时也接收回复,那么我们可以在onCreate()绑定,并在onDestroy()解绑定。这意味着我们的activity在它运行的整个周期(甚至是在后台),都需要使用service。因此,如果service在另一个进程,我们要增加这个进程的优先级,否则它更有可能会被系统销毁。

注意:通常,我们不应该在activity的onResume()和onPause()中绑定和解绑定,因为这些回调发生生命周期的每一次转变中,我们应该使转变过程中的处理降到最低。同样地,如果应用程序中多个activity绑定到同一个service上,在这些activity的两者之间service也会发生转变,随着当前activity解绑定(pause期间)发生在下一个activity绑定(resume期间)之前,这个service有可能被销毁然后重新创建。(这里关于activity如何进行它们的生命周期的转变,在Activities文档中描述。)

更多的示例代码展示了如何绑定到一个service上,请查看ApiDemos中的RemoteService.java类。

Managing the Lifecycle of a Bound Service

当一个service被所有客户端解绑定时,Android系统会销毁它(除非它也被用onStartCommand()启动过)。同样地,如果service仅仅是一个bound service,我们不需要管理它的生命周期——根据它是否被其它客户端绑定,Android系统为我们进行管理。

无论如何,如果我们选择实现onStartCommand()回调方法,那么我们必须显式的停止这个service,因为此时这个service被认为是started。在这种情况下,无论它是否被其它客户端绑定,service将一直运行直到它自己调用stopSelf()或其它组件调用stopService()。

此外,如果我们的service是started并接受绑定,那么当系统调用onUnbind()方法时,如果我们想要在下一次客户端绑定到service时执行onRebind()调用(而不是onBind()调用),我们可以选择性的返回true。onRebind()返回void,但是客户端仍然可以在onServiceConnected()方法中接收IBinder。下面,图1展示了这种service的生命周期。

API Guides之Bound Services_第1张图片

图1. 一个既是started有接收绑定的service的生命周期。

想要更多关于一个started service生命周期的信息,请查看Services文档。

你可能感兴趣的:(Bound,Services,Android)