Android四大组件之Service第二章:Bound Service

这一章介绍bound service(我也不知道怎么翻译比较好。。)。建议先看前一章对服务有个大概的了解了再看bound service。
Android四大组件之Service第一章:service及其生命周期

bound service是一种采用了client-server架构(CS架构)的接口,它使得组件同一个服务进行绑定后的发送请求,接收结果,IPC通信成为可能。本文将介绍如何创建一个bound service并与之绑定。

基础知识

在service这边,为了实现绑定,必须重写onBind()方法,该方法返回一个IBinder对象,这个对象决定了client与server之间的通信方式。

在client这边,必须要调用bindService()与服务绑定,同时要提供一个ServiceConnection对象,这个对象会监控client与server之间的绑定情况。当二者的绑定关系建立的时候,就会调用ServiceConnection的onServiceConnected()方法并传入一个IBinder对象,这个对象就是service那边的onBind()方法执行完之后返回的IBinder对象。可以说ServiceConnection是二者之前建立关系的桥梁。

一个service可以同时绑定多个client,但是其onBind()方法只会在首次绑定的时候调用,之后绑定更多client的时候不会再调用这个方法,但是之后绑定的client依然会收到同样的IBinder对象。(作者注:可见onBind()方法返回的IBinder对象并不是直接返回给client,而是由系统接管,再派发给client。)

当最后一个client解绑的时候,系统将会销毁这个服务,除非这个服务还是一个started service。

对于一个bound service来讲,最重要的是如何与client通信,即返回一个什么样的IBinder。下面介绍三种不同的IBinder。

创建一个bound service

创建一个bound service的核心在于要能够提供一个client能够和该service进行交互的IBinder对象(准确来说是接口,因为IBinder是接口,不能生成对象)。有三种方式来提供这种接口:

1. 继承Binder类

如果一个服务只为自身所在的应用程序服务,并且在同一个进程中跑,那么继承Binder类,并在onBind()方法中返回该类对象的方式是最合适的。client将会使用这个Binder对象来调用Binder或Service中的public方法。

如果一个服务可能被其他程序所用或者需要运行在不同的进程中,那么就无法使用该方式,否则推荐使用该方式来创建一个bound service。

2. 使用Messenger

如果你需要跨进程通信,那可以使用Messenger通信的方式来提供这个操作接口。在这种方式下,Service内部有一个Handler对象用来处理各种不同的Message对象,根据这个Handler对象可以创建一个Messenger。这里很难说清楚,具体看下面的代码示例吧。

如果要进行IPC的话,使用Messenger是最简单的方式了,因为Messenger会将所有的请求列队,依次处理。因此不需要担心线程安全问题。

3. 使用AIDL

上面的Messenger方式实际上是基于AIDL。正如上面提到的那样,Messenger会将所有的请求列队,依次处理。如果你一定要你的service能够同时处理多个request,那就直接用AIDL吧。注意线程安全问题哦。

大多数应用程序不应该直接使用AIDL来创建bound service,因为这会让逻辑变得很复杂。本章节不讨论直接使用AIDL的方式。

继承Binder类

如上所述,如果一个服务是一个本地服务,并且不需要跨进程,那么可以使用这种方式向client提供一个Binder对象使得其能够直接操作service中的public方法。

使用步骤
  1. 在service的内部生成一个Binder对象(成员变量),该Binder对象需要符合以下几个特征之一:
    • 该Binder对象本身包含一些public方法
    • 该Binder对象能够返回其所在的Service对象,并且该Service对象中要包含public方法
    • 该Binder对象能够返回Service中的另外一些包含public方法的对象
  2. 在onBind()方法中返回该Binder对象
  3. 在client中,从onServiceConnected()方法中接收到这个Binder对象,进而通过其调用一些方法。

service和client必须在同一个应用程序中才行,这样client才能对收到的Binder对象进行正确的cast并调用一些api。同时service和client也必须在同一个进程中,因为该技术并不支持进程间的通信。

总结一下继承Binder类的方式就是:向client提供引用,然后client使用该引用直接调用对象的public方法。

以下是代码示例:

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

上面的代码中有个关键方法:getService()。该方法返回了一个Service对象的引用,然后client直接使用这个引用来操作对象的public方法。下面来看看Activity(client)中的代码:

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;
        }
    };
}

可以看到在client的onStart()方法中进行了对服务的绑定操作,传入了一个ServiceConnection对象。在绑定关系建立的时候,会在ServiceConnection对象的onServiceConnected()方法中收到Binder对象,然后通过该Binder对象的getService()方法获得Service的引用,然后直接调用service的public方法。

在该示例中,在onStop()方法中对Service进行了unBind,client应该懂得在合适的时候对绑定的服务进行解绑,具体参加下面的附加部分。

使用Messenger

如果你的服务需要跨进程通信,那么可以使用Messenger来提供通信的接口。

使用Messenger的步骤如下:

  1. 在Service中创建一个Handler对象,用来处理所有的请求。该Handler会持有一个IMessenger引用。
  2. 使用这个Handler创建一个Messenger对象。该Messenger也会持有同一个IMessenger的引用。
  3. 使用Messenger创建一个IBinder对象,并在onBind()方法中返回。
  4. client收到Binder对象后使用它创建一个Messenger对象,由于这个Messenger是使用Binder对象创建的,而Binder对象是Service中的Messenger创建的,而Service中的Messenger是使用Service中的Handler创建的,所以client中的Messenger和Service中的Handler就这样建立了联系。
  5. 在client中使用Messenger对象发送消息。Service中的Handler就会收到消息。准确来讲是Handler的handleMessage()方法会收到消息。

通过这种方式,client不会直接调用service的任何方法,而是通过message来传递信息。下面是例子:

Server端:

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端:

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;
        }
    }
}

这里的关键点在onServiceConnected()方法中使用传进来的IBinder对象new了一个Messenger,由于这个IBinder是和Service中的Handler有关系的,因此将client中的Messenger与service中的Handler建立了联系。然后就可以在client中通过Messenger发送消息了。

感觉Service端的Messenger只是起了个构建IBinder的作用,它自己本身并没有传到client中去。Client端是利用这个IBinder对象又new了一个Messenger。

上面的代码例子只能建立一个单向的通信通道,即service无法向client返回结果。如果你需要service对client的请求返回一个结果的话,你需要在client中创建一个Messenger(注意:这个Messenger不是上面那个Messenger,下面有代码示例),然后发送message的时候将这个Messenger对象封装到message中去(Message有一个字段为Messeneger类型,名为replyTo,即发送的消息中包含处理完这条消息要向谁返回结果的信息)。Service收到消息后,需要返回结果的话,取出这个replyTo,使用它再向client发一条消息。方式和client向serveice发送消息是一样的。说再多还不如 show me the code!直接在上面的代码基础上进行更改!

client端:

public class ActivityMessenger extends Activity {
    /** Messenger for communicating with the service. */
    Messenger mService = null;

    int GIVE_ME_A_RESPONSE = 100;
    int RESPONSE_FROM_SERVICE = 110;
    Messenger activityMessenger = new Messenger(new ActivityHandler());

    /** Flag indicating whether we have called bind on the service. */
    boolean mBound;

    /** this handler is created by the activity to handle response from service. */
    class ActivityHandler extends Handler{
        @Override
        public void handlerMessage(Message msg){
            switch(msg.what){
              case RESPONSE_FROM_SERVICE:
                Toast.makeText(ActivityMessenger.this, "response from service received",
                              Toast.LENGTH_SHORT).show();
                break:
              default:
                super.handlerMessage(msg);
            }
        }
    }

    /**
     * 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);//this Messenger is based on the Handler of 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();
        }
    }

    public void sentMsgToServiceWithResponse(){
        if (!mBound) return;
        try {
            Message msg = Message.obtain(null, GIVE_ME_A_RESPONSE)
            msg.replyTo = activityMessenger;
            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端:

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();
              case ActivityMessenger.GIVE_ME_A_RESPONSE:
                    Message msg = Message.obtain(null, ActivityMessenger.RESPONSE_FROM_SERVICE);
                    msg.replyTo.send(msg);//retrieve the replyTo messenger to send back a response message
                    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();
    }
}

仔细观察上面的代码会发现,service返回结果给client的通信方式和client发送消息给service的通信方式完全相同!

与服务绑定

应用程序组件(client)通过调用bindService()方法与服务绑定。系统随即调用服务的onBind()方法,返回一个IBinder给client用来通信。

绑定的过程是异步的,bindService()方法会立即返回,而不会等IBinder对象传给client。要接收这个IBinder对象,必须在bindService()的时候传入一个ServiceConnection对象。系统会回调它的onServiceConnected()方法并传入IBinder对象。

注意:只有Activity,Service,ContentProvider能够和Service绑定,广播接收器不能。

如上面的代码中所示,绑定一个服务需要如下几个过程:

  1. 创建一个ServiceConnection对象,该对象必须实现下面两个方法:

    • onServiceConnected()

      系统通过回调该方法来传入IBinder对象

    • onServiceDisconnected()

      当client与server之间的链接意外断开的时候,系统会回调该方法,比如当service崩溃或者被系统杀掉的时候。正常的解绑操作不会回调该方法。

  2. 调用bindService(),将上一步的ServiceConnection对象传进去。

  3. 当绑定关系建立后,在onServiceConnected()中使用IBinder接口来与服务通信。

  4. 要与服务解绑,调用unbindService()

    如果不解绑或者还没来得及解绑,这个client就被app销毁了,那么将自动解绑。最好是在服务完成工作之后立即解绑,这样可以关闭空闲服务。

关于bindService()的三个参数:

  • Intent:上一篇关于服务的文章中也说过了,启动服务不要使用隐式Intent。
  • ServiceConnection:前面讲了
  • int:这个值决定了绑定后的服务要进行什么操作,有好多种值,具体查阅API文档。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值