第三部分:Android 应用程序接口指南---第一节:应用程序组件---第二章2-1.Bound Service

第2-1章 Bound Service

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

本篇文章将介绍如何创建一个bound service,包括如何把其他的应用程序组件绑定到service。

2-1.1 基本知识

bound service是Service类的一个实现,这个类允许其他的应用程序绑定它,并与之交互。为了给service提供绑定,你必须实现onBind()回调方法。这个方法返回一个IBinder对象,它定义了客户端可以与service进行交互的编程接口。客户端可以调用bindService()方法来绑定到service。当它这样做了,它就需要提供一个ServiceConnection的实现,这个实现会监测它与service的连接。这个bindService()方法会立即返回且不带有返回值,但是当Android系统在客户端与service之间创建联接时,它会调用ServiceConnection中的onServiceConnection()方法,来传递一个IBinder,这样客户端就可以用这个IBinder来与service通信。多个客户端可以同时绑定到service。然而,只有当第一个客户端绑定到service时,系统才会调用service的onBind()方法,来检索IBinder对象。这样系统不需要调用onBind()方法,就能把这个对象传递给任何要绑定到service的客户端。当最后一个客户端与service解除时,系统就会销毁service(除非service也是通过startService()方法启动的)。当实现你的bound service时,最重要的部分是定义从onBind()回调方法中返回的接口。下面就介绍几种定义IBinder接口的方法。

小扩展:绑定到一个启动态的service

正如第二章所讲到的,你可以创建一个同时是启动态和绑定态的service。也就是说service可以通过调用允许service无限期地运行的startService()方法来启动它,并且客户端可以通过调用bindService()方法来绑定到service。如果你允许你的service同时是启动态和绑定态,那么当service启动,系统不会在所有的客户端还没有与service解除绑定时,就销毁service。相反,你必须通过调用stopSelf()方法和stopService()方法才能显式地停止它。尽管在通常情况下,你要么是实现onBind()方法,要么是实现onStartCommand()方法,但有时你需要全部实现它们。例如,一个音乐播放器可能会发现允许它的service无限期地运行而且还提供绑定是很有用的。通过这种方式,一个activity可以启动service来播放音乐,即使用户离开应用程序,这个音乐也会继续播放。然后,当用户返回到应用程序时,activity可以绑定到service来重新获得回放的控制权。

2-1.2  创建一个Bound service

当创建一个能提供绑定的service时,你必须提供一个IBinder,这个IBinder会提供客户端用来与service进行交互的编程接口。下面是三种定义接口的方法:

1.继承Binder类:如果你的service是对你的应用程序私有,并与客户端运行在同一个进程(通常是这样),那么你应该通过继承Binder类来创建你的接口,并从onBind()方法中返回一个实例。客户端接收到了Binder,就可以用它来直接访问对Binder实现甚至service有用的公有方法。

当你的service仅仅是你应用程序中的一个后台工人时,这个是首选的方法。但如果你的service被其他应用程序占用或者是你的service跨了不同的进程,那么你就应该放弃这种创建方式。

2.使用Messenger:如果你需要你的service跨进程工作,你就可以用一个Messenger为service创建一个接口。通过这种方式,service可以定义一个Handler,这个Handler可以响应不同类型的Message对象。同时这个Handler也是Messenger的基础,它与客户端共享一个IBinder,允许客户端用Message对象给service发送命令。此外,客户端可以定义自己的Messenger,这样service就可以把消息发回给客户端。

这是执行进程间通信(IPC)的最简单方法,因为Messenger队列中的所有请求都在一个单一的线程里,所以你不必去为你的service设计成线程安全。

3.使用AIDL:AIDL执行的工作就是把对象分解成操作系统可以理解的基本单位,然后操作系统才可以安排这些对象来执行跨进程的IPC操作。前面提到的使用Messenger的方法实际上是基于AIDL的,是它的底层架构。根据上面所说,Messenger在一个单一的线程中,为客户端中的所有请求创建一个队列,这样service每次就只能处理一个请求。然而,如果你想要你的service可以同时处理多个请求,那么你可以直接使用AIDL。这样的话,你的service就可以同时处理多线程并被设计成线程安全。

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

注意:大多数应用程序不应该使用AIDL来创建一个bound service,因为它需要多线程能力,并且它会导致更复杂的实现。

1. 继承Binder类

如果你的service仅用于本地应用程序,并且不需要跨进程工作,那么你可以实现你自己的Binder类,这个类会为你的客户端提供直接访问service公有方法的机会。

注意:只有当客户端与service在同一个应用程序和进程时,这个方法才可行。例如,对于一个音乐应用程序来说,它需要把一个activty绑定到在后台播放音乐的service。

这里介绍设置它的步骤:

1.在你的service中,创建一个Binder实例,下面几种就是实例:

(1)包含客户端可以调用的公有方法

(2)返回当前service的实例,它有客户端可以调用的公有方法

(3)或者,返回一个另一个类的实例,这个类是由service持有的,它有客户端可以调用的公有方法

2.从onBind()回调方法中返回这个Binder实例

3.客户端接收onServiceConnected()回调方法中的Binder,并用提供的方法执行对bound service的调用。

注意:service和客户端必须在同一个应用程序的原因是客户端可以对返回的对象进行类型转换并且适当的调用它的APIs。另一个原因是这个方法不能进行任何跨进程的编组操作。

这有一个例子是service通过实现一个Binder,来为客户端提供访问service中方法的机会,如代码清单2-1-1所示:

public class LocalService extends Service {
    // 创建一个Binder实例
    private final IBinder mBinder = new LocalBinder();
    // 随机数
    private final Random mGenerator = new Random();
    /**
     * 因为我们知道这个service一直在同一个客户端的进程中运行,不需要处理IPC。所以我们选择Binder
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            //返回的这个LocalService实例可以让客户端调用里面的共有方法
            return LocalService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /** 这个是客户端的方法*/
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}

 

代码清单2-1-1

    LocalBinder会为客户端提供一个getService()方法,来取回当前LocalService的实例。它允许客户端调用service中的公有方法。例如,客户端可以调用service中的getRandomNumber()方法。

下面是一个绑定到LocalService的activity,当点击一个按钮时,它会调用getRandomNumber()方法,如代码清单2-1-2所示:

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();
        // 绑定LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
 
    @Override
    protected void onStop() {
        super.onStop();
        // 解除绑定
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
 
    /** 当一个按钮被点击时,这里我们是在layout文件中指定了android:onClick 的属性为”onButtonClick”*/
    public void onButtonClick(View v) {
        if (mBound) {
            // 从LocalService调用一个方法。当然如果是一个耗时的操作我们可以在一个单一的线程中执行,避免减慢
          // activity的性能
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }
 
    /** 通过回调方法来绑定客户端中的mService */
    private ServiceConnection mConnection = new ServiceConnection() {
 
        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // 这里强制转换了一下
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }
 
        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

 

代码清单2-1-2

   上面的例子向我们展示了客户端怎样使用一个ServiceConnection接口中的onServiceConnected()回调方法来绑定service。

2. 使用一个Messenger

如果你的service要与远程进程进行通信,那么你可以使用一个Messenger,来为你的service提供一个接口。使用Messenger的方法能让你在没有AIDL的情况下执行进程间通信(IPC)。

下面是使用一个Messenger的摘要:

1.service会实现一个Handler,它接收来自客户端的每个回调。

2.这个Handler被用来创建一个Messenger对象(它是Handler的一个引用)。

3.Messenger创建一个IBinder,这个IBinder是service从onBind()中返回给客户端的。

4.客户端用这个IBinder来实例化Messenger(它是service的Handler引用),这样用户就可以用它来给service发送Message。

5.service在它的Handler中接收每个Message-具体是在handleMessage()方法中。

这样,在service中就没有供客户端调用的“方法”。相反,客户端会传递“消息”(Message对象),service可以在Handler中接收它。下面是一个使用Messenger接口的简单例子,如代码清单2-1-3所示:

public class MessengerService extends Service {
    /**service显示消息的命令*/
    static final int MSG_SAY_HELLO = 1;
 
    /**
     * 处理来自客户端的消息
     */
    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);
            }
        }
    }
 
    /**
     * 在Messenger中传入IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());
 
    /**
     * 当service正在绑定时,我们返回一个messenger接口中的getBinder()方法来发送消息
     */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    }
}

 

代码清单2-1-3

    注意,在Handler对象中的handleMessage()方法,是service用来接收传入的Message的地方,并且它会根据what成员变量来决定要做的事情。

    客户端所要做的是创建一个Messenger,它基于由service返回的IBinder对象,并用send()方法发送一个消息。例如,这有一个简单的activity,它与service绑定并给service发送MSG SAY HELLO消息,如代码清单2-1-4所示:

public class ActivityMessenger extends Activity {
    /** 用于和service通信的Messenger */
    Messenger mService = null;
 
    /** 一个标志位,表示我们是否在service中调用了绑定的操作*/
    boolean mBound;
 
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // 连接已建立时,通过Messenger与service交互
            mService = new Messenger(service);
            mBound = true;
        }
 
        public void onServiceDisconnected(ComponentName className) {
            mService = null;
            mBound = false;
        }
    };
 
    public void sayHello(View v) {
        if (!mBound) return;
        // 创建一个发送消息的service,使用“what”值
        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();
        //绑定service
        bindService(new Intent(this, MessengerService.class), mConnection,
            Context.BIND_AUTO_CREATE);
    }
 
    @Override
    protected void onStop() {
        super.onStop();
        // 接触绑定service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}

 

代码清单2-1-4

注意,这个例子没有展示出service是如何响应客户端的。如果你想要service响应客户端,那么你还需要在客户端里创建一个Messenger。然后当客户端接收到onServiceConnected()方法的回调时,它通过send()方法的replyTo参数发送一个消息到service,这个消息包含客户端的Messenger对象。

与AIDL相比较:

当你需要执行IPC操作时,你可以为你的接口使用一个Messenger方法,这比用AIDL方法实现它来的更简单,因为Messenger会队列化所有对service的调用,而一个AIDL接口给service发送同步请求,service必须在稍后才能处理它。对于大多数应用程序来说,service不需要执行多线程,所以使用一个Messenger就可以让它只处理一个调用。如果你的service是多线程的,那么你应该使用AIDL来定义你的接口。

 

2-1.3 绑定到一个service

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

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

注意:只有activity、services和content provider可以绑定一个service,broadcast receiver是不能与service绑定的。

所以,为了使你的客户端绑定到service,你必须做下面的工作:

1.实现ServiceConnection。在方法实现中,必须重新写入下面两种回调方法:

(1)onServiceConnected():系统调用这个方法来传递从onBind()方法中返回的IBinder。

(2)onServiceDisconnected():当与service的连接异常丢失时,比如service已经崩溃或者已经被kill掉,Android系统会调用这个方法,当客户端解除绑定时,系统不会调用这个方法。

2.调用bindService()方法,传递ServiceConnection的实现。

3.当系统调用你的onServiceConnected()回调方法时,你可以使用由接口定义的方法,对service执行调用。

4.要解除与service的连接,就调用unbindService()方法:当你的客户端被销毁时,它会与service解除绑定,但是当与service的交互完成或当你的activity已经暂停时,你总是要与service解除绑定,以便当service不再使用时可以关闭它。

   例如,下面的代码表示的是通过继承Binder类,把客户端连接到service,所以它所需要做的是为LocalService类转换返回的IBinder,并请求一个LocalService的实例,如代码清单2-1-5所示:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // 连接已建立时被调用
    public void onServiceConnected(ComponentName className, IBinder service) {
        // 因为我们有一个明确的绑定,所以我们能强制转换Ibinder为LocalBinder
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }
 
    // 连接异常时被调用
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};

 

代码清单2-1-5

   客户端可以通过把这个ServiceConnection传递给bindService(),来绑定到service,如代码清单2-1-6所示:

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

代码清单2-1-6

 

1.bindService()方法的第一个参数是一个Intent,这个Intent能显式地给要绑定的service命名(尽管这个intent也可以是隐式的)。

2.第二个参数是ServiceConnection对象。

3.第三个参数是一个标记(flag),它显示出要绑定的选项。通常情况下,它应该是BIND_AUTO_CREATE,因为,如果service不存在时,它还能指示可以创建一个service。其他可能的值请参阅API。

附加注意事项

下面是关于绑定到service的重要注意事项:

1.你应该总是要捕获到DeadjectException的异常,这个异常会在连接断开时抛出。这是远程方法抛出的唯一一个异常。

2.这些对象是被计算为跨进程的引用。

3.在匹配客户端生命周期的组合和分拆时,你通常应该将绑定和解除绑定配对。例如:

◆如果你只需要在activity变得可见时,与service进行交互,那么你应该在onStart()实现期间绑定service,而在onStop()实现期间与service解除绑定。

◆如果你希望你的activity,即使它在后台已经停止也能接收到响应,那么你可以在onCreate()期间绑定并在onDestroy()期间解除绑定。注意,这意味着你的activity在整个运行时间中都要使用service(即使是在后台),所以,如果service是在另一个进程里,然后你又增加这个进程的负荷,那么service就极有可能被系统kill掉。

注意:你不应该在onResume()和onPause()方法实现期间绑定service和解除与service的绑定,因为这些方法都会出现在生命周期的每个过渡期间,而你应该保持发生在这些过渡期间的进程为最小负荷。同样的,如果在应用程序中的多个activity同时与service绑定,并且两个activity之间存在一个过渡,那么在当前activity与service解除绑定之后(在暂停期间)、下一个activity绑定到service之前(在恢复期间),service可能会被销毁并重新创建。

2-1.4 管理一个bound service的生命周期

当service同所有的客户端解除绑定时,Android系统就会销毁它(除非它也是由onStartCommand()方法启动的)。因此,如果你的service只是一个bound service,那么你不需要管理它的生命周期—Android系统会根据是否绑定到任意客户端来为你管理它。然而,如果你选择实现onStartCommand()方法,那么你必须显式地停止service,因为它目前被认定是处于启动状态。在这种情况下,不管service有没有绑定到任何的客户端,它都会一直运行,直到它用stopSelf()方法自行停止或者其他的组件调用stopService来停止它,它才不会运行。此外,如果你的service已经启动并接受绑定,那么当系统调用onUnbind()方法时,如果你想在下个客户端绑定到service时接收到onRebind()的调用(而不是接收到onBind()的调用),那么你可以返回true。虽然onRebind()方法会返回void,但是客户端仍然可以在它的onServiceConnected()回调中接收到IBinder。下面的图2-1-1是这类service生命周期的逻辑图:

 

 

图2-1-1  已启动并允许绑定的service的生命周期

 本文来自jy02432443,QQ78117253。是本人辛辛苦苦一个个字码出来的,转载请保留出处,并保留追究法律责任的权利

posted @ 2013-11-19 21:18  jy02432443  阅读(1039)  评论(0编辑  收藏  举报