您的位置:首页 > 其它

BoundService的基本使用

2015-08-03 13:48 579 查看
导读:

bound服务允许组件(比如activity)对其进行绑定、发送请求、接收响应、甚至进行进程间通信(IPC)。bound服务一般只在为其它应用程序组件服务期间才是存活的,而不会一直在后台保持运行。

bound服务是Service类的一种实现,它允许其它应用程序与其绑定并交互。为了让服务支持绑定,你必须实现onBind()回调方法。这个方法返回一个IBinder对象,此对象定义了客户端与服务进行交互时所需的编程接口。

客户端可以通过调用bindService()方法来绑定服务。在调用时,必须提供一个ServiceConnection的实现码,用于监控与服务的联接。bindService()将会立即返回,没有返回值。但是Android系统在创建客户端与服务之间的联接时,会调用ServiceConnection中的onServiceConnected()方法,传递一个IBinder,客户端将用它与服务进行通信。

多个客户端可以同时联接到一个服务上。不过,只有在第一个客户端绑定时,系统才会调用服务的onBind()方法来获取IBinder。然后,系统会向后续请求绑定的客户端传送这同一个IBinder,而不再调用onBind()

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

当你实现自己的bound服务时,最重要的工作就是定义onBind()回调方法所返回的接口。定义服务IBinder接口的方式有好几种。

创建一个Bound服务

创建一个支持绑定的服务时,你必须提供一个IBinder,用作客户端和服务间进行通信的编程接口。定义这类接口的方式有三种:

扩展Binder类如果服务是你的应用程序所私有的,并且与客户端运行于同一个进程中(通常都是如此),你应该通过扩展Binder类来创建你的接口,并从onBind()返回一个它的实例。客户端接收该Binder对象并用它来直接访问Binder甚至Service中可用的公共(public)方法。如果你的服务只是为你自己的应用程序执行一些后台工作,那这就是首选的技术方案。不用这种方式来创建接口的理由只有一个,就是服务要被其它应用程序使用或者要跨多个进程使用。
使用Messenger如果你需要接口跨越多个进程进行工作,可以通过Messenger来为服务创建接口。在这种方式下,服务定义一个响应各类消息对象MessageHandler。此HandlerMessenger与客户端共享同一个IBinder的基础,它使得客户端可以用消息对象Message向服务发送指令。此外,客户端还可以定义自己的Message,以便服务能够往回发送消息。这是执行进程间通信(IPC)最为简便的方式,因为Messenger会把所有的请求放入一个独立进程中的队列,这样你就不一定非要把服务设计为线程安全的模式了。
使用AIDLAndroid接口定义语言AIDL(Android Interface Definition Language)完成以下的所有工作:将对象解析为操作系统可识别的原始形态,并将它们跨进程序列化(marshal)以完成IPC。前一个使用Messenger的方式,实际上也是基于AIDL的,它用AIDL作为底层结构。如上所述,Messenger将在一个单独的进程中创建一个包含了所有客户端请求的队列,这样服务每次就只会收到一个请求。可是,如果想让你的服务能同时处理多个请求,那你就可以直接使用AIDL。这种情况下,你的服务必须拥有多线程处理能力,并且是以线程安全的方式编写的。要直接使用AIDL,你必须创建一个.aidl文件,其中定义了编程的接口。Android SDK 工具使用此文件来生成一个抽象类(abstract class),其中实现了接口及对IPC的处理,然后你就可以在自己的服务中扩展该类。

注意:绝大多数应用程序都不应该用AIDL来创建bound服务,因为这可能需要多线程处理能力并且会让代码变得更为复杂。因此,AIDL对绝大多数应用程序都不适用,并且本文也不会讨论如何在服务中使用它的内容。如果你确信需要直接使用AIDL,那请参阅AIDL文档。

扩展Binder类

如果你的服务只用于本地应用程序并且不需要跨进程工作,那你只要实现自己的Binder类即可,这样你的客户端就能直接访问服务中的公共方法了。

注意:仅当客户端和服务位于同一个应用程序和进程中,这也是最常见的情况,这种方式才会有用。比如,一个音乐应用需要把一个activity绑定到它自己的后台音乐播放服务上,采用这种方式就会很不错。

以下是设置步骤:

在你的服务中,创建一个Binder的实例,其中实现以下三者之一:

包含了可供客户端调用的公共方法
返回当前Service实例,其中包含了可供客户端调用的公共方法。
或者,返回内含服务类的其它类的一个实例,服务中包含了可供客户端调用的公共方法。

从回调方法onBind()中返回Binder的该实例。
在客户端中,在回调方法onServiceConnected()中接收Binder并用所提供的方法对绑定的服务进行调用。

注意:
服务和客户端之所以必须位于同一个应用程序中,是为了让客户端能够正确转换(cast)返回的对象并调用对象的API。服务和客户端也必须位于同一个进程中,因为这种方式不能执行任何跨进程的序列化(marshalling)操作。

在进行具体的代码前,我们可以通过一幅图来了解,boundService的工作顺序:



下面是代码:

Service

package com.boundservice;

import java.util.Random;

import android.app.Service;

import android.content.Intent;

import android.os.Binder;

import android.os.IBinder;

/**

* BoundService的使用:使用IBinder(其他两种是Messenger、AIDL)

* 1.onBind()

* 2.客户端与服务的交互是通过IBinder对象,实现一个接口,在这个接口里做些逻辑操作

* 3.一个服务可以被多个客户端绑定 4.当服务完成任务后会自行调用unBindService来解绑

*

*/

public class TestBoundService extends Service {

//给客户端定义的IBinder

private IBinder mBinder = new LocalBinder();

//生成随机数

private final Random mRandom = new Random();

@Override

public IBinder onBind(Intent intent) {

// TODO Auto-generated method stub

return mBinder;

}

/**

* 用于客户端Binder的类。

* 因为知道本服务总是运行于与客户端相同的进程中,我们就不需要用IPC进行处理。

*/

public class LocalBinder extends Binder{

public TestBoundService getService(){

return TestBoundService.this;

}

}

/** method for clients 给客户端定义的方法 public*/

public int getRandomNumber() {

return mRandom.nextInt(100);

}

}

Activity

package com.example.boundservice;

import com.boundservice.TestBoundService;

import com.boundservice.TestBoundService.LocalBinder;

import android.os.Bundle;

import android.os.IBinder;

import android.app.Activity;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.view.Menu;

import android.view.View;

import android.widget.Toast;

public class MainActivity extends Activity {

private boolean mBound = false;

TestBoundService mService;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

}

/**

* 这里是在onStart()方法中绑定服务的,你也可以根据自己的需求绑定在onCreate()中等

* bindService中的参数说明:第一个:intent对象;第二个:ServiceConnection对象;第三个:表明只要绑定存在,就自动建立

* Service;同时也告知Android系统,这个Service的重要程度与调用者相同,

* 除非考虑终止调用者,否则不要关闭这个Service

*/

@Override

protected void onStart() {

super.onStart();

Intent intent = new Intent(MainActivity.this,TestBoundService.class);

bindService(intent, servinceConnection, Context.BIND_AUTO_CREATE);

}

public void click(View view){

if(mBound){

// 调用服务中的公共方法,但是当该调用使应用程序挂起时,就应该另起线程调用该方法,以免降低Activity的性能

int num = mService.getRandomNumber();

Toast.makeText(MainActivity.this, "获取的随机数字是:--->"+num, Toast.LENGTH_SHORT).show();

}

}

@Override

protected void onStop() {

super.onStop();

if(mBound){

unbindService(servinceConnection);

}

}

private ServiceConnection servinceConnection = new ServiceConnection(){

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

//在这个方法中获取Binder对象,通过Binder对象得到服务类的实例,再将mBound置为true

LocalBinder localBinder = (LocalBinder) service;

mService = localBinder.getService();

mBound = true;

}

@Override

public void onServiceDisconnected(ComponentName name) {

//当与服务绑定失败时调用的方法

mBound = false;

}

};

}

xml文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context=".MainActivity" >

<Button

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="点击与服务进行交互"

android:textSize="20sp"

android:onClick="click"

/>

</RelativeLayout>

最后,不要忘记在Manifest.xml中的<Application>节点中注册Service服务

参考网址:http://www.android-doc.com/guide/components/bound-services.html

使用Messenger

与AIDL相比
当你需要进行IPC时,使用Messenger要比用AIDL实现接口要容易些,因为Messenger会把所有调用服务的请求放入一个队列。而纯粹的AIDL接口会把这些请求同时发送给服务,这样服务就必须要能够多线程运行。

对于绝大多数应用程序而言,服务没有必要多线程运行,因此利用Messenger 可以让服务一次只处理一个调用。如果你的服务非要多线程运行,那你就应该用AIDL来定义接口。

如果你的服务需要与远程进程进行通信,那你可以使用一个Messenger来提供服务的接口。这种技术能让你无需使用AIDL就能进行进程间通信(IPC)。

以下概括了Messenger的使用方法:

服务实现一个Handler ,用于客户端每次调用时接收回调。
Handler用于创建一个Messenger对象(它是一个对Handler的引用)。
Messenger对象创建一个IBinder,服务在onBind()中把它返回给客户端。
客户端用IBinderMessenger(引用服务的Handler)实例化,客户端用它向服务发送消息对象Message

服务接收Handler中的每个消息Message——确切的说,是在handleMessage()方法中接收。

通过这种方式,客户端不需要调用服务中的“方法”。取而代之的是,客户端发送“消息”(Message对象),服务则接收位于Handler中的这个消息。

以下是服务使用一个Messenger做为接口的简单例子:

public class
MessengerService
extends
Service {

/** 发送给服务的用于显示信息的指令*/

static
final int MSG_SAY_HELLO
= 1;

/**

* 从客户端接收消息的Handler

*/

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

}

}

}

/**

* 向客户端公布的用于向IncomingHandler发送信息的Messager

*/

final
Messenger mMessenger
= new
Messenger(new
IncomingHandler());

/**

* 当绑定到服务时,我们向Messager返回接口,

* 用于向服务发送消息

*/

@Override

public
IBinder onBind(Intent intent)
{

Toast.makeText(getApplicationContext(),
"binding",
Toast.LENGTH_SHORT).show();

return mMessenger.getBinder();

}

}
请注意Handler中的handleMessage()方法,这里是服务接收输入消息Message的地方,也是根据what数字来决定要执行什么操作的地方。

客户端要做的全部工作就是根据服务返回的IBinder创建一个Messenger,并用send()方法发送一个消息。例如,以下是一个activity示例,它绑定到上述服务,并向服务发送MSG_SAY_HELLO消息:

public class
ActivityMessenger
extends
Activity {

/** 用于和服务通信的Messenger*/

Messenger mService
= null;

/** 标识我们是否已绑定服务的标志 */

boolean mBound;

/**

* 与服务的主接口进行交互的类

*/

private
ServiceConnection mConnection
= new
ServiceConnection()
{

public
void onServiceConnected(ComponentName className,
IBinder service)
{

// 与服务建立联接后将会调用本方法,

// 给出用于和服务交互的对象。

// 我们将用一个Messenger来与服务进行通信,

// 因此这里我们获取到一个原始IBinder对象的客户端实例。

mService =
new
Messenger(service);

mBound =
true;

}

public
void onServiceDisconnected(ComponentName className)
{

// 当与服务的联接被意外中断时——也就是说服务的进程崩溃了,

// 将会调用本方法。

mService =
null;

mBound =
false;

}

};

public
void sayHello(View v)
{

if
(!mBound)
return;

// 创建并向服务发送一个消息,用到了已约定的'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();

// 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()回调后,它再发送一个消息Message给服务,消息的send()方法中的replyTo参数里包含了客户端的Messenger

MessengerService.java(服务)和MessengerServiceActivities.java(客户端)例程中,你可以看到如何双向发送消息的例子。



总结:

绑定一个服务

应用程序组件(客户端)可以通过调用bindService()来绑定服务。然后Android系统会调用服务的onBind()方法,返回一个用于和服务进行交互的IBinder

绑定是异步进行的。bindService()将立即返回,并不会向客户端返回IBinder。为了接收IBinder,客户端必须创建一个ServiceConnection的实例,并把它传给bindService()ServiceConnection包含了一个回调方法,系统将会调用该方法来传递客户端所需的那个IBinder

注意:
只有activity、服务和content provider才可以绑定到服务上——你不能从广播接收器(broadcast receiver)中绑定服务。

因此,要把客户端绑定到服务上,你必须:

实现ServiceConnection
你的实现代码必须重写两个回调方法:onServiceConnected()
系统调用该方法来传递服务的onBind()方法所返回的IBinder
onServiceDisconnected()
当与服务的联接发生意外中断时,比如服务崩溃或者被杀死时,Android系统将会调用该方法。客户端解除绑定时,不会调用该方法。

调用bindService(),传入已实现的ServiceConnection
当系统调用你的onServiceConnected()回调方法时,你可以利用接口中定义的方法开始对服务的调用。
要断开与服务的联接,请调用unbindService()

当客户端被销毁时,与服务的绑定也将解除。但与服务交互完毕后,或者你的activity进入pause状态时,你都应该确保解除绑定,以便服务能够在用完后及时关闭。(绑定和解除绑定的合适时机将在后续章节中继续讨论。)
例如,以下代码段将客户端与前面#扩展Binder类创建的服务联接,而要做的全部工作就是把返回的IBinder转换(cast)为LocalService类,并获取LocalService的实例:

LocalService mService;

private
ServiceConnection mConnection
= new
ServiceConnection()
{

// 与服务的联接建立之后将会调用

public
void onServiceConnected(ComponentName className,
IBinder service)
{

// 因为我们已经与明显是运行于同一进程中的服务建立了联接,

// 我们就可以把它的IBinder转换为一个实体类并直接访问它。

LocalBinder binder
= (LocalBinder) service;

mService = binder.getService();

mBound =
true;

}

// 与服务的联接意外中断时将会调用

public
void onServiceDisconnected(ComponentName className)
{

Log.e(TAG,
"onServiceDisconnected");

mBound =
false;

}

};
利用这个ServiceConnection,客户端就能够把它传入bindService()完成与服务的绑定。例如:

Intent intent
= new
Intent(this,
LocalService.class);

bindService(intent, mConnection,
Context.BIND_AUTO_CREATE);

bindService()的第一个参数是一个Intent,它明确给出了要绑定的服务名称(注意intent可以是隐式的)。
第二个参数是ServiceConnection对象。
第三个参数是一个指明绑定选项的标志。通常应该是BIND_AUTO_CREATE,表示如果服务未启动的话则创建服务。其它可能的值包括BIND_DEBUG_UNBINDBIND_NOT_FOREGROUND,或者为0表示不指定。

其它注意事项
以下是有关绑定服务的一些重要注意事项:

你应该确保捕获DeadObjectException异常,当联接中断时会抛出该异常。这是远程方法唯一会抛出的异常。
对象的引用计数是跨进程的。
你通常应该成对地进行绑定和解除绑定,并与客户端生命周期的启动和结束过程相呼应。比如:

如果仅当你的activity可见时才需要与服务进行交互,则你应该在onStart()中进行绑定,并在onStop()中解除绑定。
如果你的activity需要在stopped后并进入后台期间仍然能接收响应,则你可以在onCreate()中进行绑定,并在[1]中解除绑定。请注意这表明你的activity在整个运行期间都需要使用服务(即使在后台),因此假如服务位于其它进程中,则你会增加进程的重量级,进程也会更容易被系统杀死。

注意:你通常应该在activity的onResume()onPause()中绑定和解除绑定,因为这两个回调方法在每次切换生命周期状态时都会发生,这时你应该让处理工作最少化。而且,如果应用程序中有多个activity都绑定到同一个服务上,则在两个activity间切换时都会发生状态转换,因为当前activity解除绑定(在pause时)后,紧接着下一个activity又会进行绑定(resume时),所以服务也许在销毁后马上就要重建。(这种activity状态转换、多个activity间的生命周期协作在Activities文档中描述。)

更多展示绑定服务的示例代码,请参阅ApiDemos中的RemoteService.java类。

管理Bound服务的生命周期





图 1. started且允许绑定的服务的生命周期

一旦服务被所有客户端解除绑定,则Android系统将会销毁它(除非它同时又是用onStartCommand()started)。因此,如果你的服务就是一个纯粹的bound服务,那你就不需要管理它的生命周期——Android系统会替你管理,根据是否还有客户端对其绑定即可。

不过,如果你选择实现onStartCommand()回调方法,那么你就必须显式地终止服务,因为此服务现在已经被视为started了。这种情况下,无论是否还存在客户端与其绑定,此服务都会运行下去,直至自行用stopSelf()终止或由其它组件调用stopService()来终止。

此外,如果你的服务是started且允许被绑定,那么系统调用你的onUnbind()方法时,你可以选择返回true。这样作的结果就是,下次客户端绑定时将会收到onRebind()调用(而不是收到onBind()调用)。onRebind()返回void,但客户端仍然能在它的onServiceConnected()回调方法中收到IBinder。图1展示了这种生命周期的运行逻辑。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: