Android官方文档笔记:Bound Services

bound service是CS接口中的服务端。一个bound service允许组件(例如activity)与它绑定,发送请求,接受回应甚至执行IPC。一般情况下,一个bound service只有在服务其余应用组件的时候才存活而不会单独的在后台运行。

基础

bound service实现了service的接口,允许其他组件与它绑定并与它交互。你必须实现onBind回调方法来给service提供绑定。此方法返回一个IBinder对象,该对象定义了客户端可用于与service交互的编程接口。

一个客户端可以调用bindService来与service绑定。这时客户端必须提供一个ServiceConnection的实现来监控与service的连接。bindService方法会直接返回,不带任何值。但是如果Android系统创建了客户端与service之间的连接,它会调用ServiceConnectiononServiceConnected方法来传递一个IBinder对象给客户端,这个IBinder可以用来与service通讯。

多个客户端可以立刻连接到一个service。然而,系统只会在第一个客户端与service绑定的时候调用onBind方法来检索IBinder。然后系统会返回相同的IBinder给其余的绑定客户端,而不是再次调用onBind。

当最后一个客户端与service解绑的时候,系统会销毁该service。(除非该service已经被startServie方法启动过)

如果你允许你的service可以被start也可以被绑定,那么当它被start后,所有客户端与它解绑的时候,系统是不会销毁它的。你还需要调用stopSelf或者stopService来停止它。

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

创建一个bound service

当创建一个可以绑定的service的时候,你必须提供一个IBinder,该IBinder提供了客户端可以与service交互的编程接口。这里有三种方式可以定义这个接口:

继承Binder类

如果你的service是你应用私有的,和客户端运行在一个进程的,你应该继承实现Binder类来创建你的接口,并且在onBind方法中返回该接口的实例。客户端接收到Binder后可以用它来直接访问Binder实现中的甚至service中的公开方法。

当你的service仅仅是你应用程序的后台工作人员的时候,这是首选的技术。你不使用这种方式创建你的service的唯一的原因是你的service可以被外部应用使用或者可以跨进程。

使用Messenger

如果你需要你的接口在不同的线程中工作,你可以用Messenger来给service创建一个接口。以这种方式,service定义了一个handler来回应不同类型的Message对象,这个Handler是Messenger的基础,可以和客户端分享一个IBinder,允许客户端用Message向service发送指令。客户端可以定义自己的Messenger,这样service就可以发送Message回来。

这是最简单的方式来进程间通讯,因为Messenger将所有的请求在一个线程中排出队列所以你不需要考虑你的service要是线程安全的。

使用AIDL

AIDL(Android接口定义语言)执行所有的工作,将对象分解成操作系统可以理解的原语,并在进程间编组它们以执行IPC。前面的技术使用了Messenger,这也是基于AIDL作为它的底层结构。像上面提到的,Messenger在单个线程中给所有的客户端请求创建了一个队列,所以service同一时间只能接收到一个请求。如果你想要你的service可以同时处理多个请求,你可以直接使用AIDL。这时候,你的service必须能支持多线程且是线程安全的。

要直接使用AIDL,您必须创建一个定义编程接口的.aidl文件。Android SDK工具使用此文件生成一个抽象类,该类实现接口并处理IPC,然后可以在你的service中继承它。

注意:大多数的应用不需要通过AIDL来创建一个bound service。因为这需要支持多线程且导致更加复杂的实现。因此,AIDL不适合大多数的应用,这篇文章也不会介绍如何给service使用它,如果你确定要直接使用AIDL,请查看专门的AIDL文章。

继承Binder类

如果你的service只在本应用中使用,不需要跨进程工作,那么你可以实现你自己的IBinder来让你的客户端直接访问service中的公开方法。

注意:这只有在service和client在同一个进程中才有效。

以下是设置方法:

  1. 在你的service中,用以下方法中的一个来创建IBinder实例:

    • 包含client可以调用的公开方法
    • 返回当前的service实例,该service包含client可以调用的公开方法
    • 返回一个service持有的对象,client可以访问该对象的公开方法
  2. onBind回调方法中返回该IBinder的实例。

  3. 在client中,接受从onServiceConnected回调方法中传来的IBinder,然后可以通过提供的方法调用service。

说明:service和client必须在同一个线程中的原因是client可以将返回的对象转型并正确的调用它的API。服务和客户端也必须处于同一个进程中,因为此技术不会跨进程执行任何编组。

下面有个例子,这个service通过实现Binder来让client可以访问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方法给client来获取当前LocalService的实例。这将允许client调用service的公开方法。例如client可以调用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;

        }

    };

}

使用Messenger

如果你的service需要和远程进程通信,你可以使用一个Messenger来给你的service提供接口。这项技术可以让你不使用AIDL就能进行IPC。

以下是如何使用Messenger的摘要:

  • 该service实现了一个handler来接受来自client的每个调用的回调。
  • Messenger创建了一个IBinder,service将该IBinder通过onBinder方法返回给client。
  • 客户端使用IBinder实例化Messenger(引用服务的Handler),客户端用它将Message对象发送到service。
  • service在它的Handler中接收到每个Message,明确的说,在handleMessage方法中。

以这种方式,client在service上没有方法可以调用,取而代之的是,client可以发送信息,service可以在Handler中接受信息。

以下是一个service使用Messenger的简单的例子:

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();

    }

}

client需要做的就是使用service返回的IBinder创建一个Messenger并且使用它的send方法发送信息。下面是一个activity绑定到上面service并发送信息给该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如何对client做出回应。如果你想要你的service做出回应,那么你也需要在client中创建一个Messenger。然后当client接收到onServiceConnected回调的时候,client发送了一条Message给service,这条Message在它的reply参数中包含了client的Messenger

绑定一个service

应用组件(client)可以 通过调用bindService来和一个service进行绑定。Android系统会调用service的OnBind方法,该方法返回一个可以与service交互的IBinder

绑定是异步的,bindSercice会立刻返回但是不会返回IBinder给client,client必须创建一个ServiceConnection实例并将它传递给bindServiceServiceConnection包含了一个方法,系统会调用这个方法传递IBinder

说明:只有activity,service和content provider可以和service绑定-你不能将broadcast receiver和service绑定。

所以,为了能后绑定service,你必须要做到:

  1. 实现ServiceConnection

你的实现必须重写两个方法:

  • onServiceConnected()
    系统调用这个方法来传送从onBind方法返回的IBinder给client
  • onServiceDisconnected()
    Android系统会在client与service的连接异常断开的时候(例如service崩溃或者被杀死)调用该方法。client解绑的时候不会调用该方法。
  1. 调用bindService,传递ServiceConnection的实现。

  2. 当系统调用onServiceConnected回调方法时,你可以使用接口定义的方法开始调用service。

  3. 调用unBindService来解绑。

当client销毁的时候,会与service解绑。但是在你与service交互完成之后或者在activity pause的时候,你应该解绑。以便service可以在不使用的时候关闭。

下面的例子连接了client和一个通过继承Binder对象来创建的service。所有要做的就是将返回的IBinder转型为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;

    }

};

client将该ServiceConnection传递给bindService来使用它。例如:

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

bindService()的第一个参数是一个Intent,它明确地命名要绑定的服务(该intent可能是隐式的)。
第二个参数是ServiceConnetction对象
第三个参数是一个指示绑定选项的标志。它通常应该是BIND_AUTO_CREATE以便于在service没有存活的时候创建它。其余可能的值为BIND_DEBUG_UNBINDBIND_NOT_FOREGROUND或者0表示没有。

附加说明:

以下有一些关于绑定service的重要说明:
你应该总是捕获DeadObjectException,这会在连接被打断的时候抛出。这是远程方法唯一抛出的一个异常。
对象是跨进程引用计数的

你通常应该将绑定和解绑与对应的client的生命周期中的创建和销毁对应起来。例如:

  • 如果你想你的activity在可见的时候与service交互,你应该在onSatrt期间绑定在onStop期间解绑。
  • 如果你想你的activity在stop的时候仍然可也接受到回应,那么你可以在onCreate中绑定,在onDestroy中解绑。注意,这意味着你的activity需要在整个生命周期中使用service(即使是在后台),如果该service是在另一个进程。你增加了该进程的负担,系统更有可能杀死它。

在activity的onResumeonPause期间,通常不应该绑定和解除绑定,因为这些回调会在每个生命周期转换时发生,并且应该将在这些转换时发生的处理保持在最低限度。 另外,如果应用程序中的多个activity绑定到相同的service,并且这两个活动之间存在转换,则可能会因为当前activity在下一个activity绑定(在resume期间)之前解除绑定(在pause期间)而被破坏并重新创建。

管理bound service的生命周期

如果一个service被所有的组件解绑,那么Android系统就会销毁它(除非它也被onStartCommand启动了)。因此,如果你的service是一个单纯的bound service,那么你不用管理它的生命周期-Android系统会根据是否有client与之绑定来为你管理。

然而,如果你选择了实现onSatrtCommand回调方法,那么你必须明确的停止service,因为现在该service被认为是started。这种情况下,该service会一直运行到自己调用stopSelf或者其余组件调用stopService来停止它,而不管它是否与所有的client解绑。

除此之外,如果你的service是started且接受绑定,那么当系统调用onUnbind方法的时候,你可选择返回true,如果你想在下次一个client与这个service绑定的时候接受到一个onRebind的回调。onRebind返回void,但是client还是会在onServiceConnected回调中接收到IBinder。下面的图说明了这种生命周期的逻辑:

image.png

你可能感兴趣的:(Android官方文档笔记:Bound Services)