4.2.0.1 Android四大组件之- 绑定Service

绑定服务:

绑定服务是客户端-服务器接口中的服务器。绑定服务可让组件(例如 Activity)绑定到服务、发送请求、接收响应,甚至执行进程间通信 (IPC)。 绑定服务通常只在为其他应用组件服务时处于活动状态,不会无限期在后台运行。

本文向您介绍如何创建绑定服务,包括如何绑定到来自其他应用组件的服务。 不过,您还应参阅服务文档,了解有关一般服务的更多信息,例如:如何利用服务传送通知、如何将服务设置为在前台运行等等。

基础知识


绑定服务是 Service类的实现,可让其他应用与其绑定和交互。要提供服务绑定,您必须实现 onBind()回调方法。该方法返回的 IBinder对象定义了客户端用来与服务进行交互的编程接口。

客户端可通过调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))绑定到服务。调用时,它必须提供 ServiceConnection的实现,后者会监控与服务的连接。[bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))方法会立即无值返回,但当 Android 系统创建客户端与服务之间的连接时,会调用 ServiceConnection上的 [onServiceConnected()](http://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder)),向客户端传递用来与服务通信的IBinder

多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind()方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder传递至任何其他绑定的客户端。

当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非startService()也启动了该服务)。

当您实现绑定服务时,最重要的环节是定义您的 onBind()回调方法返回的接口。您可以通过几种不同的方法定义服务的IBinder接口,下文对这些方法逐一做了阐述。

绑定到已启动服务

正如服务文档中所述,您可以创建同时具有已启动和绑定两种状态的服务。 也就是说,可通过调用startService()启动该服务,让服务无限期运行;此外,还可通过调用[bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))使客户端绑定到服务。

如果您确实允许服务同时具有已启动和绑定状态,则服务启动后,系统“绝对不会”在所有客户端都取消绑定时销毁服务。 为此,您必须通过调用[stopSelf()](http://developer.android.com/reference/android/app/Service.html#stopSelf()stopService()显式停止服务。

尽管您通常应该实现 onBind()[onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int)),但有时需要同时实现这两者。例如,音乐播放器可能发现让其服务无限期运行并同时提供绑定很有用处。 这样一来,Activity 便可启动服务进行音乐播放,即使用户离开应用,音乐播放也不会停止。 然后,当用户返回应用时,Activity 可绑定到服务,重新获得回放控制权。

请务必阅读管理绑定服务的生命周期部分,详细了解有关添加绑定已启动服务时该服务的生命周期信息。

创建绑定服务


创建提供绑定的服务时,您必须提供 IBinder,用以提供客户端用来与服务进行交互的编程接口。

您可以通过三种方法定义接口:

  • 扩展 Binder 类
    如果服务是供您的自有应用专用,并且在与客户端相同的进程中运行(常见情况),则应通过扩展 Binder类并从onBind()返回它的一个实例来创建接口。客户端收到Binder后,可利用它直接访问 Binder实现中乃至Service中可用的公共方法。
    如果服务只是您的自有应用的后台工作线程,则优先采用这种方法。 不以这种方式创建接口的唯一原因是,您的服务被其他应用或不同的进程占用。

  • 使用 Messenger
    如需让接口跨不同的进程工作,则可使用 Messenger为服务创建接口。服务可以这种方式定义对应于不同类型 Message对象的 Handler。此 HandlerMessenger的基础,后者随后可与客户端分享一个 IBinder,从而让客户端能利用 Message对象向服务发送命令。此外,客户端还可定义自有Messenger,以便服务回传消息。
    这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger会在单一线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。

  • 使用 AIDL
    AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 IPC。之前采用 Messenger的方法实际上是以 AIDL 作为其底层结构。如上所述,Messenger会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。
    如需直接使用 AIDL,您必须创建一个定义编程接口的 .aidl文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,您随后可在服务内对其进行扩展。

注:大多数应用“都不会”****使用 AIDL 来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL 并不适合大多数应用,本文也不会阐述如何将其用于您的服务。如果您确定自己需要直接使用 AIDL,请参阅 AIDL 文档。

扩展 Binder 类

如果您的服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder类,让您的客户端通过该类直接访问服务中的公共方法。

注:此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。 例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效。

以下是具体的设置方法:

  1. 在您的服务中,创建一个可满足下列任一要求的 Binder实例:
  • 包含客户端可调用的公共方法
  • 返回当前 Service 实例,其中包含客户端可调用的公共方法
  • 或返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法
  1. onBind()回调方法返回此 Binder实例。
  2. 在客户端中,从 [onServiceConnected()](http://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder))回调方法接收 Binder,并使用提供的方法调用绑定服务。

注:之所以要求服务和客户端必须在同一应用内,是为了便于客户端转换返回的对象和正确调用其 API。服务和客户端还必须在同一进程内,因为此方法不执行任何跨进程编组。

例如,以下这个服务可让客户端通过 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的当前实例。这样,客户端便可调用服务中的公共方法。 例如,客户端可调用服务中的 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;
        }
    };
}

上例说明了客户端如何使用 ServiceConnection的实现和 [onServiceConnected()](http://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder))回调绑定到服务。下文更详细介绍了绑定到服务的过程。

注:上例并未显式取消与服务的绑定,但所有客户端都应在适当的时间(例如当 Activity 暂停时)取消绑定。

如需查看更多示例代码,请参阅 ApiDemos 中的 LocalService.java
类和 LocalServiceActivities.java
类。

使用 Messenger

如需让服务与远程进程通信,则可使用 Messenger为您的服务提供接口。利用此方法,您无需使用 AIDL 便可执行进程间通信 (IPC)。

当您需要执行 IPC 时,为您的接口使用Messenger要比使用 AIDL 实现它更加简单,因为 Messenger会将所有服务调用排入队列,而纯粹的 AIDL 接口会同时向服务发送多个请求,服务随后必须应对多线程处理。
对于大多数应用,服务不需要执行多线程处理,因此使用 Messenger可让服务一次处理一个调用。如果您的服务必须执行多线程处理,则应使用 AIDL来定义接口。

以下是 Messenger 的使用方法摘要:

  • 服务实现一个 Handler,由其接收来自客户端的每个调用的回调
  • Handler 用于创建 Messenger 对象(对 Handler 的引用)
  • Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
  • 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务
  • 服务在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message

这样,客户端并没有调用服务的“方法”。而客户端传递的“消息”(Message对象)是服务在其 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();
    }
}

请注意,服务就是在 HandlerhandleMessage()方法中接收传入的 Message,并根据 what成员决定下一步操作。

客户端只需根据服务返回的 IBinder创建一个 Messenger,然后利用 send()发送一条消息。例如,以下就是一个绑定到服务并向服务传递 MSG_SAY_HELLO消息的简单 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。然后,当客户端收到 [onServiceConnected()](http://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder))回调时,会向服务发送一条 Message,并在其send()方法的 replyTo参数中包含客户端的 Messenger

如需查看如何提供双向消息传递的示例,请参阅 MessengerService.java
(服务)和MessengerServiceActivities.java
(客户端)示例。

绑定到服务


应用组件(客户端)可通过调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))绑定到服务。Android 系统随后调用服务的 onBind()方法,该方法返回用于与服务交互的 IBinder

绑定是异步的。[bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))会立即返回,“绝对不会”**使 IBinder返回客户端。要接收 IBinder,客户端必须创建一个 ServiceConnection实例,并将其传递给 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))。ServiceConnection
包括一个回调方法,系统通过调用它来传递 IBinder

注:只有 Activity、服务和内容提供程序可以绑定到服务—您无法从广播接收器绑定到服务。

因此,要想从客户端绑定到服务,必须:

  1. 实现 ServiceConnection。您的实现必须重写两个回调方法:
  1. 调用 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))以传递 ServiceConnection实现。

  2. 当系统调用您的 [onServiceConnected()](http://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder))回调方法时,您可以使用接口定义的方法开始调用服务。

  3. 要断开与服务的连接,请调用 unbindService()
    当您的客户端被销毁时,它将取消与服务的绑定,但您应该始终在完成与服务的交互时或您的 Activity 暂停时取消绑定,以便服务能够在未被占用时关闭。 (下文更详细地阐述了绑定和取消绑定的适当时机。)

例如,以下代码段通过扩展 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;
    }
};

客户端可通过将此 ServiceConnection传递至 [bindService()](http://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))绑定到服务。例如:

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

附加说明

以下是一些有关绑定到服务的重要说明:

  • 您应该始终捕获 DeadObjectException
    异常,它们是在连接中断时引发的。这是远程方法引发的唯一异常
  • 对象是跨进程计数的引用
  • 您通常应该在客户端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 时刻期间配对绑定和取消绑定。 例如:
  • 如果您只需要在 Activity 可见时与服务交互,则应在 onStart()期间绑定,在 onStop()期间取消绑定。
  • 如果您希望 Activity 在后台停止运行状态下仍可接收响应,则可在 onCreate()期间绑定,在onDestroy()期间取消绑定。请注意,这意味着您的 Activity 在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当您提高该进程的权重时,系统终止该进程的可能性会增加。

注:通常情况下,切勿在 Activity 的 onResume()onPause()期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,您应该使发生在这些转换期间的处理保持在最低水平。此外,如果您的应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务。 (Activity文档中介绍了这种有关 Activity 如何协调其生命周期的 Activity 转换。)

如需查看更多显示如何绑定到服务的示例代码,请参阅 ApiDemos 中的 RemoteService.java
类。

管理绑定服务的生命周期


当服务与所有客户端之间的绑定全部取消时,Android 系统便会销毁服务(除非还使用 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))启动了该服务)。因此,如果您的服务是纯粹的绑定服务,则无需对其生命周期进行管理—Android 系统会根据它是否绑定到任何客户端代您管理。

不过,如果您选择实现 [onStartCommand()](http://developer.android.com/reference/android/app/Service.html#onStartCommand(android.content.Intent, int, int))回调方法,则您必须显式停止服务,因为系统现在已将服务视为已启动。在此情况下,服务将一直运行到其通过 stopSelf()自行停止,或其他组件调用 stopService()为止,无论其是否绑定到任何客户端。

此外,如果您的服务已启动并接受绑定,则当系统调用您的 onUnbind()方法时,如果您想在客户端下一次绑定到服务时接收 onRebind()调用(而不是接收 onBind()调用),则可选择返回 true。onRebind()返回空值,但客户端仍在其 [onServiceConnected()](http://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder))
回调中接收 IBinder。下文图 1 说明了这种生命周期的逻辑。

**图 1. **允许绑定的已启动服务的生命周期

如需了解有关已启动服务生命周期的详细信息,请参阅服务文档。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容