Android开发指南中文版(六)Bound Services

Bound Services

bound service在客户端-服务器接口中作为服务器。一个bound service允许组件(比如activity)绑定到service,发送请求,接收响应,甚至执行进程间通信(IPC)。一个bound service通常在为其他程序组件服务时才存在并且不会无限期的在后台运行。

这个文档将显示如何创建一个bound service,包括如何绑定到其他应用程序的service。

基本原理

一个bound service是一个Servcie类的实现,它允许其他应用程序绑定到它上面并与之交互。为了为service提供绑定,你必须实现onBind()回调函数。这个函数返回一个IBinder对象,该对象定义了客户端与service交互的编程接口。

一个客户端可以通过调用bindService()绑定到service。它必须提供一个ServiceConnection的实现,该实现监听与service的连接。这个bindService()方法没有返回值立即返回,但是当Android系统创建客户端与service之间的连接时,它调用ServiceConnection的onServiceConnected()函数来传递一个IBinder,客户端可以用其与service通信。

多个客户端可以同时与service连接。然而,在第一个客户端绑定到service时,系统调用你的service的onBind()方法来获得IBinder。然后系统将同一个IBinder传递给之后添加的所有绑定客户端,而不会再调用onBinder()。

当最后一个客户端从service解除绑定时,系统会destroy这个service(除非service通过调用startService()启动)。

当你实现一个bound service,最为重要的部分是定义你的onBinder()回调函数返回的接口。有多种途径来定义你的service的IBinder接口。

创建一个 Bound Service

创建IBinder的几种途径:

  • 继承Binder
    如果你的service对你的应用程序是私有的并与客户端运行在相同的进程中,你应该通过继承Binder类创建接口并从onBind()返回。客户端接收到这个Binder并用它直接访问在Binder实现的甚至是Service的公共可用的方法。
    当你的service仅仅是一个自己应用程序的后台worker时,这种方法首选。你不会使用这种方法创建一个接口的唯一原因是你的service被其他的应用程序使用或者使用了单独的进程。
  • 使用Messenger
    如果你需要你的接口在不同的进程使用,可以使用Messenger为service创建一个接口.使用这种方式,service定义一个Handler来回应不同类型的Message对象。这个Handler作为可以与客户端共享IBinder的Messenger的基础,允许客户端使用Message对象给service发送命令。另外,这个客户端可以定义一个自己的Messenger,所以service可以送回消息。
    这是来执行进程间通信最简单的方式,因为这个Messenger查询所有的单独的线程的请求,所以你不需要将你的service设计成线程安全的。
  • 使用AIDL
    AIDL(Android接口定义语言) 执行所有的工作来将对象分解为操作系统可以理解的原始状态并安排它们来跨线程执行IPC。先前使用Messenger的方式事实上是使用AIDL作为它的底层结构。如前面提到的,这个Messenger在一个单独的线程中创建一个所有客户端请求的队列,所以service依次接收请求。然而,如果你希望你的service来同时处理多个请求,你可以直接使用AIDL。在这种情况下,你的service必须是多线程的并且可以用线程安全的方式建立。
    为了直接使用AIDL,你必须创建一个.aidl文件来声明编程接口。Android SDK工具使用这个文件来生成一个实现了接口并处理IPC的抽象类,该类可以在你的service内部继承

Note:多数应用程序不应该使用AIDL来创建bound service,因为它或许需要多线程能力并导致更为复杂的实现。因此,AIDL不适合大多数的应用程序。本文档不会讨论如何为你的service使用它,如果你确信需要直接使用AIDL,查看AIDL文档。

下面依次介绍每种途径:

继承Binder

如果你的service只在本地程序使用并且不需要跨进程工作,这时你可以实现你自己的Binder类来为你的客户端提供对service公有函数的直接访问。

Note:这种方式只有在客户端和service在同一个程序中时有效。比如说,一个音乐程序需要绑定一个activity到它自己的service来在后台播放音乐,采用这种方式将很有效。

下面是如何建立它:

    1. 在你的service中创建一个Binder的实例,该实例:
      或者包含客户端调用的公共函数
      或者返回当前Service的实例,该实例包含客户端可以调用的公共函数
      或者返回另一个类的实例,这个类包含客户端可以调用的公共函数且其宿主为service
    2. 从onBind()回调函数返回这个Binder实例
    3. 在客户端从onServiceConnected()回调函数接收这个Binder并使用其提供的方法访问bound service。

Note:service和客户端必须在同一个程序的原因是客户端可以对返回的对象转换并正确的调用其API。service和客户端同样必须在同一个进程中的原因是因为这项技术不执行任何跨进程编组(marshalling)。

比如,下面的例子显示service通过实现Binder使客户端能访问其内部方法

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;//要绑定的目标service
    boolean mBound = false;//是否绑定了service
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    @Override
    protected void onStart() {
        super.onStart();
        //绑定到LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 
    }
    @Override
    protected void onStop() {
        super.onStop();
        //从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和onServiceConnection()回调函数将客户端绑定到service。下一节将提供更多关于处理绑定的service的信息。

Note:上面的例子没有明确的从service解绑定,但是所有客户端应该在合适的时机解除绑定(如当activity pause 时)。

使用Messenger

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

下面是如何使用Messenger:

  • Service实现一个Handler为来自客户端的访问接收回调
  • Handler是用来创建Messenger对象(它是一个对Handler的引用)
  • Messenger创建一个IBinder并由service通过onBind()返回给客户端。
  • 客户端使用IBinder来实例化Messenger,客户端使用它来给service发送Message对象
  • Service在其Handler接收每个Message---尤其是在handleMessage()方法中。通过这种方式客户端没有”方法”来访问service,取而代之的是客户端传递”消息”(Message对象),消息在service的Handler中接收。

下面是一个使用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();
    }
}

注意Handler中的handleMessage()方法是service接收传递过来的Message和基于what参数决定做什么的地方。

客户端要做的是基于service返回的IBinder创建一个Messenger并使用send()发送消息。比如说,下面是一个简单的绑定到service的activity并传递了一个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给服务器,该消息在send()方法的replyTo参数包含了客户端的Messenger。

绑定到Service

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

这个绑定是异步的。bindService()立即返回并且不将IBinder返回给客户端,为了接收这个IBinder,客户端必须创建一个ServiceConnection的实例然后将其传递给bindService()。这个ServiceConnection包含供系统传递IBinder的回调函数。

Note:只有activity、service和content provider可以绑定到service---你不能把一个broadcast receiver绑定到service。

所以,为了将客户端绑定到一个service,你必须:

  1. 实现ServiceConnection。
    你的实现必须重写两个回调函数:
    onServiceConnected():系统调用该函数来传递由service的onBind()方法返回的IBinder。
    onServiceDisconnection():对service的连接意外丢失,比如当service崩溃或被kill时,Android系统会调用该函数。当客户端解除绑定时不会调用。
  2. 调用bindService(),传递ServiceConnection实现。
  3. 当系统调用你的onServiceConnected()回调函数时,你就可以使用接口中定义的方法开始访问service
  4. 为了从service解除连接,可以调用unbindService()。
    当你的客户端被销毁时,它将从service解除绑定,但是你应该在与service完成交互或者你的activity pause时解除绑定,这样service就可以在其不用时关闭。

比如说,下面一个片段连接一个客户端到service,该service是之前用继承Binder类方式创建的。所有要做的是将返回的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;
    }
};

客户端可以绑定到service,并将ServiceConnection传递给bindService()。比如:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • bindService()的第一个参数是一个Intent,指明要绑定的service的名字(虽然这个Intent可以是implicit的)
  • 第二个参数是ServiceConnection对象
  • 第三个参数是一个表示flag,来指明绑定的选项,它常常是BIND_AUTO_CREATE来在service不存在时创建。其它的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND或0。

附加注释:

下面是关于绑定到service的重要注释:

  • 你必须总是捕获DeadObjectException,该异常在连接损坏时抛出。这是远程方法抛出的唯一异常
  • 对象是跨进程计算的引用(Objects are reference counted across processes).
  • 在客户端生命周期的匹配的建立(bring-up)和卸载(tear-down)时刻,你应该总是匹配绑定和解绑定,比如说:
    • 如果你只需要在你的activity可见时与service交互,应该在onStart()中绑定,并在onStop()中解除绑定。
    • 如果你需要你的activity即使在后台停止的时候也可以接收响应,你可以在onCreate()时绑定service,然后在onDestroy()中解除绑定。当心,这意味着你的activity在整个生命周期都需要用到service(即使是在后台),所以如果service在其他的进程中,你增加了这个进程的负担,它变得更容易被系统kill。

Note:你不因该在onResume()和onPause()绑定以及解绑定,因为这些回调函数发生在每次生命周期变换,你应该确保这些处理发生在转换的最低限度。同样,如果在你的应用程序中多个activity绑定到同一个service并且两个activity之间有一个转换(transition),在当前activity解除绑定(正在pause),而在下一个activity绑定之前,service或许会被销毁和重新创建。

管理Bound Service的生命周期

当一个service从所有的客户端解除绑定时,Android系统会销毁它(除非它是通过onStartCommand()启动的)。因此,

如果service只是个bound service,你不需要管理它的生命周期。---Android系统会根据其是否绑定到客户端来管理它。

然而,如果你选择实现onStartCommand()回调函数,此时你必须明确的stop service,因为这个service此时被认定是started。在这种情况下,这个service一直运行,直到service通过调用stopSelf()停止自己或者其他的组件调用stopService()来停止它,此时不用考虑是否有组件绑定到它上面。

另外,如果你的service是started并接受绑定,在系统调用你的onUnbind()方法时,如果你想在下一次一个客户绑定到service时接收onRebind()的调用,你可以选择返回true。onRebind()返回void,但是客户端仍在其onServiceConnected()回调中接收到IBinder,下图阐明了这种生命周期的逻辑:

原文地址:https://www.cnblogs.com/blueofsky/p/2308507.html