Service探究系列之启动方式

在上一篇中我们介绍了Android Service的基础概念和如何创建及启动Service。在这一篇中我们来详细探索一下Service的两种启动方式:startService和bindService。

一、startService

当你的服务不需要与页面组件进行交互或只有提示性交互时,一般会使用startService启动服务,让其独立于组件而运行,如在后台定位用户位置信息。

通过 startService() 方法启动服务,其生命周期如下图所示:

start_lifecycle

下面分别介绍其生命周期方法:

onCreate() :服务第一次被创建时调用,主要用来处理一些服务所必须的初始化操作。注意,这里不要做任何耗时或繁重的操作。

onStartCommand() :通过startService启动服务,在服务创建后会即可调用该方法,也是服务处理任务的主要方法,其一般实现如下:

1
2
3
4
5
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
workThread.doSomething(intent);
return START_NOT_STICKY;
}

可以看到该方法需要返回一个 int 类型的值,该值用于描述服务被系统终止后,希望系统后续如何处理该服务。只能取以下三个常量中的一个:

  1. START_NO_STICKY:如果系统在 onStartCommand() 返回值后终止了服务,除非有待传递的挂起Intent,否则系统不会重现创建并启动服务。这是最安全的选项,可以避免服务及未完成任务状态的错乱,在合适的时机再次启动服务来处理待处理的任务。此模式适用于一些需要获取结果数据,但允许在内存紧张时停止服务且后续不需要再次恢复的任务,如从服务端请求数据。
  2. START_STICKY:如果系统在 onStartCommand() 返回值后终止了服务,则系统会重新创建并调用 onStartCommand() 方法,但不会重新传递最后一个Intent,因此该模式下 onStartCommond() 方法中的参数 intent 有可能为null,必须进行判空处理。此模式适用于不执行命令、但无限期运行的任务,如媒体播放器。
  3. START_REDELIVER_INTENT:如果系统在 onStartCommand() 返回值后终止了服务,则系统会重新创建服务,并将最后一个 Intent 传递给 onStartCommand() 。所有挂起的 Intent 均会依次传递。此模式适用于主动执行,且应立即恢复的任务,如文件下载或上传。

onStartCommand() 接收三个参数:第一个 intent 就是通过startService传递进来的Intent,可以携带一些数据;第二个 flags 为标识此次请求服务的额外数据,该标识由系统处理并传递,通常请求下为0,当服务被系统终止后再次启动服务时,系统会根据 onStartCommand() 方法的返回值传递该标识, 取值为 START_FLAG_REDELIVERYSTART_FLAG_RETRY,一般我们不会用到这个参数;第三个 startId 是一个标识本次请求的唯一Integer值,主要用于停止服务标识(stopSelfResult(int)),如果调用stopSelfResult的 startId 和最后一个调用服务分配的startId不同,则不会停止服务。

停止服务有两种方式:一是在组件中调用 stopService() 停止服务;二是在 Service 内部调用 stopSelf() 停止服务;

注意:再次提醒下,Service 的所有生命周期方法默认都是运行在启动服务的进程中的主线程里的,如果有耗时或阻塞性操作,需要在 onStartCommand 方法中另起工作线程处理任务,也可以使用 IntentService 代替

onDestroy:服务销毁时调用,一般在这里需要将service中的资源做释放处理。

通过 startService() 启动的服务可以通过 Toast状态栏通知 来通知用户任务处理的状态,也可以通过广播的方式将执行结果传递给接受者。

二、bindService

如果你需要在服务启动后,与服务进行交互或IPC,应该以bindService()来启动服务,并实现ServiceonBind()方法,返回一个IBinder实例来进行与服务的交互或IPC。以下为一个简单的onBind()实现示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "Hello service onBind, intent:" + (intent != null ? intent.toString() : "null"));
return new HelloBinder();
}

public void sayHello(String name) {
Toast.makeText(getApplicationContext(), "Hello " + name + " from HelloService", Toast.LENGTH_SHORT).show();
}

public class HelloBinder extends Binder {

public HelloService getService() {
return HelloService.this;
}
}

如上所示onBind()方法需要返回一个IBinder类型的参数,IBinder是Android中进行IPC重要的类,很多地方都有其身影。这里我们只是简单地继承了IBinder的子类Binder,并提供了一个获取当前服务实例的方法。关于IBinder在不同服用中推荐的不同实现方式我们放到后面再详细展开说明,这里先关注以绑定启动服务的方法或步骤。

实现了onBind()方法后,我们就可以在组件中以绑定的方式来启动服务了。以绑定的方式启动服务比直接启动服务要稍微复杂些,除了需要封装Intent来显式声明服务外,还必须提供ServiceConnection实例,后者会监控与服务的连接,并能从中获取onBind()返回的IBinder实例,以便组件与服务进行交互或IPC。以下为bindService()启动服务的示例:

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

// 创建ServiceConnection实例
private ServiceConnection helloServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
mBound = true;
HelloService.HelloBinder binder = (HelloService.HelloBinder) service;

helloService = binder.getService();
helloService.sayHello("MainActivity");
}

@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected");
mBound = false;
helloService = null;
}
};

// 在Activity中启动服务
private void bindService() {
Intent intent = new Intent(MainActivity.this, HelloService.class);
intent.putExtra("count", count);
bindService(intent, helloServiceConnection, Context.BIND_AUTO_CREATE);
}

@Override
private void onClick(View v){
if(mBound && helloService != null){
helloService.sayHello("CLICK");
}
}

以上即为绑定启动服务的示例,下面我们来一一分析下ServiceConnection的实现和bindService()的参数及绑定启动后Service的生命周期。

ServiceConnection是一个接口,当创建客户端和服务的连接时,Android系统会调用其 onServiceConnected(ComponentName name, IBinder service)方法,并将服务在onBind()方法返回的IBinder作为其方法的第二个参数传递进来,客户端可以使用该参数与绑定的服务进行通信交互。当客户端和服务断开连接时,Android系统会调用其onServiceDisconnected(ComponentName name)方法,通知客户端与服务的连接断开了。

bindService()的第三个参数为绑定服务的选项标识,可能的取值为以下几种:

  1. Context. BIND_AUTO_CREATE:在绑定服务时自动创建服务,是绑定服务时常用的标识,其他的几个不常用;
  2. Context. BIND_NOT_FOREGROUND:不允许服务的进程提升到前台;
  3. Context. BIND_ABOVE_CLIENT:服务的优先级高于客户端,当OOM时会优先销毁application,然后才销毁服务;
  4. Context. BIND_ALLOW_OOM_MANAGEMENT:允许进程持有的服务超出自身的内存管理边界,使服务得以运行;
  5. Context. BIND_WAIVE_PRIORITY:不会超出目标服务进程的计划或内存管理优先级,允许服务像在后台运行的普通应用进程那样被后台LRU列表管理;

以单纯的bindService()启动服务,组件可以通过unbindService()来解除与服务的绑定关系,同一个服务允许多个客户端同时绑定服务,当所有与服务绑定的组件都解绑后,服务也会随之销毁。其生命周期如下所示:

start_lifecycle

同一个服务也允许以startService()bindService()两种方式同时启动,这种情况下服务的声明周期跟两种启动方式都相关,只有所有与服务绑定的组件都解绑了且调用了停止服务,此时服务才会销毁。可以用下图来表示Service可能的生命周期:

start_lifecycle

总结下绑定启动服务的步骤:

  1. 创建ServiceConnection实例,用来监控服务的连接情况。
  2. 创建声明服务的显示Intent,调用bindService()方法来绑定启动服务。
  3. ServiceConnection实例的onServiceConnected()回调方法中接收onBind()返回的IBinder实例,并调用该实例的公共方法获取Service实例或Service其他可供客户端调用的对象实例。
  4. 客户端就可以通过获取到的服务相关实例来与Service进行交互了。

三、IBinder的几种实现方式

绑定服务时onBind()需要返回一个IBinder实例,其实现方式有以下三种方式:继承Binder类、使用Messenger创建IBinder和AIDL。这三种方式有不同的适用场景和实现难度,接下来一一介绍。

1. 继承Binder

如果你的服务只供自身应用使用,且无需进行跨进程工作,则优先推荐使用该方式,使用此方法让客户端访问Service的公共方法或资源。这也是实现IBinder最简单的方式,上面的示例中我们就是使用这种方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

/**
* 提供给客户端调用的公共方法
* @param name
*/
public void sayHello(String name) {
Toast.makeText(getApplicationContext(), "Hello " + name + " from HelloService", Toast.LENGTH_SHORT).show();
}

/**
* 自定义的Binder对象
*/
public class HelloBinder extends Binder {

public HelloService getService() {
return HelloService.this;
}
}

继承Binder的一般使用方式可以总结如下:

  1. 在你的服务中,创建可执行以下某种操作的 Binder 实现类:
    • 包含客户端可调用的公共方法。
    • 返回当前的 Service 实例,该实例中包含客户端可调用的公共方法。
    • 返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法。
  2. onBind() 回调方法返回此 Binder 实例。
  3. 在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务。

使用Messenger

如果你的服务需要进行跨进程通讯(IPC),且无需执行多线程处理,推荐使用Messenger为你提供IBinder实例。Messenger比使用AIDL更简单,其会将请求加入到一个队列中,然后让其按照队列顺序一次执行一个请求,不存在线程不安全的问题(多个进程中的不同线程调用同一个服务)。以下是使用Messenger的示例和大致流程:

创建Service类,内部实现处理消息的Handler和以该Handler实例为参数的Messenger实例,在onBinder()方法中返回Messenger实例创建的IBinder实例。

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

/**
* 远程Service,通过Messenger进行IPC
*/
public class RemoteService extends Service {
private final String TAG = "RemoteService";
public static final int REMOTE_HELLO = 1;
public static final int REGISTER_CLIENT = 2;
/**
* 处理客户端Messenger发送过来消息的Handler
*/
private Handler mHandler;
/**
* Service端的Messenger
*/
private Messenger mServiceMessenger;

@Override
public void onCreate() {
super.onCreate();
mHandler = new RemoteHandler(getApplicationContext());
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
mServiceMessenger = new Messenger(mHandler);
IBinder binder = mServiceMessenger.getBinder();
return binder;
}

@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "remote service onUnbind");
return super.onUnbind(intent);
}

@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "remote service onDestroy");
}

static class RemoteHandler extends Handler{
private Context context;
RemoteHandler(Context applicationContext){
context = applicationContext;
}

@Override
public void handleMessage(Message msg) {
switch (msg.what){
case REMOTE_HELLO:
Toast.makeText(context, "Hello from remote service", Toast.LENGTH_SHORT).show();
break;
default:

break;
}
}
}
}

配置RemoteService单独的进程,清单文件里配置如下:

1
2
3
4
5
6

<service
android:name=".service.RemoteService"
android:description="@string/app_name"
android:exported="true"
android:process="com.summer.remote_service" />

Activity中绑定服务,并通过ServiceConnection获取服务传递过来的IBinder实例,并以此实例构建Messenger,这样客户端就可以通过该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

private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.i(TAG, "onServiceConnected, componentName:" + componentName.toString() + ", iBinder:" + iBinder.toString());
// 以iBinder构建Messenger
messenger = new Messenger(iBinder);
sayHelloByRemoteService();
}

@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.i(TAG, "onServiceDisconnected, componentName:" + componentName.toString());
}
};

/**
* 通过Messenger与RemoteService通讯
*/
private void sayHelloByRemoteService() {
Message msg = Message.obtain(null, RemoteService.REMOTE_HELLO);
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}

至此组件就可以通过Messenger同服务进行通讯了,但上面的示例中只实现了由客户端主动同服务端通讯,而无法由服务端主动向客户端发起通讯。可以通过客户端在Message设置replyTo来实现。以下为完整的服务端和客户端双向通讯的示例代码:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

/**
* 远程Service,通过Messenger进行IPC
*/
public class RemoteService extends Service {
private final String TAG = "RemoteService";
public static final int REMOTE_HELLO = 1;
public static final int REGISTER_CLIENT = 2;
/**
* 处理客户端Messenger发送过来消息的Handler
*/
private Handler mHandler;
/**
* Service端的Messenger
*/
private Messenger mServiceMessenger;
/**
* 接收的客户端的Messenger
*/
private static Messenger clientMessenger;

@Override
public void onCreate() {
super.onCreate();
mHandler = new RemoteHandler(getApplicationContext());
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
mServiceMessenger = new Messenger(mHandler);
IBinder binder = mServiceMessenger.getBinder();
return binder;
}

@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "remote service onUnbind");
return super.onUnbind(intent);
}

@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "remote service onDestroy");
}

static class RemoteHandler extends Handler{
private Context context;
RemoteHandler(Context applicationContext){
context = applicationContext;
}

@Override
public void handleMessage(Message msg) {
switch (msg.what){
case REMOTE_HELLO:
Toast.makeText(context, "Hello from remote service", Toast.LENGTH_SHORT).show();
// 通过接收到的客户端Messenger发消息给客户端
Message message = Message.obtain(null, 0, 110, 0);
try {
clientMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case REGISTER_CLIENT:
// 接收客户端传递过来的Messenger
clientMessenger = msg.replyTo;
break;
default:

break;
}
}
}
}

Activity中的代码

// 处理服务端发送过来的消息的Handler
private Handler clientHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int obj = msg.arg1;
Toast.makeText(getApplicationContext(), obj + "", Toast.LENGTH_SHORT).show();
}
};

private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.i(TAG, "onServiceConnected, componentName:" + componentName.toString() + ", iBinder:" + iBinder.toString());
// 构建与服务端通讯的Messenger
messenger = new Messenger(iBinder);
// 构建客户端的Messenger,通过Message传递过服务端
clientMessenger = new Messenger(clientHandler);

Message obtain = Message.obtain(null, RemoteService.REGISTER_CLIENT);
// 将Messenger传递给服务端
obtain.replyTo = clientMessenger;
try {
messenger.send(obtain);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.i(TAG, "onServiceDisconnected, componentName:" + componentName.toString());
}
};

总结下使用 Messenger 创建 IBinder 实例的大致流程如下:

  1. 在服务内创建处理消息的 Handler ,并以此作为参数构建 Messenger实例;
  2. onBind()方法中返回由 Messager 创建的 IBinder 实例;
  3. 在绑定服务的 ServiceConnectiononConnected()方法中接收服务的 IBinder 实例,并以此为参数构建 Messenger 实例,然后客户端就可以通过此 Messenger 发送消息给服务端,并在服务端的 Handler里处理此消息。
  4. 如果想要服务端主动与客户端交互,需要在客户端中创建自己的 Handler 并以此为参数构建 Messenger,将此 Messenger 作为使用服务端 IBinder 构建的 Messenger发送 MessagereplyTo 参数发送给服务端,服务端使用 replyToMessenger 与客户端进行主动交互。

AIDL

如果你的服务需要进行跨进程通讯,且需要多线程操作,可以使用 AIDL 来创建 IBinder 实例,你需要自己来保证多线程操作时的线程安全。AIDL实现起来比较复杂,后续作为单独的章节来介绍,这里就不展开了。