Android service(服务)中的绑定服务(binderService)详解与使用

前言

前两篇文章中介绍了普通的后台服务及前台服务,这些服务有个共同的特点就是,启动服务的组件和服务之间没有任何关系。要想两者之间发生点关系,那就需要将两者之间绑定起来,这就用到了绑定服务。

何为绑定服务

绑定服务是提供客户端 (例如 Android 活动))可以与之交互的客户端-服务器接口的 Android 服务。绑定服务一般涉及客户端与服务端,绑定服务是客户端-服务端接口中的服务端。借助绑定服务,组件(例如 Activity)可以绑定到服务、发送请求、接收响应,以及执行进程间通信 (IPC)。绑定服务通常只在为其他应用组件提供服务时处于活动状态,不会无限期在后台运行。简单来说就是为客户端提供客户端服务器接口以直接与服务交互的服务称为-绑定服务。

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

绑定服务允许应用组件通过调用 bindService() 与其绑定,从而创建长期连接。此服务通常不允许组件通过调用 startService() 来启动它。

如需与 Activity 和其他应用组件中的服务进行交互,或需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。

启动服务与绑定服务

一个服务的类型一般是单一的,如启动服务和绑定服务,但也可以是多种状态的,您可以创建同时具有已启动和已绑定两种状态的服务。换言之,您可以通过调用 startService() 来启动服务,让服务无限期运行,您也可以通过调用 bindService() 让客户端绑定到该服务。

如果您确实允许服务同时具有已启动已绑定状态,那么服务启动后,系统不会在所有客户端均与服务取消绑定后销毁服务,而必须由您通过调用 stopSelf() 或 stopService() 显式停止服务。即同时具备两种状态的情况下,解绑服务了,不能销毁服务服务。

尽管您通常应实现 onBind()onStartCommand(),但有时也需要同时实现这两种方法。例如,音乐播放器可能认为,让其服务无限期运行并同时提供绑定很有用处。如此一来,Activity 便可启动服务来播放音乐,并且即使用户离开应用,音乐播放也不会停止。然后,当用户返回应用时,Activity 便能绑定到服务,重新获得播放控制权。

绑定服务的特点

客户端通过调用 bindService() 绑定到服务。调用时,它必须提供 ServiceConnection 的实现,后者会监控与服务的连接。bindService() 的返回值指示所请求的服务是否存在,以及是否允许客户端访问该服务。Android 系统创建客户端与服务之间的连接时,会对 ServiceConnection 调用 onServiceConnected()onServiceConnected() 方法包含一个 IBinder 参数,客户端随后会使用该参数与绑定服务通信。

您可以将多个客户端同时连接到某项服务。但是,系统会缓存 IBinder 服务通信通道。换言之,只有在第一个客户端绑定服务时,系统才会调用服务的 onBind() 方法来生成 IBinder。然后,系统会将该 IBinder 传递至绑定到同一服务的所有其他客户端,无需再次调用 onBind()

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

在实现绑定服务的过程中,最重要的环节是定义 onBind() 回调方法所返回的接口。下一部分将为您介绍几种不同方式,以便您定义服务的 IBinder 接口。

如要创建绑定服务:

  • 您需通过实现 onBind() 回调方法返回 IBinder,从而定义与服务进行通信的接口。然后,其他应用组件可通过调用 bindService() 来检索该接口,并开始调用与服务相关的方法。服务只用于与其绑定的应用组件,因此若没有组件与该服务绑定,则系统会销毁该服务。您不必像通过 onStartCommand() 启动的服务那样,以相同方式停止绑定服务。

  • 您必须定义指定客户端如何与服务进行通信的接口。服务与客户端之间的这个接口必须是 IBinder 的实现,并且服务必须从 onBind() 回调方法返回该接口。收到 IBinder 后,客户端便可开始通过该接口与服务进行交互。

多个客户端可以同时绑定到服务。完成与服务的交互后,客户端会通过调用 unbindService() 来取消绑定。如果没有绑定到服务的客户端,则系统会销毁该服务。

实现绑定服务有多种方法,并且此实现比启动服务更为复杂。

绑定服务使用

创建绑定服务

创建提供绑定的服务时,您必须提供 IBinder,用以提供编程接口,供客户端与服务进行交互之用。您可以通过三种方式定义接口:

1、 扩展binder类

通过继承Binder类的形式来进行服务绑定,此中情况最为常用,它适用于在应用内部服务与客户端的通信,并且服务与客户端在相同的进程中运行(常见情况),您应当通过扩展 Binder 类并从 onBind() 返回该类的实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Binder 实现或 Service 中提供的公共方法。

应用场景

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

如果服务只是您自有应用的后台工作器,应优先采用这种方式。您不使用这种方式创建接口的唯一一种情况是:其他应用或不同进程占用了您的服务。

需要注意的是:

  • 只有当客户端和服务处于同一应用和进程内(最常见的情况)时,此方式才有效。例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方式非常有效。
  • 服务和客户端必须在同一应用内,这样客户端才能转换返回的对象并正确调用其 API。服务和客户端还必须在同一进程内,因为此方式不执行任何跨进程编组。
使用案例

实现一个绑定服务需要做如下开发步骤:

  1. 创建一个服务类继承自Service,并实现Service类的生命周期函数。
  2. 在您的服务中,创建可执行以下某种操作的 Binder 实例:
  • 包含客户端可调用的公共方法。
    
  • 返回当前的 Service 实例,该实例中包含客户端可调用的公共方法。这里不绝对,也可以直接定义方法,不做返回,直接在onBind() 中返回Binder实例也可。
    
  • 返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法。
    
  1. onBind() 回调方法返回此 Binder 实例。
  2. 在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务。

按照上述描述的步骤,首先创建一个服务类

package com.cop.ronghw.study_android_exact;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

/**
 * 创建一个绑定服务
 */
public class MyBinderService extends Service {
    public MyBinderService() {
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }
}

其次创建Binder实例,做为绑定器供客户端使用,并提供客户端调用的公共方法:

/**
 * 创建一个绑定服务
 */
public class MyBinderService extends Service {
    public MyBinderService() {
    }

    //给客户端使用的binder
    private final IBinder iBinder =  new MyServiceBinder();
    /**
     * 用于客户端绑定器的类。因为我们知道这个服务总是运行在与其客户机相同的进程中,
     * 所以我们不需要处理IPC。
     */
    public class MyServiceBinder extends Binder{
          MyBinderService getService(){
              //返回MyBinderService实例,以便客户端可以调用公共方法
             return MyBinderService.this;
          }
        }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("MyBinderService","执行onBind方法");
        return iBinder;
    }

    //服务提供的公共方法,供客户端调用
    public String getFileString(){
       return "file获取成功";
    }
    // 文件上传
    public void uplodFile(){
        Log.i("MyBinderService","uplodFile-文件上传");
    }

    @Override
    public void onCreate() {
        Log.i("MyBinderService","执行onCreate方法");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("MyBinderService","执行onStartCommand方法");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("MyBinderService","执行onDestroy方法");
        super.onDestroy();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i("MyBinderService","执行onUnbind方法");
        return super.onUnbind(intent);
    }

}

MyServiceBinder 为客户端提供 getService() 方法,用于检索 MyBinderService 的当前实例。这样,客户端便可调用服务中的公共方法。例如,客户端可调用服务中的 getFileString()方法和uplodFile() 两个公共的方法。

接着在客户端添加测试按钮,并绑定服务:

public class MyBinderActivity extends AppCompatActivity{

    MyBinderService myBinderService;

   //定义服务绑定的回调,传递给bindService()
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className,IBinder service) {
            //我们已经绑定到MyBinderService,向下转型转换IBinder为MyBinderService.MyServiceBinder并获得MyBinderService实例
           MyBinderService.MyServiceBinder binder = (MyBinderService.MyServiceBinder) service;
           myBinderService = binder.getService();

        }
        @Override
        public void onServiceDisconnected(ComponentName arg0) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mybinder);
    }

    // 活动隐藏挂起后解绑服务
    @Override
    protected void onStop() {
        super.onStop();
        //解绑服务
        unbindService(connection);
    }

    // 在这里我们分别在活动OnStart这里及界面刚刚加载时就给此活动绑定好服务
    @Override
    protected void onStart() {
        super.onStart();
        // activity加载后就进行服务绑定
        Intent intent = new Intent(this, MyBinderService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    //测试点击事件
    public void onButtonclick(View v){
        String fileString  = myBinderService.getFileString();
        Log.i("MyBinderActivity","文件内容为=="+fileString);
        myBinderService.uplodFile();
    }
}

上述代码中:MyBinderActivity 启动后并是可见状态下会自动调用bindService绑定服务,通过此方法连接到应用程序服务,并在需要时创建它。这定义了应用程序和服务之间的依赖关系。bindService方法提供了三个参数:

  • service intent :标识要连接的服务。Intent必须指定显式的组件名称。
  • serviceConnection :在服务启动和停止时接收信息。这必须是一个有效的ServiceConnection对象,它不能为空。它类似一个通信管道,通过ServiceConnection实例对象建立起了客户端与服务器的连接。
  • flags:这里提供了多种标志,本示例中使用BIND_AUTO_CREATE,代表只要绑定存在,就自动创建服务。第三个参数是指示绑定选项的标记。如要创建尚未处于活动状态的服务,此参数通常应为 BIND_AUTO_CREATE。其他可能的值为 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或者 0(表示无此参数)。

其中参数serviceConnection实例对象,是在MyBinderActivity中创建的,此内部类中重写了两个方法:onServiceDisconnected()onServiceConnected (ComponentName name, IBinder service) 这两个方法分别是在客户端与服务器端建立连接失败和建立连接成功后调用。其中onServiceDisconnected()方法是在当与服务的连接丢失时调用。这通常发生在托管服务的进程崩溃或被杀死时。这并不会移除ServiceConnection本身——这个与服务的绑定将保持活动状态,当服务下次运行时,您将收到对onserviceconnconnected (ComponentName, IBinder)的调用。而在onServiceConnected方法中,通过向下转型,获取到了service实例,后续的方法中会使用service实例来使用service中定义的公共方法。
需要注意的是,onServiceDisconnected()onServiceConnected (ComponentName name, IBinder service) 这两个方法均是在bindservice() 方法调用之后触发。

下面来看一下执行日志:当进入到MyBinderActivity时会执行活动的onStart方法,在onStart方法中调用了 bindService(intent, connection, Context.BIND_AUTO_CREATE) 进行服务的绑定,此时会调用到服务的oncreat方法和onbind方法如下:

此时如果绑定成功就会在MyBinderActivity返回了MyBinderService实例。此时就可以在活动中调用MyBinderService中的方法了。这样就使得客户端与服务端建立起了联系。

bindService方法的返回值是一个布尔类型的值, 如果系统正在启动客户端有权限绑定的服务,则为True;如果系统无法找到该服务,或者如果您的客户端没有绑定该服务的权限,则为False。不管返回值是多少,您都应该稍后调用unbindService(ServiceConnection)来释放连接。

总结

我们再把上述步骤总结一下:

应用组件(客户端)可通过调用 bindService() 绑定到服务。然后,Android 系统会调用服务的 onBind() 方法,该方法会返回用于与服务交互的 IBinder

绑定是异步操作,并且 bindService() 可立即返回,无需将 IBinder 返回给客户端。如要接收 IBinder,客户端必须创建一个 ServiceConnection 实例,并将其传递给 bindService()ServiceConnection 包含一个回调方法,系统通过调用该回调方法来传递 IBinder

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

如要从您的客户端绑定到服务,请按以下步骤操作:

  1. 实现 ServiceConnection。
    您的实现必须替换两个回调方法:
  • onServiceConnected()
    系统会调用该方法以传递服务的 onBind() 方法所返回的 IBinder。
  • onServiceDisconnected()
    当与服务的连接意外中断时,例如服务崩溃或被终止时,Android 系统会调用该方法。当客户端取消绑定时,系统不会调用该方法。
  1. 调用 bindService(),从而传递 ServiceConnection 实现。

注意:如果该方法返回 false,说明您的客户端与服务之间并无有效连接。不过,您的客户端仍应调用 unbindService();否则,您的客户端会使服务无法在空闲时关闭。

  1. 当系统调用 onServiceConnected() 回调方法时,您可以使用接口定义的方法开始调用服务。
  2. 如要断开与服务的连接,请调用 unbindService()。
    当应用销毁客户端时,如果客户端仍与服务保持绑定状态,销毁会导致客户端取消绑定。更好的做法是在客户端与服务交互完成后,就立即取消客户端的绑定。这样做可以关闭空闲的服务。

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

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

注意:通常情况下,不应在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每次切换生命周期状态时都会发生这些回调,您应将这些转换期间的处理工作保持在最低水平。此外,如果您的应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,那么如果当前 Activity 先取消绑定(暂停期间),然后下一个 Activity 再进行绑定(恢复期间),系统可能就会销毁后再重新创建该服务。

绑定服务的生命周期

当服务与所有客户端之间的绑定全部取消时,Android 系统会销毁该服务(除非还使用 startService() 调用启动了该服务)。因此,如果您的服务是纯粹的绑定服务,则无需对其生命周期进行管理,Android 系统会根据它是否绑定到任何客户端代您管理。

不过,如果您选择实现 onStartCommand() 回调方法,就必须显式停止服务,因为系统现在将服务视为已启动状态。在此情况下,服务将一直运行,直到其通过 stopSelf() 自行停止,或其他组件调用 stopService()(与该服务是否绑定到任何客户端无关)。

此外,如果您的服务已启动并接受绑定,那么当系统调用您的 onUnbind() 方法时,如果您想在客户端下一次绑定到服务时接收 onRebind() 调用,可以选择返回 true。onRebind() 返回空值,但客户端仍会在其 onServiceConnected() 回调中接收 IBinder。下图说明了这种生命周期的逻辑。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2023年12月13日
下一篇 2023年12月13日

相关推荐