Android中bindService基本使用方法概述

Android中bindService基本使用方法概述Android中有两种主要方式使用Service,通过调用Context的startService方法或调用Context的bindService方法,本文只探讨纯bindService的使用,不涉及任何startService方法调用的情况。bindService启动服务的特点相比于用startService启动的Service,bindService启动的服务具有如下特点: 

大家好,又见面了,我是你们的朋友全栈君。

Android中有两种主要方式使用Service,通过调用Context的startService方法或调用Context的bindService方法,本文只探讨纯bindService的使用,不涉及任何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的使用有所帮助。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/136670.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • excel怎么只打印某页?excel怎么只打印某几页

    excel怎么只打印某页?excel怎么只打印某几页

  • 常用的css文件_css常用的三种选择器

    常用的css文件_css常用的三种选择器reset.css(几乎每个项目都要引入的css)@charset"utf-8";html{background-color:#fff;color:#000;font-size

  • ubuntu16.04安装搜狗输入法_ubuntu18.04安装搜狗输入法

    ubuntu16.04安装搜狗输入法_ubuntu18.04安装搜狗输入法首先安装fcitx一、检测是否安装fcitx首先检测是否有fcitx,因为搜狗拼音依赖fcitx>fcitx提示:程序“fcitx”尚未安装。您可以使用以下命令安装:>sudoapt-getinstallfcitx-bin二、安装fcitxsudoapt-getinstallfcitx-bin相关的依赖库和框架都会自动安装上。sudoapt-getinstall…

    2022年10月18日
  • python进阶(13)装饰器[通俗易懂]

    python进阶(13)装饰器[通俗易懂]装饰器装饰器放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,

  • ArrayList初始化长度

    ArrayList初始化长度每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的…

    2022年10月23日
  • CentOs6 Yum 源失效 404[通俗易懂]

    CentOs6 Yum 源失效 404[通俗易懂]今天下午想装点东西。。突然发现所有的6yum源都失效了YumRepoError:AllmirrorURLsarenotusingftp,http[s]orfile.Eg.Invalidrelease/repo/archcombination/removingmirrorlistwithnovalidmirrors:/var/cache/yum/x86_64/6/base/mirrorlist.txtError:Cannotfindavalid..

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号