当前位置: 代码迷 >> Android >> 【Android API指南】App组件(七) - Services(1) - Bound Services
  详细解决方案

【Android API指南】App组件(七) - Services(1) - Bound Services

热度:59   发布时间:2016-05-01 14:48:01.0
【Android API指南】App组件(7) - Services(1) - Bound Services
一个bound service是客户端-服务器接口中的服务器。它允许组件去绑定service,发送请求,接收响应,甚至是执行进程间的通信。一个bound service通常在服务其他组件时存在,而不是一直运行在后台。

基础

一个bound service是Service类的实现,为了绑定一个service,你需要实现onBind()回调函数。这个函数返回一个IBinder对象,定义了客户端和服务器间交互的程序接口。

客户端调用bindService()绑定一个service。这样做时,你需要提供一个ServiceConnection的实现,它监控与服务器的链接。bindService()不会返回值,不过当系统创建客户端和服务器链接时,会调用ServiceConnection中的onServiceConnected()方法来传递IBinder。

多个客户端可以同时链接到service。不过,系统只会在第一个客户端绑定时调用onBind()方法取得IBinder。其他客户端是传递的相同的IBinder,而不重新调用onBind()。

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

创建一个Bound Service

要能提供绑定,你就需要提供一个IBinder来和service交互,下面有三种定义通信接口的方法:

扩展Binder类
如果你的service是私有的,并且和客户端运行在同一个进程中,那么你可以扩展Binder类来实现自己的接口,onBind()返回一个这个类的实例。客户端接收到这个Binder,然后直接调用它的公共函数。

如果service仅仅为你的程序工作,那么这个是一个好的选择。

使用一个Messenger
如果你需要接口能在不同的进程中工作,你需要使用Messenger来创建接口。service定义一个Handler来响应不同的Message对象,Handler是Messenger的基础,可以分享一个IBinder给客户端,运行客户端使用Message对象发送命令到服务器。另外,客户端可以定义自己的Messenger来让service发送信息回来。

这是进程间通信最简单的方法,因为Messenger使用单线程管理请求队列,所以你不需要设计你的service为线程安全的。

使用AIDL
AIDL(Android接口定义语言)把对象分解成原始部分,让系统能够理解并安排他们进行进程间通信。前面说的使用Messenger的方法就是一个简单的AIDL实现,使用Messenger的话,service一次只会接收到一个请求,如果你想同时处理多个请求,那么你可以直接使用AIDL,这种情况下,你的service必须支持多线程,并且要实现线程安全,更多AIDL使用看下一个教程。

扩展Binder类
如果service只是本地使用,不需要跨进程工作,那么使用自定义的Binder类就可以了。

提示:这种方式只能在客户端和服务器在一个程序和进程中才能使用,例如,一个音乐程序绑定一个activity到自己的service然后在后台播放音乐。

那么是实现的步骤:
  1. 在service中创建一个Binder的实例:
    a. 包含客户端可以调用的公共函数。
    b. 返回当前Service实例,实例包含客户端可以调用的公共函数。
    c. 或者返回service内部类的实例,包含可调用的公共函数。
  2. 从onBind()中返回Binder的实例。
  3. 在客户端的OnServiceConnected()取得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的实例。然后用这个实例就可以调用它的getRandomNumber()方法。

下面的客户端activity的实现:
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
如果要实现进程间通信,就需要使用Messenger了。

下面是使用Messenger的概述:
  • service实现一个Handler接收客户端的回调。
  • Handler被用来创建一个Messenger对象。
  • Messenger创建一个IBinder,在onBind()方法中返回给客户端。
  • 客户端使用IBinder初始化Messenger,然后使用它发送Message到服务器。
  • 服务器接收到Message,在Handler中处理这些Message。
这种方法中客户端不需要调用service的方法,而是让Handler接收信息,然后处理。

下面是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()方法接收客户端传送过来的Message,然后基于what参数做比较,从而执行对应的工作。

下面是简单的客户端activity的实现:
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;        }    }}
上面的例子没有实现服务器返回信息给客户端的功能,如果你想收到服务器的响应的话,你需要在客户端创建一耳光Messenger,然后在send()方法中使用replyTo参数传递Messenger到服务器。

绑定到一个Service

客户端可以使用bindService()来绑定到一个service。系统就会调用onBind()方法来响应bindService()的调用。然后返回一个IBinder给客户端。

绑定时异步的,bindService()会立即返回,当时并没有返回IBinder给客户端,要接收到IBinder,客户端必须创建一个ServiceConnection的实例,然后传递给bindService()方法。ServiceConnection包含接收IBinder的回调函数。

提示:只有activity,service,content provider可以绑定一个service,你不能使用broadcast receiver绑定一个service。

那么,绑定一个service你需要做:
  1. 实现ServiceConnection。
    包括重写里面的两个方法:
    onServiceConnected()
            系统调用它来取得onBind()返回的IBinder。
    onServiceDisconnected()
            在service被意外丢失时调用,比如service自己崩溃了,或者被系统杀掉了。这个方法在客户端解除绑定时不会被调用。
  2. 调用bindService()方法传递ServiceConnection的实现。
  3. 当系统调用onServiceConnected方法时,你可以开始使用服务器的方法。
  4. 使用unbindService()来断开service连接。
    当客户端被销毁后,会从服务器解除绑定,不过你应该交互完成或者activity暂停时解除绑定,这样能很好的关闭不需要的service。
例如,下面是绑定的代码片段:
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);

补充提示
下面是一些重要的提示:
  • 当连接被破坏时需要抛出一个DeadObjectException异常。这是远程函数唯一会抛出的异常。
  • 对象是夸进程引用的。
  • 绑定和解除绑定应该是成对出现的。例如:
    a. 在activity中在onStart()绑定,在onStop解除绑定。
    b. 如果你系统activity在被停止后还能接收响应,你可以在onCreate()中绑定,在onDestroy()中解除绑定。
    提示:你不应该再onResume()中绑定,在onPause()中解除绑定,因为这两个方法发生在生命周期的过渡阶段,时间比较短,很难保证进程的启动。同样的,如果有多个activity绑定到相同的service,那么在第二个activity绑定前,当前的activity解除绑定可能会导致service被销毁和重建。
管理Bound Service的生命周期

如果service仅仅是一个bound service,那么系统自动管理它的生命周期。不过,如果你选择实现onStartCommand()回调方法,你必须显式的停止service,因为这个时候service是被启动的,它一直运行,直到你来停止它。

另外,如果你的service是被启动的,然后接受了绑定,那么当系统调用你的onUnbind()方法时,你可以选择返回true的话,就是说下一次客户端绑定这个service时,是调用onRebind()来替换调用onBind()。onRebind()返回空,但是客户端仍然在onServiceConnected()中接受IBinder。下图展示了这个逻辑关系:


  相关解决方案