[翻译]Android Bound Services

[亡魂溺海] 提交于 2019-12-07 16:17:59

一个bound service是一个client-server接口中的server端。一个bound service允许应用组件(比如activities)bind到它,发送请求,接收响应,甚至是执行进程间通信(IPC)。一个bound service在典型情况下,只有在它服务于另一个应用组件时才存活,而不是在后台无限期的运行。

这份文档向您说明了要如何创建bound service,包括在其他的应用组件中如何bind到service。然而,你也应该参考Services文档来大体地了解关于services的额外信息,比如如何在service中传送通知,设置service在前台运行,等等。

基本概念

一个bound service是一个Service类的实现,它允许其它应用bind到它并与它交互。为了给一个service提供binding功能,你必须实现onBind()回调方法。这个方法返回一个IBinder对象,该对象则定义了客户端可以用来与service进行交互的编程接口。

Binding到一个Started Service

如同在Services文档中讨论的那样,你可以创建一个service,既可以被started,也可以被bound。即,service可以通过调用startService()被started,从而允许service无限期的运行,也可以允许一个客户端通过调用bindService()来bind到service

如果你确实允许你的service被started和bound,则当service已经被started时,系统不会在所有的客户端都unbind时销毁那个service。相反,你必须通过调用stopSelf()stopService()来显式地停止那个service。

尽管你通常应该实现onBind()onStartCommand()之一,但有时需要同时实现两者。比如,一个音乐播放器可能会发现同时允许它的service无限期的运行及提供binding是很有用的。以这种方式,一个activity可以start service来播放一些音乐,而在用户离开了应用后音乐也可以继续播放。然后,在用户回到应用时,activity可以bind到service来恢复对回放的控制。

请确保阅读了关于管理一个Bound Service的生命周期的部分,来了解更多关于给一个started service添加binding功能时service生命周期的信息。


一个客户端可以通过调用bindService()来bind到service。当它bind时,它必须提供一个ServiceConnection的实现,其者监视与service之间的连接。bindService()将立即返回,并且没有返回值,但当Android系统创建客户端和service之间的连接时,它会调用ServiceConnectiononServiceConnected(),来传送IBinder给客户端用于和service进行通信。

然而,系统只有在第一个客户端binds时才会去调用你的service的onBind()来获取IBinder。随后系统传送相同的IBinder给其它bind的客户端,而不再次调用onBind()

当最后一个客户端从service unbinds时,系统将销毁service(除非service也通过startService()被started了)。

当你实现你的bound service时,最重要的部分是定义你的onBind()回调方法返回的接口。你可以用一些不同的方法来定义你的service的IBinder接口,下面的小节将讨论每种技术。

创建一个Bound Service

当创建一个提供binding功能的service时,你必须提供一个客户端可以用于与service端交互的提供了编程接口的IBinder。有三种方法你可以用来定义这样的接口:

  • 扩展Binder类

    如果你的service是你自己的应用私有的,并且与客户端运行在相同的进程中(很常见的情况),你应该通过扩展Binder类来创建你的接口,并在onBind()方法中返回一个它的实例。客户端接收到Binder,然后就可以使用它来直接访问Binder实现或Service的可用的public方法。

    当你的service只是你自己的应用的一个后台工作者时,这是首选的技术。不使用这种方式来创建你的接口的仅有的理由就是,你的service被其他应用或跨越不同的进程被用到了。

  • 使用一个Messenger

    如果你需要你的接口跨进程工作,你可以借助于一个Messenger来为你的service创建一个接口。用这种方式,service定义了一个Handler,来响应不同类型的Message对象。这个Handler是一个Messenger的基础,然后后者可以与客户端共享一个IBinder,允许客户端使用Message对象给service发送命令。此外,客户端可以定义一个它自己的Messenger以使service可以发送消息回去。

    这是执行进程间通信(IPC)最简单的方式,因为Messenger把所有的请求入队到一个单独的线程,因此你不需要为线程安全而外设计你的service。

  • 使用AIDL

    AIDL (Android Interface Definition Language)执行所有将对象分解成操作系统可以理解的元语的工作,并在进程之间处理它们来执行IPC。前面的一种技术,即使用一个Messenger,实际上是以AIDL为它的底层结构的。如上面提到的,Messenger在一个单独的线程中创建一个所有的客户端请求的队列,以使service在某一时刻只接收一个请求。如果,然而,你想要你的service并行的处理多个请求,那么你可以直接使用AIDL。在这种情况下,你的service必须具有多线程的能力,并且要被构建的是线程安全的。

    要直接使用AIDL,你必须创建一个定义了编程接口的.aidl文件。Android SDK工具使用这个文件来产生一个实现了接口并处理IPC的抽象类,随后你可以在你的service中扩展这个抽象类。

注意:大多数应用不应该使用AIDL来创建一个bound service,因为它可能需要多线程能力,并可能导致一个更复杂的实现。因此,AIDL对于大多数应用都是不合适的,并且这份文档中不讨论如何使用它来实现你的service。如果你确认你需要直接使用AIDL,请参考AIDL文档。

扩展Binder类

如果你的service只被本地应用用到了,并且不需要跨进程工作,那么你可以实现你自己的Binder类,来为你的客户端提供直接访问service中的public方法的能力。

注意:这种方法只有在客户端和service在相同的应用和进程中时才有效,当然这种情况很常见。例如,对于一个音乐应用,需要bind一个activity到它自己的在后台播放音乐的service,就很有效。

这里是如何设置它:

  1. 在你的service中创建一个下述类型之一的Binder实例:

    (1). 包含了客户端可以调用的public方法

    (2). 返回当前的Service实例,其中具有客户端可以调用的public方法

    (3). 或者,返回另一个类的实例,其中寄宿了service,具有客户端可以调用的public方法。

  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中的public方法。比如,客户端可以调用service的getRandomNumber()。

这里是一个activity,它bind到LocalService,并在button被点击时调用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()回调bind到service。下一节将提供更多关于binding到service的这一过程的信息。

注意:上面的例子没有显式地从service unbind,但所有的客户端都应该在一个适当的时间点 (比如当activity pause时) unbind。

.更多示例代码,请参考ApiDemos中的LocalService.java类和LocalServiceActivities.java类。

使用一个Messenger

与AIDL的比较

当你需要执行IPC时,使用一个Messenger来实现你的接口比通过AIDL来实现它要简单,因为Messenger将所有的调用入队到service,然而,一个纯粹的AIDL接口发送并发的请求给service,这将必须要处理多线程。

对于大多数应用,service不需要执行多线程,因此使用一个Messenger允许service每次只处理一个调用。如果你的service一定要是多线程的,那么你应该使用AIDL来定义你的接口。


如果你需要你的service与远端进程通信,那么你可以使用一个Messenger来为你的service提供接口。这项技术允许你在不需要使用AIDL的情况下执行进程间通信(IPC)。

这里是一个如何使用一个Messenger的总结:

以这种方式,客户端不调用service的"方法"。而是,客户端传送"消息" (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并根据what成员决定做什么的地方。

一个客户端所需要做的就是创建一个基于service返回的IBinderMessenger,并使用send()来发送一个消息。比如,这里是一个简单的activity,它binds到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)来看一下如何提供两路消息的例子。

Binding到一个Service

应用组件(客户端)可以通过调用bindService()来bind到一个service。然后,Android系统调用service的onBind()方法,它会返回一个IBinder用于与service交互。

binding是异步的。bindService()会立即返回,而不给客户端返回IBinder。要接收IBinder,客户端必须创建一个ServiceConnection的实例,并把它传递给bindService()ServiceConnection包含一个回调方法,系统可以调用它来传送IBinder

注意:只有activities,services,和content providers可以bind到一个service——你不能在一个broadcast receiver中bind到一个service。

因而,要在客户端中bind到一个series,你必须:

  1. 实现ServiceConnection.

    你的实现必须覆写两个回调方法:

    onServiceConnected()

        系统调用这个方法来传送service的onBind()方法返回的IBinder

    onServiceDisconnected()

        当与service的连接意外断开时,Android系统会调用它。比如当service发生了crash或已       经被杀掉时。当client unbinds时则不会被调到。

  2. 调用bindService(),并传入ServiceConnection的实现。

  3. 当系统调用了你的onServiceConnected()回调时,你就可以开始调用service了,使用接口所定义的方法。

  4. 要从service断开连接,则调用unbindService()

    当你的客户端被销毁时,它将从service unbind,但你应该总是在你完成了与service的交互或者你的activity pause时unbind,以使得service可以在它没有被用到时关闭。(下面有更多关于bind和unbind的合适的时间的讨论。)

例如,下面的代码片段把客户端与上面通过扩展Binder类创建的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()来bind到一个service。比如:

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

补充说明

这里是关于binding到一个service的一些重要的说明:

  • 你应该总是捕获DeadObjectException一场,当连接破坏时它会被抛出。这是远程方法抛出的仅有的异常。

  • 对象是在进程间引用计数的。

  • 你通常应该在客户端的生命周期中对应的bring-up和tear-down的时刻成对的调用binding和unbinding。比如:

    如果你只需要在你的activity可见时与service交互,则你应该在onStart()中bind,并在onStop()unbind。

    如果你想要你的activity即使已经被stopped了,也要在后台接收响应,则你可以在onCreate()中bind,并在onDestroy()中unbind。注意,这意味着你的activity需要在它运行的所有时间都使用service(即使是在后台),因此如果service在另一个进程,则你将增加那个进程的weight,并且它变得更可能被系统杀掉。

注意:通常你不应该在你的activity的onResume()onPause()中bind和unbind,因为这些回调在每一个生命周期事务中都会发生,你应该把这些事务中发生的处理量降到最低。如果你的应用中的多个activities bind到相同的service,并且在这些activities中的两个之间有一个事务,则在当前的activity unbinds (在pause中)之后,下一个binds (在resume中) 之前可能会发生service的销毁和重建。(这种activities如何协调他们的生命周期的activity事务,在Activities文档中有描述)。

更多演示如何bind到一个service的示例代码,可以参考ApiDemos中的RemoteService.java类。

管理一个Bound Service的生命周期

当所有的客户端都从service unbound时,Android系统会销毁它(除非它也同时通过onStartCommand()被started了)。因此,你不需要管理你的service的生命周期,如果它是纯粹的bound service的话——Android系统会基于它是否被bound到了客户端来为你管理它。

然而,如果你选择实现onStartCommand()回调方法,则你需要显式的来停止service,因为现在service被认为是被started的了。在这种情况下,service会一直运行,直到service通过stopSelf()来终止它自己,或者另一个组件调用stopService(),而不管它是否被bound到任何的客户端。

此外,如果你的service是被started的,并接受binding,那么当系统调用了你的onUnbind()方法,如果你想要在下次客户端binds到service时接收一个对onRebind()的调用(而不是接收一个对onBind()的调用),你可以选择返回true。onRebind()返回void,但是客户端仍然在它的onServiceConnected()回调中接收IBinder。下面的图1描绘了这种生命周期的逻辑。

图1. The lifecycle for a service that is started and also allows binding.

更多关于一个started的service的生命周期的信息,请参考Services文档。

Done。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!