Android中bindService基本使用方法概述

时间:2022-11-16 12:12:11

android中有两种主要方式使用service,通过调用context的startservice方法或调用context的bindservice方法,本文只探讨纯bindservice的使用,不涉及任何startservice方法调用的情况。如果想了解startservice相关的使用,请参见《android中startservice基本使用方法概述》

bindservice启动服务的特点

相比于用startservice启动的service,bindservice启动的服务具有如下特点:
1. bindservice启动的服务在调用者和服务之间是典型的client-server的接口,即调用者是客户端,service是服务端,service就一个,但是连接绑定到service上面的客户端client可以是一个或多个。这里特别要说明的是,这里所提到的client指的是组件,比如某个activity。
2. 客户端client(即调用bindservice的一方,比如某个activity)可以通过ibinder接口获取service的实例,从而可以实现在client端直接调用service中的方法以实现灵活的交互,并且可借助ibinder实现跨进程的client-server的交互,这在纯startservice启动的service中是无法实现的。
3. 不同于startservice启动的服务默认无限期执行(可以通过context的stopservice或service的stopself方法停止运行),bindservice启动的服务的生命周期与其绑定的client息息相关。当client销毁的时候,client会自动与service解除绑定,当然client也可以通过明确调用context的unbindservice方法与service解除绑定。当没有任何client与service绑定的时候,service会自行销毁(通过startservice启动的除外)。
4. startservice和bindservice二者执行的回调方法不同:startservice启动的服务会涉及service的的onstartcommand回调方法,而通过bindservice启动的服务会涉及service的onbind、onunbind等回调方法。

bindservice代码示例

使用bindservice主要分两种情形:
1. service的调用者client与service在同一个app中;
2. service的调用者client是app1中的一个activity,而service是app2中的service,client与service分属两个app,这种情形下主要用于实现跨进程的通信。

为了简单起见,本文只讨论第一种情形,即service的调用者client与service在同一个app中,该情形也是我们在实际开发中用到最多的情形。如果想了解通过bindservice在两个不同的进程中让客户端与service通信,可参见另一篇博文《android中通过messenger与service实现进程间双向通信》。

下面我们通过一个例子演示一下第一种情形下bindservice的基本使用流程。

首先我们有一个testservice,该类继承自service,其是client-server接口中的server端。我们在其主要的生命周期回调方法中都加入了输出语句。testservice代码如下:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.ispring.startservicedemo;
 
import android.app.service;
import android.content.intent;
import android.os.binder;
import android.os.ibinder;
import android.util.log;
 
import java.util.random;
 
public class testservice extends service {
 
  public class mybinder extends binder{
 
    public testservice getservice(){
      return testservice.this;
    }
 
  }
 
  //通过binder实现调用者client与service之间的通信
  private mybinder binder = new mybinder();
 
  private final random generator = new random();
 
  @override
  public void oncreate() {
    log.i("demolog","testservice -> oncreate, thread: " + thread.currentthread().getname());
    super.oncreate();
  }
 
  @override
  public int onstartcommand(intent intent, int flags, int startid) {
    log.i("demolog", "testservice -> onstartcommand, startid: " + startid + ", thread: " + thread.currentthread().getname());
    return start_not_sticky;
  }
 
  @override
  public ibinder onbind(intent intent) {
    log.i("demolog", "testservice -> onbind, thread: " + thread.currentthread().getname());
    return binder;
  }
 
  @override
  public boolean onunbind(intent intent) {
    log.i("demolog", "testservice -> onunbind, from:" + intent.getstringextra("from"));
    return false;
  }
 
  @override
  public void ondestroy() {
    log.i("demolog", "testservice -> ondestroy, thread: " + thread.currentthread().getname());
    super.ondestroy();
  }
 
  //getrandomnumber是service暴露出去供client调用的公共方法
  public int getrandomnumber(){
    return generator.nextint();
  }
}

在该app中,除了testservice,还有两个activity: activitya和activityb,它们都是service的调用者,即client-server接口中的client。

activitya是app的启动界面,界面如下:

Android中bindService基本使用方法概述

activitya的代码如下:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.ispring.startservicedemo;
 
import android.app.activity;
import android.content.componentname;
import android.content.intent;
import android.content.serviceconnection;
import android.os.bundle;
import android.os.ibinder;
import android.util.log;
import android.view.view;
import android.widget.button;
 
 
public class activitya extends activity implements button.onclicklistener {
 
  private testservice service = null;
 
  private boolean isbound = false;
 
  private serviceconnection conn = new serviceconnection() {
    @override
    public void onserviceconnected(componentname name, ibinder binder) {
      isbound = true;
      testservice.mybinder mybinder = (testservice.mybinder)binder;
      service = mybinder.getservice();
      log.i("demolog", "activitya onserviceconnected");
      int num = service.getrandomnumber();
      log.i("demolog", "activitya 中调用 testservice的getrandomnumber方法, 结果: " + num);
    }
 
    @override
    public void onservicedisconnected(componentname name) {
      isbound = false;
      log.i("demolog", "activitya onservicedisconnected");
    }
  };
 
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_a);
    log.i("demolog", "activitya -> oncreate, thread: " + thread.currentthread().getname());
  }
 
  @override
  public void onclick(view v) {
    if(v.getid() == r.id.btnbindservice){
      //单击了“bindservice”按钮
      intent intent = new intent(this, testservice.class);
      intent.putextra("from", "activitya");
      log.i("demolog", "----------------------------------------------------------------------");
      log.i("demolog", "activitya 执行 bindservice");
      bindservice(intent, conn, bind_auto_create);
    }else if(v.getid() == r.id.btnunbindservice){
      //单击了“unbindservice”按钮
      if(isbound){
        log.i("demolog", "----------------------------------------------------------------------");
        log.i("demolog", "activitya 执行 unbindservice");
        unbindservice(conn);
      }
    }else if(v.getid() == r.id.btnstartactivityb){
      //单击了“start activityb”按钮
      intent intent = new intent(this, activityb.class);
      log.i("demolog", "----------------------------------------------------------------------");
      log.i("demolog", "activitya 启动 activityb");
      startactivity(intent);
    }else if(v.getid() == r.id.btnfinish){
      //单击了“finish”按钮
      log.i("demolog", "----------------------------------------------------------------------");
      log.i("demolog", "activitya 执行 finish");
      this.finish();
    }
  }
 
  @override
  protected void ondestroy() {
    super.ondestroy();
    log.i("demolog", "activitya -> ondestroy");
  }
}

通过单击activitya上的“start activityb”可以启动activityb,activityb的界面如下:

Android中bindService基本使用方法概述

activityb的代码如下:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package com.ispring.startservicedemo;
 
import android.app.activity;
import android.content.componentname;
import android.content.intent;
import android.content.serviceconnection;
import android.os.bundle;
import android.os.ibinder;
import android.util.log;
import android.view.view;
import android.widget.button;
 
 
public class activityb extends activity implements button.onclicklistener {
 
  private testservice service = null;
 
  private boolean isbound = false;
 
  private serviceconnection conn = new serviceconnection() {
    @override
    public void onserviceconnected(componentname name, ibinder binder) {
      isbound = true;
      testservice.mybinder mybinder = (testservice.mybinder)binder;
      service = mybinder.getservice();
      log.i("demolog", "activityb onserviceconnected");
      int num = service.getrandomnumber();
      log.i("demolog", "activityb 中调用 testservice的getrandomnumber方法, 结果: " + num);
    }
 
    @override
    public void onservicedisconnected(componentname name) {
      isbound = false;
      log.i("demolog", "activityb onservicedisconnected");
    }
  };
 
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_b);
  }
 
  @override
  public void onclick(view v) {
    if(v.getid() == r.id.btnbindservice){
      intent intent = new intent(this, testservice.class);
      intent.putextra("from", "activityb");
      log.i("demolog", "----------------------------------------------------------------------");
      log.i("demolog", "activityb 执行 bindservice");
      bindservice(intent, conn, bind_auto_create);
    }else if(v.getid() == r.id.btnunbindservice){
      if(isbound){
        log.i("demolog", "----------------------------------------------------------------------");
        log.i("demolog", "activityb 执行 unbindservice");
        unbindservice(conn);
      }
    }else if(v.getid() == r.id.btnfinish){
      //单击了“finish”按钮
      log.i("demolog", "----------------------------------------------------------------------");
      log.i("demolog", "activityb 执行 finish");
      this.finish();
    }
  }
 
  @override
  public void ondestroy(){
    super.ondestroy();
    log.i("demolog", "activityb -> ondestroy");
  }
}

我们暂时不点击上面的按钮,先看一下testservice和activitya的代码。

调用者(客户端client)要想和service进行交互,那么service和调用者必须都要做好准备。

我们先看service要做的工作
使用bindservice将client与server联系在一起的关键是binder,在testservice中,我们在其中写了一个内部类mybinder,该类有个公共方法getservice,通过该方法我们可以获取包含mybinder的testservice。如果想要自己的service支持bindservice启动方式,就必须在service的onbind中返回一个ibinder类型的实例。在示例中,我们实例化了一个mybinder的实例binder作为testservice的字段,并且将其作为onbind的返回值。
我们总结一下如果想让service支持bindservice调用方式,service需要做以下事情:
1. 在service的onbind方法中返回ibinder类型的实例。
2. onbind方法返回的ibinder的实例需要能够返回service实例本身或者通过binder暴露出service公共方法。通常情况下,最简单明了的做法就是将binder弄成service的内部类,然后在binder中加入类似于getservice之类的方法返回包含binder的service,这样client可以通过该方法得到service实例。

我们已经知道了service需要做的事情,我们接下来看一下调用者需要做的工作。
在我们的示例中,调用者(也就是客户端client)是activitya,我们在其中初始化了一个serviceconnection类型的实例,需要重写其onserviceconnected方法以及onservicedisconnected方法。我们需要将这个serviceconnection类型的实例作为参数传给bindservice方法,当service还没有创建的时候,android会先创建service的实例,然后执行service的onbind方法,得到ibinder类型的实例,将该方法作为参数传入client端的serviceconnection的onserviceconnected方法中,onserviceconnected方法的执行表明client端可以获取到service的ibinder类型的实例,然后将ibinder转换为自己实际的binder类型,然后可以通过其直接获取service的实例或者通过binder直接执行公共方法,这取决于service中binder的具体实现。在本例中,在onserviceconnected方法中,调用者activitya通过binder的getservice方法获取到了与其对应的service,然后我们就可以直接调用service的公共方法以达到使用service的目的,这样client与service之间就通过ibinder建立了连接,从而进行交互。当client与service失去连接时会触发onservicedisconnected方法。
我们总结一下client端要做的事情:
1. 创建serviceconnection类型的实例,并重写其onserviceconnected方法和onservicedisconnected方法。
2. 当android执行onserviceconnected回调方法时,我们可以通过ibinder实例得到service的实例对象或直接调用binder的公共方法,这样就实现了client与service的连接。
3. 当android执行onservicedisconnected回调方法时,表示client与service之间断开了连接,我们在此处要写一些断开连接后需要做的处理。

在知道了如何让client与service进行交互之后,我们运行我们的app,观察各个回调方法的执行过程,我们有三个测试流程。

测试流程a

该测试涉及到activitya,但不涉及activityb.
首先我们点击activitya中的“bindservice”按钮,然后点击”unbindservice”按钮,输出结果如下所示:

Android中bindService基本使用方法概述

首先,通过上面的代码我们可以看到service中执行的回调方法都是执行在主线程中的。
当我们调用bindservice方法时,我们需要将intent、serviceconnection等实例传入,intent包含了我们要绑定的service,serviceconnection我们在上面提到过,实现了其onserviceconnected方法和onservicedisconnected方法。 在调用了bindservice之后,由于service此时还不存在,那么android就会首先创建一个testservice的实例,并执行其oncreate回调方法,oncreate方法在其生命周期中只会被调用一次。然后会调用service的onbind方法,该方法只有在第一次bindservice调用后才会执行,onbind执行后会返回一个ibinder类型的实例,此时android会将该ibinder实例存起来,这个ibinder实例是对所有client共享的。当下次其他的client执行bindservice的时候,不会再执行onbind方法,因为我们之前已经得到了一个ibinder实例,android会直接使用这个ibinder实例。 在得到了ibinder实例之后,android会执行client端serviceconnection中的onserviceconnected方法,在该方法中我们会得到ibinder实例,并通过该ibinder实例得到了testservice实例,这样我们的客户端activitya就通过ibinder与testservice建立了连接,我们就可以调用testservice的公共方法,比如调用其getrandomnumber方法获得随机数。

总结一下调用bindservice之后发生的事情:
client 执行 bindservice ->
如果service不存在,service 执行 oncreate ->
如果没有执行过onbind,service 执行 onbind ->
client的实例serviceconnection 执行 onserviceconnected

在执行了bindservice之后,一共有一个client连接到了testservice,即activitya,每次client在调用了unbindservice方法之后,该client会与service解除绑定,在与某个client解除绑定之后,service会检测是否还有其他的client与其连接绑定,如果没有其他任何client与其处于连接状态,那么service会执行onunbind方法,然后执行ondestroy方法,最终销毁自己。当activitya执行unbindservice的时候,唯一的一个client与testservice解除了绑定的关系,testservice就执行了onunbind方法,进而执行ondestroy方法。

总结一下调用unbindservice之后发生的事情:
client 执行 unbindservice ->
client 与 service 解除绑定连接状态 ->
service 检测是否还有其他client与其连接,如果没有 ->
service 执行onunbind ->
service 执行ondestroy

测试流程b

我们在测试完第一种流程后,关掉app,重启app,进行第二种测试流程。
该测试也只涉及activitya,不涉及activityb。首先先点击activitya中的“bindservice”按钮,然后点击”finish”按钮,输出结果如下图所示:

Android中bindService基本使用方法概述

在该测试中,我们首先通过点击”bindservice”按钮,使得activitya绑定了testservice,但是我们没有调用unbindservice,而是直接通过调用“finish”按钮让activitya直接销毁,通过上面的输出结果我们可以看到,在activitya销毁的时候,执行了activitya的ondestroy回调方法,之后testservice依次执行了onunbind、ondestroy回调方法,testservice销毁。client与service通过bindservice连接起来之后,如果client销毁,那么client会自动与service解除绑定,相当于在destroy之前会执行unbindservice,在activitya销毁之后,activitya与service解除了绑定,此时再没有client与service处于连接绑定状态,这样service就会执行onunbind回调方法,表示没有client和我玩了,最后执行ondestroy回调方法。

测试流程c

我们在之前的两次测试流程中都只涉及activtitya,本测试流程会同时涉及activitya以及activityb。
首先关掉app,重启app,按照以下步骤测试:
1. 点击activitya中的”bindservice”按钮
2. 点击activitya中的”start activityb”按钮,界面切换到activityb
3. 点击activityb中的”bindservice”按钮
4. 点击activityb中的”unbindservice”按钮
5. 点击activityb中的”finish”按钮
6. 点击activitya中的”unbindservice”按钮

logcat输出结果如下:

Android中bindService基本使用方法概述

下面我们依次分析每一步产生的影响,以便于完整地理解通过bindservice启动的service的生命周期:

点击activitya中的”bindservice”按钮
由于初始情况下testservice实例不存在,也就是testservice没有运行。第一次调用bindservice会实例化testservice,然后会执行其onbind方法,得到ibinder类型的实例,然后将其作为参数传入activitya的serviceconnection的onserviceconnected方法中,标志着activitya与testservice建立了绑定连接,此时只有activitya这一个客户端client与testservice绑定。

点击activitya中的”start activityb”按钮,界面切换到activityb

点击activityb中的”bindservice”按钮
由于testservice已经处于运行状态,所以activityb调用bindservice时,不会重新创建testservice的实例,所以也不会执行testservice的oncreate回调方法,由于在activitya执行bindservice的时候就已经执行了testservice的onbind回调方法而获取ibinder实例,并且该ibinder实例在所有的client之间是共享的,所以当activityb执行bindservice的时候,不会执行其onbind回调方法,而是直接获取上次已经获取到的ibinder实例。并将其作为参数传入activityb的serviceconnection的onserviceconnected方法中,标志着activityb与testservice建立了绑定连接,此时有两个客户单client(activitya和activityb)与testservice绑定。

点击activityb中的”unbindservice”按钮
activityb执行了unbindservice之后,activityb就与testservice解除了绑定。当没有任何client与service处于绑定连接状态的时候,testservice才会执行onunbind方法、ondestroy方法。但是由于此时还有activitya这个client与testservice处于绑定连接中,所以不会执行service的onbind及ondestroy回调方法。

点击activityb中的”finish”按钮
执行了activityb的finish方法后,activityb销毁了,界面返回到activitya

点击activitya中的”unbindservice”按钮
activitya执行unbindservice之后,activitya与testservice就解除绑定了,这样就没有客户端client与testservice相连,这时候android会销毁testservice,在销毁前会先执行testservice的onunbind方法,然后才会执行其ondestroy方法,这样testservice就销毁了。

bindservice生命周期流程图

这里特别要说明的是,本文所提到的client指的是组件component,比如某个activity。如果在某一个activity中,多次调用bindservice方法连接service,那么对于service来说,这个activity也只是一个client,而不是多个client。

最后我们将bindservice启动的service的生命周期总结为如下的流程图:

Android中bindService基本使用方法概述

希望本文对大家了解bindservice的使用有所帮助。