BoundService的基本使用

本文详细介绍了Android中的绑定服务,包括其工作原理、实现方法以及客户端如何与服务进行交互。重点讲解了通过扩展Binder类和使用Messenger两种方式实现绑定服务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

导读:

         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会把所有的请求放入一个独立进程中的队列,这样你就不一定非要把服务设计为线程安全的模式了。
使用AIDL
Android接口定义语言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绑定到它自己的后台音乐播放服务上,采用这种方式就会很不错。

以下是设置步骤:

  1. 在你的服务中,创建一个Binder的实例,其中实现以下三者之一:
    • 包含了可供客户端调用的公共方法
    • 返回当前Service实例,其中包含了可供客户端调用的公共方法。
    • 或者,返回内含服务类的其它类的一个实例,服务中包含了可供客户端调用的公共方法。
  2. 从回调方法onBind()中返回Binder的该实例。
  3. 在客户端中,在回调方法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的使用方法:

通过这种方式,客户端不需要调用服务中的“方法”。取而代之的是,客户端发送“消息”(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)中绑定服务。

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

  1. 实现ServiceConnection
    你的实现代码必须重写两个回调方法:
    onServiceConnected()
    系统调用该方法来传递服务的 onBind()方法所返回的 IBinder
    onServiceDisconnected()
    当与服务的联接发生意外中断时,比如服务崩溃或者被杀死时,Android系统将会调用该方法。客户端解除绑定时, 不会调用该方法。
  2. 调用bindService(),传入已实现的ServiceConnection
  3. 当系统调用你的onServiceConnected()回调方法时,你可以利用接口中定义的方法开始对服务的调用。
  4. 要断开与服务的联接,请调用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);

其它注意事项

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

  • 你应该确保捕获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服务的生命周期


Service binding tree lifecycle.png

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

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

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

此外,如果你的服务是started且允许被绑定,那么系统调用你的onUnbind()方法时,你可以选择返回true。这样作的结果就是,下次客户端绑定时将会收到onRebind()调用(而不是收到onBind()调用)。onRebind()返回void,但客户端仍然能在它的onServiceConnected()回调方法中收到IBinder。图1展示了这种生命周期的运行逻辑。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值