Earth Guardian

You are not LATE!You are not EARLY!

0%

四大组件 -- Service

基础

概念

Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

选择使用服务还是线程

简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件,但是它仍会在应用的主线程中运行。如需在主线程外部执行工作,不过只是在用户正在与应用交互时才有此需要,则应创建新线程而非服务。

注意:ServiceActivity 都属于 UI 主线程,所以在 Service 中同样需要开新线程来执行耗时操作

两种启动方式

  • 启动服务 startService

组件间无交互:启动服务时,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。例如它可能通过网络下载或上传文件,操作完成后服务会自行停止运行。

  • 绑定服务 bindService

组件间有交互:绑定到服务时,服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 ( IPC )跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

实际使用中,大量会出现两种启动方式同时使用,既希望服务不受影响的在后台运行,又需要达到组件交互的目的。

清单属性

  • android:exported
    这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互。设置为 false,只有同一个应用程序的组件或带有相同用户 ID 的应用程序才能启动或绑定该服务

  • android:enabled
    是否可以被系统实例化,默认为 true,表示服务能被激活,否则不会激活

  • android:process
    是否需要在单独的进程中运行,格式: android:process=”:remote”
    后面表示新的进程名,为包名加上冒号后值,如:AppPackageName:remote

  • android:name
    服务的名称

示例:

1
2
3
<service
android:name=".MyIntentService"
android:exported="false"></service>

启动服务 startService

重写回调 onStartCommand

通过调用 startService() 启动服务时,系统将调用此方法。源码:
public int onStartCommand(Intent intent, int flags, int startId) {...}
其中 startId 为该服务的唯一标记符

返回值在 Service 被意外 Kill 掉后是否重启,代表的含义分别为:

  • START_NOT_STICKY
    则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务
  • START_STICKY
    会重建服务,并调用 onStartCommand(),传递一个空 Intent。如果有挂起 Intent 要启动服务,才会传递这些 Intent,否则为空。适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)
  • START_REDELIVER_INTENT
    会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()
    。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务等
  • START_STICKY_COMPATIBILITY
    START_STICKY 的兼容版本,但不保证服务一定能重启

显示启动和停止服务

显示启动和停止服务都是通过 Intent 来实现,指定具体的服务类名

1
2
3
4
5
6
7
// 启动服务
Intent intent = new Intent(ShowServiceActivity.this, StartService.class);
startService(intent);

// 停止服务
Intent intent = new Intent(ShowServiceActivity.this, StartService.class);
stopService(intent);

启动服务,一般是在服务中新建工作线程做耗时操作,操作完成后结束服务。因为组件间生命周期相互不影响,为了避免浪费系统资源和消耗电池电量,应用必须在耗时工作完成之后停止其服务,所以在服务中直接结束会更合适。源码:

1
2
3
public void stopSelf();
// startId 为 onStartCommnd 中指定的服务 Id
public final boolean stopSelfResult(int startId) {...}

示例分析

启动服务后,在服务中新建线程执行耗时操作,耗时执行完后关闭服务

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
private class MyRunnable implements Runnable {
// 服务的 Id
private int mStartId;

public MyRunnable(int startId){
mStartId = startId;
}

@Override
public void run() {
try {
Log.d(TAG, "run: ");
Thread.sleep(3000);
Log.d(TAG, "run: stopSelfResult.mStartId = " + mStartId);
// 耗时操作之后,结束服务
stopSelfResult(mStartId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: flags = " + flags + ", startId = " + startId);
// 新建线程执行耗时操作,并传递该服务的 Id
Thread thread = new Thread(new MyRunnable(startId));
thread.start();
return super.onStartCommand(intent, flags, startId);
}

对应的 Log 分析:

  • 正常流程,服务自我关闭
1
2
3
4
5
6
7
8
9
10
// 点击启动服务,服务新建线程,执行完耗时操作后主动关闭服务
11:15:14.046 17723-17723/ ShowService:: onClick: button.onClick = Start Service
// Activity 和 Service 都属于主线程,线程 id 相同
11:15:14.063 17723-17723/ StartService:: onCreate:
11:15:14.064 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 1
// 线程 id 可以看出,进入后台线程运行
11:15:14.066 17723-17917/ StartService:: run:
11:15:17.066 17723-17917/ StartService:: run: stopSelfResult.mStartId = 1
11:15:17.068 17723-17723/ StartService:: onDestroy:
11:15:20.358 17723-17723/ ShowService:: onClick: button.onClick = Stop Service
  • 主动点击停止服务
1
2
3
4
5
6
7
8
9
// 点击启动服务,服务新建线程,执行完耗时,点击停止服务,并没有影响后台线程继续执行
11:15:34.975 17723-17723/ ShowService:: onClick: button.onClick = Start Service
11:15:34.998 17723-17723/ StartService:: onCreate:
11:15:34.999 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 1
11:15:35.003 17723-18238/ StartService:: run:
11:15:36.455 17723-17723/ ShowService:: onClick: button.onClick = Stop Service
// 点击停止服务,服务销毁后 ,后台线程继续执行
11:15:36.457 17723-17723/ StartService:: onDestroy:
11:15:38.004 17723-18238/ StartService:: run: stopSelfResult.mStartId = 1
  • 连续启动服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 连续点击三次启动服务,服务会逐个开启线程并放到后台执行,直到所有线程执行完才会停止服务
// 只要服务没有停止,Id 会逐渐增加,一旦停止服务,Id 重新由 1 开始计数
11:18:09.999 17723-17723/ ShowService:: onClick: button.onClick = Start Service
11:18:10.011 17723-17723/ StartService:: onCreate:
11:18:10.013 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 1
// 后台线程 1
11:18:10.025 17723-20539/ StartService:: run:
11:18:10.702 17723-17723/ ShowService:: onClick: button.onClick = Start Service
11:18:10.711 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 2
// 后台线程 2
11:18:10.713 17723-20555/ StartService:: run:
11:18:11.404 17723-17723/ ShowService:: onClick: button.onClick = Start Service
11:18:11.409 17723-17723/ StartService:: onStartCommand: flags = 0, startId = 3
// 后台线程 3
11:18:11.413 17723-20567/ StartService:: run:
// 后台耗时逐个执行完毕
11:18:13.026 17723-20539/ StartService:: run: stopSelfResult.mStartId = 1
11:18:13.713 17723-20555/ StartService:: run: stopSelfResult.mStartId = 2
11:18:14.413 17723-20567/ StartService:: run: stopSelfResult.mStartId = 3
// 所有后台线程执行完后,服务才销毁
11:18:14.415 17723-17723/ StartService:: onDestroy:

生命周期

启动服务的生命周期:
onCreate() --> onStartCommand() --> onDestroy()

绑定服务 bindService

本地服务,扩展 Binder

如果服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder 类,让客户端通过该类直接访问服务中的公共方法。只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。 例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效

1
2
3
4
5
public class LocalBinder extends Binder{
public BindService getService(){
return BindService.this;
}
}

重写回调 onBind

当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。如果不允许绑定服务,则应返回 null。源码:
public IBinder onBind(Intent intent) {...}

多个客户端可同时连接到一个服务,或者一个客户端多次连接同一个服务。不过只有在第一次绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至任何其他绑定的客户端,也就是onBind 只会执行一次

如本地服务中:

1
2
3
4
private final IBinder mBinder = new LocalBinder();
public IBinder onBind(Intent intent) {
return mBinder;
}

建立连接 ServiceConnection

绑定是异步的,bindService() 会立即返回,不会使 IBinder 返回客户端。要接收 IBinder 客户端必须创建一个 ServiceConnection 实例来接收。重写两个回调方法:

  • onServiceConnected()
    系统会调用该方法接收服务的 onBind() 方法返回的 IBinder
  • onServiceDisconnected()
    系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。当客户端取消绑定时,系统不会调用该方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private boolean mBound = false;
private ServiceConnection mBindServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// IBinder 是 Service 中 onBind 的返回值,根据这个得到 Service
mBindService = ((BindService.LocalBinder)service).getService();
// 拿到 Service 后,可以调用公共方法
int randomNumber = mBindService.genRandomNumber());
mBound = true;
}

@Override
public void onServiceDisconnected(ComponentName name) {
mBindService = null;
mBound = false;
}
};

显示绑定和解绑服务

显示启动和停止服务也是通过 Intent 来实现,指定具体的服务类名

1
2
3
4
5
6
7
8
9
// 显示绑定服务
Intent intent = new Intent(ShowServiceActivity.this, StartService.class);
bindService(intent, mBindServiceConnection, Context.BIND_AUTO_CREATE);

// 解绑服务
if(mBound) {
unbindService(mBindServiceConnection);
mBound = false;
}

注意:解绑前必须要判断该服务是否已经绑定,如果服务并没有绑定就直接解绑,会抛出异常

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
10:30: 9105/com.y E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.y, PID: 9105
Caused by: java.lang.IllegalArgumentException:
Service not registered: com.y.ShowService$1@11bddcab
````


常见 `flags` 的含义:
- `BIND_AUTO_CREATE`
创建尚未激活的服务
- `BIND_NOT_FOREGROUND`
系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行

### 生命周期
`onCreate() --> onBind() --> onUnBind() --> onDestroy()`

## 联合使用 `start and bind service`
既可以后台保留服务,又可以通过 `IBinder` 和服务做交互
### 客户端

```java
intent.setClass(ShowService.this, StartAndBindService.class);
// 显示启动服务
startService(intent);
// 显示绑定服务
bindService(intent, mStartAndBindServiceConnection, Context.BIND_AUTO_CREATE);

服务端

同时实现 onStartCommandonBind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 扩展本地 Binder
public class LocalStartAndBinder extends Binder{
public StartAndBindService getService(){
return StartAndBindService.this;
}
}

// 实例化本地 Binder
private final IBinder mBinder = new LocalStartAndBinder();

// 启动服务回调
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}

// 绑定服务回调,并返回本地 Binder
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

Log 显示

1
2
3
4
5
6
7
19:05: 8807/com.yD/StartAndBindService: onCreate: 
19:05: 8807/com.yD/StartAndBindService: onStartCommand: flags = 0, startId = 1
19:05: 8807/com.yD/StartAndBindService: onBind:
19:05: 8807/com.yD/:ShowService:: onServiceConnected:
mStartAndBindService.genRandomString = hechiffecd
19:05: 8807/com.yD/StartAndBindService: onUnbind:
19:05: 8807/com.yD/StartAndBindService: onDestroy:

生命周期对比图

0020_service_lifecycle.png

onUnbind 返回值对生命周期的影响

如果服务没有停止,onUnbind 返回 true 时,下次再次绑定会执行 onRebind,源码:
public void onRebind(Intent intent) {...}

生命周期流程对比图:

0020_service_binding_tree_lifecycle.png

对应 Log 分析:

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
// 点击按钮启动服务
16:39:14.923 20504-20504/ D/:ShowService:: onClick: button.onClick = Start Service
// 创建服务
16:39:14.932 20504-20504/ D/:StartAndBindService: onCreate:
16:39:14.935 20504-20504/ D/:StartAndBindService: onStartCommand: flags = 0, startId = 1
// 点击按钮绑定服务
16:39:15.730 20504-20504/ D/:ShowService:: onClick: button.onClick = Bind Service
// 第一次绑定 onBind
16:39:15.732 20504-20504/ D/:StartAndBindService: onBind:
// 服务连接
16:39:15.734 20504-20504/ D/:ShowService:: onServiceConnected:
mStartAndBindService.genRandomString = bhahahafeh
// 点击按钮解绑服务
16:39:16.715 20504-20504/ D/:ShowService:: onClick: button.onClick = Unbind Service
// onUnbind 返回 true
16:39:16.721 20504-20504/ D/:StartAndBindService: onUnbind:
// 再次点击按钮绑定服务
16:39:17.846 20504-20504/ D/:ShowService:: onClick: button.onClick = Bind Service
// 服务连接
16:39:17.848 20504-20504/ D/:ShowService:: onServiceConnected:
mStartAndBindService.genRandomString = bdghebhdde
// 重新绑定 onRebind
16:39:17.848 20504-20504/ D/:StartAndBindService: onRebind:
// 解绑服务
16:39:19.288 20504-20504/ D/:ShowService:: onClick: button.onClick = Unbind Service

IntentService

Service 因为属于主线程,开启后需要新建线程来执行耗时操作,我们需要去管理 Service 的生命周期以及子线程,所以 Android 提供了 IntentService 来处理这些问题。IntentService 继承了 Service,并实现了 ServiceHandlerHandlerThread,极大的方便了后台线程操作,并且执行完耗时操作后自动关闭本服务。

特点

  • 串行执行
    因为使用的是HandlerMessage 的机制,所以所有任务都是串行执行,逐一执行收到的 Intent
  • 启动方式 startService
    默认属于启动服务,无法组件间交互
  • 带参数
    通过 Intent 传递参数到服务中

重写回调

  • 构造函数
    必须调用 super 并传递类名

  • onHandleIntent
    在这里执行所有的耗时工作

示例:

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 MyIntentService extends IntentService {

/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public MyIntentService() {
super("MyIntentService");
}

/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
}
}

其他生命周期函数如果要重写,必须调用 super.*** 否则影响 IntentService 的功能

示例及 Log 分析

同时开启两次服务,分别处理 ActionBazActionFoo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 同时发送 ActionBaz 和 ActionFoo 两个 Intent,串行处理
14:00:49.965 7776-7776/:ShowService:: onCreate:
// 点击 Button ,启动两次服务
14:00:51.332 7776-7776/:ShowService:: onClick: button.onClick = Start Intent Service
14:00:51.333 7776-7776/:MyIntentService:: startActionBaz:
14:00:51.337 7776-7776/:MyIntentService:: startActionFoo:
// 启动服务
14:00:51.343 7776-7776/:MyIntentService:: onCreate:
// 开启服务 1
14:00:51.344 7776-7776/:MyIntentService:: onStartCommand: startId = 1
14:00:51.347 7776-7866/:MyIntentService:: onHandleIntent: action = com...action.BAZ
// 开启服务 2
14:00:51.348 7776-7776/:MyIntentService:: onStartCommand: startId = 2
// 处理 ActionBaz,工作线程处理耗时工作
14:00:51.348 7776-7866/:MyIntentService:: handleActionBaz: param1 = Action, param2 = Baz
14:00:51.348 7776-7866/:MyIntentService:: doLongRunningWork Start...
14:00:54.349 7776-7866/:MyIntentService:: doLongRunningWork Done!
// 处理 ActionFoo,工作线程处理耗时工作
14:00:54.349 7776-7866/:MyIntentService:: onHandleIntent: action = com...action.FOO
14:00:54.350 7776-7866/:MyIntentService:: handleActionFoo: param1 = Action, param2 = Foo
14:00:54.350 7776-7866/:MyIntentService:: doLongRunningWork Start...
14:00:57.350 7776-7866/:MyIntentService:: doLongRunningWork Done!
// 关闭服务
14:00:57.351 7776-7776/:MyIntentService:: onDestroy:

前台服务

前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知

  • 开启前台服务 startForeground()
    通过 startForeground 来标记服务为前台服务,源码:
    public final void startForeground(int id, Notification notification) {...}
    参数 id 是唯一标识通知的整型数但不能为 0和状态栏的通知 Notification

  • 移除前台服务 stopForeground
    移除不是停止服务,所以移除后,服务还是在运行,源码:
    public final void stopForeground(boolean removeNotification) {...}

示例代码:

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
private void startForegroundService(){
Log.d(TAG, "startForegroundService: ");
CharSequence text = getText(R.string.foreground_service_started);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, ShowService.class), 0);

// Set the info for the views that show in the notification panel.
// 设置通知
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.stat_sample) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.alarm_service_label)) // the label
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when clicked
.build();

// 设置前台服务
startForeground(R.string.foreground_service_started, notification);
}

private void stopForegroundService(){
// 移除前台服务
stopForeground(true);
}

小结

  • 启动服务
    建议使用 IntentService,不用关心生命周期及线程的管理,它的任务是串行执行的,不支持并发。并发还是需要继承 Service 来实现。
  • 绑定服务
    继承 Service,大部分情况绑定服务一般会和启动服务联合使用。

参考文档

  1. https://developer.android.com/guide/components/services.html
  2. https://developer.android.com/guide/components/bound-services.html
  3. http://blog.csdn.net/u013553529/article/details/54754491
  4. http://blog.csdn.net/huutu/article/details/40357481
  5. http://blog.csdn.net/javazejian/article/details/52709857
  6. http://blog.csdn.net/lmj623565791/article/details/47143563