原文:http://android.eoe.cn/topic/android_sdk

一个绑定的服务是客户服务器接口上的一个服务器。一个绑定的服务允许组件(如:活动)来绑定一个服务,传送请求,接收响应,甚至执行进程间的通信(IPC)。绑定服务通常只生存在其服务于另一个程序组件时,并且不会无限期的在后台运行

这篇文章将向你展示怎么创建一个绑定服务,包括怎么样从其他应用程序组件绑定到服务。然而你也应该查看Services|服务-Services文档,更多关于普通情况下服务的额外信息,如怎么从一个服务传递通知,设备服务在前台运行等。

基础知识-The Basics

绑定服务是一个Service类的一个实现,允许其它服务绑定到它并与其互动。为服务提供绑定,你必须实现onBind())回调方法。这个方法返回一个IBinder对象,它定义了程序接口,用户可以用其与服务交互。

一个客户可以通过调用bindService())来绑定到一个服务。当这么做时,必须提供一个ServiceConnection的实现,它监视着与服务的连接。bindService())方法立刻返回了一个空值。但是当Android系统在用户和服务器之间创建一个连接时,它将会在ServiceConnection上调用onServiceConnected())方法来传递IBinder对象,使用户可以与服务通信

多个用户可以同时连接到一个服务。但是,系统只以第一个用户绑定时调用onBind())方法来获得IBinder对象。然后,系统将同一个IBinder对象传递给后续绑定的客户,并不会重新调用onBind())方法。

当最后一个用户从服务上解绑时,系统摧毁这个服务(除非这个服务由startService()))。

当你实现绑定服务时,最重要的就是定义onBind())回调方法返回的接口。你可以使用有一些不同的方法来定义服务的IBinder接口,接下来的章节将讨论每一项技术。

创建绑定服务-Creating a Bound Service


当创建一个提供了绑定的服务时,你必须提供一个IBinder来提供程序接口,用户可以用这个接口与服务交互。有三种方法可以用来定义这个接口:

扩展binder类-Extending the Binder class

如果你的服务只对你自己的应用程序私用,并且作为客户在同一个进程中运行(这种情况很常见),你应该通过扩展Binder类来创建你的接口并且从onBind())运行一个实例。客户接收Binder并直接使用它来接入Binder实现,甚至Service中可用的公共方法。

当服务对于你的应用程序几乎是运行于后台时,这是首选技术。这种情况下,唯一一个你不建立自己接口的原因是你的服务被其他应用程序所用或使用了多个分开的进程。

使用一个消息传递器-Using a Messenger

如果你想让你的接口在多个不同的进程间工作,你可以为服务创建一个带有Messenger的接口。在这种情况下,服务将定义一个Handler来响应不同类型的Message对象。这个HandlerMessenger的基础,它可以与客户共享一个IBinder,允许客户使用Message对象向服务发送指令。以此,客户可以定义一个属于自己的Messenger,这样,服务就可以把消息传递回来。

这是最简单的执行进种间通信(IPC)的方法,因为Messenger队列所有的请求到一个单独的线程当中,所以你不需要设计你的服务为线程安全(thread-safe)

使用AIDL

AIDL(Android接口定义语言-Android Interface Definition Language)执行了把一个对象分解到操作系统能理解的基元,并安排它们到各个进程间来完成IPC等所有工作。前文中提到的技术,使用一个Messenger(消息传送器)就是基于AIDL和其下面的结构。如前所述,Messenger创建了一个队列,把所有的请求都放在一个线程中,所以服务一次只接收一个请求。然而,如果想你的服务同时接收多个请求,那么你可以直接创建AIDL。在这种情况下,你的服务必须有能力执行多个纯程,并且为线程安全。

为了直接使用AIDL,你必须创建一个.aidl文档,其定义了编程接口。Android SDK工具包使用这个文件来生成一个抽象类,并实现了那个接口来处理IPC。你可以在你的服务中扩展使用。

注解: 大多数应用程序不应该使用AIDL来创建一个绑定服务,因为它可能要求能够支持多线程,其结果将会是很复杂的实现。例如,AIDL不适用于大多数应用程序,这篇文档并不讨论怎样使用。如果你确定你需要AIDL,请见AIDL文档。

扩展binder类-Extending the Binder class


如果你的服务只被本地应用程序所使用,并且不需要在多个进程间工作,那么你可以实现你自己的Binder类,让你的客户可以直接接入方法中的公共方法。

注解: 这个方法只有在客户和服务在同一个应用程序和进程中时才可行,这种情况很常见。例如,这个方法对于一个音乐应用程序将会非常有用,它需要绑定一个活动到它自己的服务用来以后台播放音乐。

以下为如何设定这个binder类:

1.在你的服务中,创建一个Binder类的实例,实现以下功能之一:

  • 包含客户可以调用的公共方法
  • 返回当前Service的实例,其包含了用户可以访问的公共方法
  • 或返回这个服务包含的另一个类,并含有客户可以访问的公共方法

2.从onBind())回调函数返回这个Binder的实例。

3.在客户端,从onServiceConnected())回调方法接收这个Binder,并用提供的方法来调用绑定服务。

注解:服务和客户端必须在同一个应用程序中的原因是客户端可以计算返回的对象并恰当的调用其APIs。服务和客户端也必须在同一个线程的原因是这种技术不能执行线程间操作。

例如,以下为一个为客户端提供了通过Binder实现接入服务中方法的服务范例:

 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
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random(); /* *
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
* /
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
} @Override
public IBinder onBind(Intent intent) {
return mBinder;
} /* * method for clients * /
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}

getService()方法来取得当前的getRandomNumber()

以下为,当一个按钮被点击时,一个绑定到getRandomNumber()方法:

 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
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
} @Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
} @Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
} /* * Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) * /
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
} /* * Defines callbacks for service binding, passed to bindService() * /
private ServiceConnection mConnection = new ServiceConnection() { @Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
} @Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}

以上范例中展示了客户端怎样通过使用一个ServiceConnection的实现和onServiceConnected())回调方法绑定到一个服务。后续章节中提供了更多的关于绑定到服务流程的信息。

更多实例代码,请见ApiDemosLocalService.javaLocalServiceActivities.java类。

使用一个消息传递器-Using a Messenger


对比AIDL

当你需要执行IPC时,为你的接口使用一个Messenger比用AIDL实现简单,因为[Messenger]把所有对此服务的调用都排在一个队伍中,而单纯的AIDL接口不断的各服务发送请求,这样就要求此服务具备处理多线程的能力。

对于大多数应用程序,服务并不需要执行多线程,所以,使用一个Messenger允许服务在同一时间只处理一个调用。如果处理多线程对你的服务很重要,那么,你应该使用AIDL来定义你的接口。

如果你需要你的服务能够与远程进程通信,那么你可以使用一个Messenger为你的服务提供接口。这个方法允许你执行进程间通信(IPC)而不需要使用AIDL。

以下为怎么样使用Messenger的总结:

通过这种方法,在服务端没有客户端能调用的“方法”。而是,客户传递“消息”(Message对象),同时服务在其Handler中接收。

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

 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
public class MessengerService extends Service {
/* * Command to the service to display a message * /
static final int MSG_SAY_HELLO = 1; /* *
* Handler of incoming messages from clients.
* /
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);
}
}
} /* *
* Target we publish for clients to send messages to IncomingHandler.
* /
final Messenger mMessenger = new Messenger(new IncomingHandler()); /* *
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
* /
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}

注意Handler中的handleMessage())方法,服务在其中接收进入的Message,基于what成员,决定下一步的做法。

客户端所需做的只是基于服务返回的IBinder创建一个Messenger并使用send())方法发送一条消息。例如,以下为一个简单的活动范例,其绑定到了服务,并向服务传递了MSG_SAY_HELLO消息:

 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
public class ActivityMessenger extends Activity {
/* * Messenger for communicating with the service. * /
Messenger mService = null; /* * Flag indicating whether we have called bind on the service. * /
boolean mBound; /* *
* Class for interacting with the main interface of the service.
* /
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
} public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
}; public void sayHello(View v) {
if (mBound) return;
// Create and send a message to the service, using a supported 'what' value
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到服务,其中包括了客户端的Messenger,其存放在在send())中replyTo参数之中。

你可以在MessengerService.java(服务端)和MessengerServiceActivities.java(客户端)提供双向通信,具体请见相关范例程序。

绑定到服务-Binding to a Service


应用程序组件(客户端)可以通过调用bindService())方法绑定到一个服务。Android系统将调用服务的onBInd())方法,返回一个IBinder用来与服务交互。

这个绑定是不同步的。bindService())立即返回,但并没有返回[IBinder]给用户。为了接收IBinder,客户端必须创建一个ServiceConnection的实例,并传递给bindService())。ServiceConnection包含了一个回调方法,系统调用它来传递IBinder

注解:只有活动(activities),服务(services),和内容提供者(content providers)可以绑定到一个服务——你'_不能* 从一个广播接收器(broadcast receiver)绑定到一个服务。

所以,从你的客户端绑定到服务,你必须:

1.实现ServiceConnection

  • 你的实现必须复写两个回调方法:

  • onServiceConnected())

    • 系统调用这个方法来传递由服务端的onBind())方法返回的IBinder
  • onServiceDisconnected())

    • 当与服务的连接发生了不可预期的丢失,Android系统调用这个方法,例如,服务发生了冲突或被杀死。在服务被解绑时并不会调用这个方法。

2.调用bindService()),传递ServiceConnection的实现。

3.当系统调用onServiceConnected())回调方法时,你可以开始使用由接口定义的方法调用服务。

4.与服务解决连接,调用unbindService())。

  • 当客户端被摧毁,它将会从服务解绑,但你应该始终在结果与服务的交互或当你的活动暂停时解绑,这样系统可以在不被使用时关闭。(后续文章将更加具体讨论适当的绑定和解绑时间。)

例如,以下的范例小片断,使客户端连接到使用前文讨论的#扩展binder类-Extending the Binder class|扩展binder类-Extending the Binder class方法创建的服务,所以,唯一需要做的就是把返回的IBinder传递给LocalService的实例:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
// Because we have bound to an explicit
// service that is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
} // Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};

使用这个ServiceConnection,客户端可以通过把其传递到bindService())来绑定到一个服务。如:

1
2
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表示什么都没有。

补充注释-Additional Notes


以下为绑定到一个服务时的几个重要的注意事项:

  • 你应该始终捕获DeadObjectException异常,这个异常在连接断开时被抛出。在这远程方法唯一会抛出的异常。

  • 对象为进程引用数。

  • 在客户端生命周期的创建和摧毁时刻,你应该成对使用绑定和解绑。例如:

    • 当你的活动可见时,如果你需要做的只是与服务交互,你应该在onStart())中绑定,在onStop())中解绑。
    • 如果你希望即使活动在后台停止时也接收响应,那么你可以在onCreate())方法中绑定,并在onDestroy())方法中解绑。需要注意的是,这个实现使得你的活动需要在所有服务运行的时候占用该服务(即使在后台运行时)。所以,如果服务存在于另一个进程中,那么你增加了进程的比重,因此,系统也将更有可能性将其杀死。

注解: 通常情况下,你不应该在活动的onResume())和onPause())方法中绑定与解绑,因为这些回调方法发生在生命周期转换的时候,你应该把此期间的发生的进程减小到最少。同样,如果有多个活动绑定到同一个服务,两个活动间必然存在着转换,当当前的活动在下一个活动绑定(在resume期间)之前,进行解绑(在pause期间),服务可能会被摧毁或重新创建。(这个活动转换中,关于活动如何协调其生命的情况,在Activities)文档中做了详细的表述。

更多的关于怎么样绑定服务的范例代码,请见ApiDemos中的RemoteService.java类。

管理绑定服务的生命周期-Managing the Lifecycle of a Bound Service


图1. 服务的生命周期被启动,并允许绑定。

当一个服务从所有客户端解绑,Android系统将将其摧毁(除非这个活动被onStartCommand())启动)。这样,如果你的服务只是纯粹一个绑定服务,那么你不需要自己管理其生命周期——Android系统将根据其是否被绑定到客户端,来管理其生命周期。

然而,如果你选择实现int, int) onStartCommand())回调方法,那么你必须明确的停止这个服务,因为服务现在被认为已被启动。在这种情况下,服务将一些运行,直到它被自身的stopSelf())方法停止,或其它活动组件调用stopService())方法,而不考虑其是否绑定到客户端。

此外,如果你的服务被启动,并接收绑定,那么当系统调用了onUnbind())方法,你可以有选择性的返回true,如果你希望下一次一个客户端绑定到服务时接收一个onRebind())方法调用(而不是接收一个onBind())调用)。onRebind())返回一个空值,但客户端依然可以接收到onServiceConnected())回调方法中的IBinder。图1展示了这种服务生命周期的逻辑。

更多关于一个启动的服务的信息,请见Services文档。

Android应用中创建绑定服务使得用户可以与服务交互的更多相关文章

  1. Android Studio中创建Kotlin For Android项目

    Kotlin俗称Android中的Swift,它是Jetbrains公司开发的基于JVM的一门语言,JetBrains公司可能大家并不熟悉,不过相信IntelliJ IDE大家一定知道,Android ...

  2. Android实验一(在Android Studio中创建项目和模拟器)

    北京电子科技学院(BESTI) 实     验    报     告 课程:移动平台开发         班级:1592 姓名:苏泽楠 学号:20159207 成绩:             指导教师 ...

  3. 在Android Studio中创建项目和模拟器

    北京电子科技学院 实      验      报      告 课程:移动平台应用开发实践  班级:201592  姓名:杨凤  学号:20159213 成绩:___________  指导老师:娄嘉 ...

  4. Android studio 中创建AIDL Service

      1.概述  AIDL在android系统中的作用 AIDL,Android Interface definition language的缩写,它是一种android内部进程通信接口的描写叙述语言, ...

  5. Android Studio中创建java项目

    1.创建普通的android工程 2.创建一个module 3.module类型选择java library 4.填写libary和class的名字 5.生成的工程如图所示 6.然后点击Run --- ...

  6. Android: Service中创建窗口显示

    WindowManager.LayoutParams: int TYPE_SYSTEM_ALERT  Window type: system window, such as low power ale ...

  7. 在Android Studio中创建(或添加)第一个Hello World应用程序

    下面我们将使用Android Studio创建第一个简单的Hello World应用程序. 1.打开Android Studio,加载画面如下图所示:   2.选择”Start a new Andro ...

  8. 如何在Android Studio中创建jniLib和asset文件夹 2

    1.创建asset文件夹 如图进行操作 2.创建jniLib文件夹 —打开app下面的gradle文件(不是project的gradle) —在gradle文件的Android标签里面添加 sourc ...

  9. 在android程序中加入widget(窗口小部件)并与之交互的关键代码

    摘要: widget(窗口小部件)可以增强应用程序的交互性, 是很多应用中都会用到的功能,本文不求大而全,但是会给出程序与widget交互的关键代码 正文: 其实widget是嵌入(embedded) ...

随机推荐

  1. Spring学习笔记一:基础概念

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6774310.html    一:Spring是什么 Spring的主要作用是作为对象的容器. 传统编程中,我们 ...

  2. unique-paths I &II 路径数,动态规划

    A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). The ...

  3. Linux下设置oracle环境变量

    Linux设置Oracle环境变量 方法一:直接运行export命令定义变量,该变量只在当前的shell(BASH)或其子shell(BASH)下是有效的,shell关闭了,变量也就失效了,再打开新s ...

  4. oracle 替换字符串中指定位置内容

      1.情景展示 返回服务器的身份证号需要进行加密:只保留前四位和后四位,中间使用*代替,如何实现? 2.解决方案 第一步:查看该表的身份证号的长度有几种类型: 第二步:编写sql 错误方式: 长度为 ...

  5. mysql匹配模式

    “_”:匹配任何单个字符“%”:匹配任意数目字符(包括零字符)“[abc]”:匹配“a”.“b”或“c”.为了命名字符的范围,使用一个“-”.“[a-z]”:匹配任何字母“[0-9]”:匹配任何数字“ ...

  6. django之异常错误2(Error was: No module named sqlite3.base)

    具体错误代码为: C:\djangoweb\helloworld>manage.py syncdbTraceback (most recent call last):  File "C ...

  7. linux上源码编译安装mysql-5.6.28

    在 linux 上编译安装 mysql-.tar.gz http://www.mysql.com/ mysql下载地址: http://www.mysql.com/downloads/mysql/#d ...

  8. Qt中运行后台线程不阻塞UI线程的方案

    有一个想法,一个客户端,有GUI界面的同时也要向网络服务器发送本地采集的数据,通过网络发送数据的接口是同步阻塞的,需要等待服务器响应数据. 如果不采用后台线程的方案,用主UI线程关联一个定时器QTim ...

  9. docker-compose 管理多个docker容器实例

    Compose 安装 运行此命令下载最新版本的Docker Compose $ curl -L https://github.com/docker/compose/releases/download/ ...

  10. 【Oracle】Oracle基本数据类型总结

    ORACLE基本数据类型(亦叫内置数据类型 built-in datatypes)可以按类型分为:字符串类型.数字类型.日期类型.LOB类型.LONG RAW& RAW类型.ROWID &am ...