Android-Service

Android-Service

1. Service的状态和种类

Service 分为 StartedBound 两种状态(可共存),以及 ForegroundBackground 两种类型。

1.1 Service的启动和绑定状态

Service 可以通过 startbind 两种方法启动。

  • startService() 后该 Service 即变为 Stared 状态,处于后台运行中,与启动它的 Activity 生命周期无关,除非手动 stopService(Intent) 或 Service 内部调用自己的 stopSelf() 终止,或被 Android 系统回收。
  • bindService() 后该 Service 为 Bound 状态,主要提供 C / S 接口,允许组件与 Service 通信或跨进程通信,生命周期与启动它的宿主绑定,宿主销毁时会自动解除与 Service 的绑定,当 Bound 状态的 Service 没有被任何宿主绑定时,就会销毁该 Service。

Service 可以同时处于 Started 状态和 Bound 状态,也即 Service 通过一种方式启动后,仍然可以被其他宿主通过另一种方式连接(但不会重新启动)。当 Service 同时处于 StartedBound 状态时,Service 的生命周期同时受到两种状态的影响,仅当两种状态都断开时,Service 才会销毁,也即:必须被 stopService() 并且被 unbindService() 后,Service 才会销毁。

1.2 Service的前台和后台

Service 可以在后台执行长时间而无界面的操作,且默认处于主进程的主线程中。

  • BackGround Service:用户不可感知的后台操作,如监控、轮询拉取等。

    • 默认情况下,不论通过 start 还是 bind 启动的 Service 都是后台 Service。后台 Service 对用户不可感知,但内存优先级较低,在内存不足时会被系统杀死(根据 Service 的参数可以指定是否需要在内存空闲后重新启动),最大允许 200 秒(ActiveServices#SERVICE_BACKGROUND_TIMEOUT)卡顿时间,超出则会抛出 ANR。
  • Foreground Service:执行一些用户可感知的操作,如 Audio 播放。

    • 可以通过调用 Service#startForeground(int id, Notification notification) 将一个 Service 设为前台 Service,其中 Notification 不能为 null 否则会抛出异常,因此 ForegroundService 强制要求在通知栏显示一个服务图标,但内存优先级高,即使内存不足也不会被系统杀死,最大允许 20 秒(ActiveServices#SERVICE_TIMEOUT)卡顿时间,超出则会抛出 ANR。

    • 在 API 26 (Android 8.0) 以上可以直接通过 startForegroundService() 启动一个 ForegroundService,但在 ForegroundService 启动后需要在 10 秒(ActiveServices#SERVICE_START_FOREGROUND_TIMEOUT)内调用 startForeground(),否则会抛出 ANR。


2. Service生命周期

所有 Service 都必须在 Manifest 内声明注册:

1
2
3
4
5
6
7
8
9
10
11
<service
android:name=".DemoService"
android:exported="true"
android:process=":demo"
android:isolatedProcess="false"
android:enabled="true"
>
<intent-filter>
<action android:name="service.DemoService"/>
</intent-filter>
</service>
  • exported: 是否能被其他 App 隐式启动(通过指定 Intent 的 ActionName 启动),当没有指定 Intent-Filter 时默认值为 false,指定了 Intent-Filter 时默认值为 true

  • process: 指定进程,默认情况下 Service 运行在主进程的主线程中,可以通过指定 process 属性分配独立进程。

  • isolatedProcess: 是否运行在一个 没有任何权限 的特殊进程中,通常用于一些沙盒任务。

  • enabled: 是否启用这个 Service (系统是否能实例化这个 Service),默认为 true,当设置为 false 时,相当于在 Manifest 中移除该 Service 的标签。

    • 关于 enabled 属性有个很常见的问题就是:如果想要不启用这个 Service,为何要在 Manifest 中声明又禁用呢?直接不写这个 Service 或者不在 Manifest 中声明不就行了吗?实际上这个属性更多用在外部依赖上,例如项目依赖了一个 Module,但是又想禁用 Module 中的某个 Service,则可以在项目 Manifest 中禁用该 Service,然后 Manifest 合并后就会禁用该 Service,理论上四大组件都可以通过这个方式禁用。

2.1 Start方式启动Service

以 start 方式启动 Service:

1
2
3
Intent intent = new Intent(context, DemoService.class);
startService(intent);
stopService(intent);

需要注意:

  • Service#stopSelf()Service#stopSelfResult(int startId) 的效果等同于 Context#stopService(Intent)
  • 仅被 Start 启动而没有被任何 Client Bind 的后台 Service,将在 App 进入后台 1 分钟后被 Kill。

2.2 Bind方式启动Service

如果通过 bind 方式绑定 Service,在 Service 中返回 IBinder 类的实例,则 Client 就能通过 Binder 的方式与 Service 通信。

虽然默认情况下 Service 运行在主进程的主线程当中,但 Service 本身是支持跨进程的,所以 Android 选择了 Binder 作为通信方式。

2.3 Service生命周期

Service 中常见的生命周期回调如下:

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
public class DemoService extends Service {
/**
* 只有当 Service 第一次被启动的时候才回调。
*
* Service 生命周期中,不论如何 onCreate() 都只会回调一次。
*/
@Override
public void onCreate() {
super.onCreate();
}

/**
* Service 被调用 Start 时回调。
* Service 运行过程中,被多次 Start 时会多次回调。
* <p/>
* @param intent 正常 Start 启动 Service 时,Intent 即为 Start 时传入的 Intent。
* 当 Service 被系统杀死后,如果重新启动,则传入的 intent 与该方法的返回值有关。
* @param flags 该 Service 启动时的参数,通常用于判断该 Service 被 Start 的原因。
* - 0: 表示 Service 是正常启动;
* - START_FLAG_REDELIVERY: 表示 Service 是由于被系统杀死,并在内存充足后被系统重新启动。
* 仅当该方法返回值为 START_REDELIVER_INTENT 时生效。
* - START_FLAG_RETRY: 表示 Service 是由于该方法没有正常返回,系统重试。
* @param startId 表明一个启动 ID,通常与 stopSelfResult(int startId) 搭配使用,
在 Service 重启或重试后,避免早前的 stopSelfResult() 请求将后来启动的 Service 停止。
* 例如:onStartCommand(intent, flags, 5) 启动的 Service,将无法被 stopSelfResult(4) 停止。
* <p/>
* @return 有三种典型取值,代表了 Service 被 Start 后,如果被系统杀死,后续的行为。
* - START_STICKY: 表示内存充足后需要重新启动,但传入的 intent == null,除非有 Intent 队列,例如 PendingIntent。
* - START_NOT_STICKY: 表示内存充足后也不重新启动。
* - START_REDELIVER_INTENT: 表示内存充足后需要重新启动,并且把上一次 Start 时使用的 Intent 再重新传入。
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}

/**
* Service 被首次 Bind 时回调,被 Start 启动时不会回调。
*
* Service 生命周期中,不论如何 onBind() 都只会回调一次。
*
* Service 在被销毁前,生命周期中只会在第一次被 Client 绑定时回调 onBind(),
* 之后不论 Client 如何解绑并重新绑定,只要 Service 没有被销毁重启,都不会再次回调 onBind()。
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

/**
* Service 被重新 Bind 时回调,仅当 onUnbind() 返回 true 时生效。
*
* 仅当:
* - Service 从 Bound 的状态下被所有 Client Unbind,
* - 并且 onUnbind() 返回 true,
* - 并且 Service 未被销毁(即同时处于 Started 状态),
* 则下一次被 Bind 时才会回调 onRebind()。
*/
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
}

/**
* Service 被所有 Client Unbind,或者被最后一个 Context.BIND_AUTO_CREATE 模式的 Client Unbind 后回调。
*
* 需要注意:
* - 如果 Service 被多个 Client Bind,只要所有 Context.BIND_AUTO_CREATE 模式的 Client Unbind 就会回调 onUnbind(),
* 此时其他模式的 Client 就会收到 ServiceConnection#onServiceDisconnected() 回调。
* - 否则当所有 Client Unbind 后才会触发该回调,且都不会收到 ServiceConnection#onServiceDisconnected() 回调。
*
* 当首次从 Bound 状态下被所有以 Context.BIND_AUTO_CREATE 模式 Bind 的 Client Unbind 时会回调,
* 之后被重新 Bind 和 Unbind,是否回调依赖上一次 onUnbind() 的返回值:
* - 如果上一次返回 false,则下一次被 Client Bind 时不会回调 onRebind(),且被所有 Client Unbind 后也不会回调 onUnbind()。
* - 如果上一次返回 true,则下一次被 Client Bind 时会回调 onRebind(),且被所有 Clinet Unbind 后会也回调 onUnbind()。
*/
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}

/**
* Service 真正被销毁时回调。
*
* Service 生命周期中,不论如何 onDestroy() 都只会回调一次。
*/
@Override
public void onDestroy() {
super.onDestroy();
}
}

同时处于 StartedBound 状态下的 Service,在被 Stop 并且 onUnbind() 后才会销毁。需要注意的是,Service 的 onUnbind() 有两种触发方式,只要满足其中任意一条就会触发 onUnbind() 回调:

  • 所有 Context.BIND_AUTO_CREATE 模式 Bind 的 Client 均已 Unbind。

    此时其他模式 Bind 的 Client 会收到 ServiceConnection#onServiceDisconnected() 回调,视为其他 Client 异常断开。

  • 所有 Client 都不是 Context.BIND_AUTO_CREATE 模式,且都已 Unbind。

    此时所有 Client 都不会收到 ServiceConnection#onServiceDisconnected() 回调,视为正常断开。

一个 Service 不论是否已经启动,都可以被多次 Start、Stop 和 Bind,Start 和 Stop 的次数不必须匹配,但 Unbind 的次数必须匹配 Bind 的次数,如果 Client 与 Service 不存在绑定关系,则调用 Unbind 会抛出异常。


3. Client与Service通信

如果 Client 需要与 Service 通信,则必须通过 bind 方式与目标 Service 绑定连接。

3.1 通过Binder持有Service

Client 直接持有 Service 实例对象通信,原理是:

  • 利用 Binder 跨进程通信的方式,直接在 Client 中拿到 Service 的 Binder 对象。
  • 然后从 ServiceBinder 中获取 Service 实例对象,相当于把 Service 的实例对象跨进程传到了 Client 进程中。
  • Client 持有了 Service 实例对象,即可直接操作 Service。
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
/**
* 直接操作 Binder 对象的方式。
*/
public class DemoService extends Service {

public class DemoBinder extends Binder {
// 通过 Binder 返回 Service 实例,使得外部可以持有该 Service 对象并与之通信
public DemoService getService() {
return DemoService.this;
}
}

@Nullable
@Override
public IBinder onBind(Intent intent) { return new DemoBinder(); }

// 可以由外部调用的方法
public void work() { }
}

public class DemoActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
bindService(new Intent(this, DemoService.class), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
DemoBinder demoBinder = (DemoBinder) service;
// 持有 DemoService 的实例对象,并调用 Service 的公有方法。
demoBinder.getService().work();
}
@Override
public void onServiceDisconnected(ComponentName name) { /* 仅当异常 Unbind 时才回调 */ }
}, Context.BIND_AUTO_CREATE);
}
}

3.2 通过Messenger发送消息

Client 和 Service 通过互相持有对方的 Messenger 通信,原理是:

  • Client 和 Service 各自通过 Handler 构造各自的 Messenger,则各自的 Messenger 都可以通过 Handler 接收消息。
  • Service 在 onBind() 中返回 serviceMessenger.getBinder(),相当于返回 Service 进程的 Binder。
  • 然后 Client 在成功连接 Service 后,通过 Service 返回的 Binder 构造并持有 Service 的 Messenger,也就相当于能向 Service 的 Handler 发送消息。
  • 此时 Service 还不能向 Client 发送消息,所以 Client 需要首先向 Service 发送一条消息,在消息中指定 replyTo 为 Client 自己的 messenger。
  • 则 Service 收到这条消息后,就能通过消息的 replyTo 取出并持有 Client 的 Messenger,也就相当于能向 Client 的 Handler 发送消息。
  • 此时 Client 和 Service 都互相持有了对方的 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
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
/**
* 通过 Messenger 发送消息。
*/
public class DemoService extends Service {
public static final int MSG_ON_BIND = 0;

// Service 自己的 Handler,用于处理来自 Client 的消息。
private final Handler serviceHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_ON_BIND: {
// 通过接收 Client 发送过来的消息,从消息中获取 Client 的 Messenger。
clientMessenger = msg.replyTo;
break;
}
default: break;
}
}
};
// Client 的 Messenger,用于向 Client 发送消息。
private Messenger clientMessenger;
// Service 自己的 Messenger,用于接收来自 Client 的消息。
private final Messenger serviceMessenger = new Messenger(serviceHandler);

@Nullable
@Override
public IBinder onBind(Intent intent) {
// 返回 Service 的 Messenger,持有方就能向 Service 发送消息。
return serviceMessenger.getBinder();
}

/**
* Service 通过 Client 的 Messenger 向 Client 发送消息通信。
*/
private void sendToClient() throws RemoteException {
if (clientMessenger != null) {
clientMessenger.send(Message.obtain());
}
}
}

public class DemoActivity extends AppCompatActivity {
// Client 自己的 Handler,用于处理来自 Service 的消息。
private final Handler clientHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
// 处理来自 Service 发送的消息
}
};
// Client 自己的 Messenger,用于接收来自 Service 的消息。
private final Messenger clientMessenger = new Messenger(clientHandler);
// Service 的 Messenger,用于向 Service 发送消息。
private Messenger serviceMessenger;

@Override
protected void onCreate(Bundle savedInstanceState) {
bindService(new Intent(this, DemoService.class), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 通过 Service 的 Binder 来构造 Service 的 Messenger。
serviceMessenger = new Messenger(service);
// 先向 Service 发送一条消息,通过 Msg.replyTo 把 Client 自己的 Messenger 传过去,
// 这样 Service 才能拿到 Client 的 Messenger,才能向 Client 发送消息。
Message onBindMsg = Message.obtain();
onBindMsg.what = DemoService.MSG_ON_BIND;
onBindMsg.replyTo = clientMessenger;
serviceMessenger.send(onBindMsg);
}
@Override
public void onServiceDisconnected(ComponentName name) { /* 仅当异常 Unbind 时才回调 */ }
}, Context.BIND_AUTO_CREATE);
}

/**
* Client 通过 Service 的 Messenger 向 Service 发送消息通信。
*/
private void sendOnBindToService() throws RemoteException {
if (serviceMessenger != null) {
serviceMessenger.send(Message.obtain());
}
}
}

3.3 其他方式

除了使用 Binder 方式以外,Activity 与 Service 之间的通信还能通过 BroadCastReceiver 以及 EventBus 的方式实现。


4. 高版本Service的限制

从 API 21 (Android 5.0) 开始,Google 全面禁止了隐式启动 Service,仅允许显式启动 Service,不论目标 Service 是否处于同一个 App 内,都需要指定目标 Service 所在 App 的包名以及该 Service 的全路径。隐式启动 Service 将会抛出异常,该限制主要是为了避免 App 恶意唤起后台服务:

1
Service Intent must be explicit

隐式启动 Service 是指仅通过 ActionName 启动 Service,即使可能存在多个 Service 的 ActionName 重复。API 21 之后,必须使用显式启动,否则会抛出异常,

从 API 26 (Android 8) 开始,Google 针对资源控制限制了后台服务,处于后台的 App 将不允许后台 Service 再直接通过 startService() 方式启动另一个后台 Service。此前 Service 需要启动后再调用 Service#startForeground() 才能将 Service 设为前台服务,而不论通过 Start 还是 Bind 启动的 Service 默认都作为后台服务,因此 API 26 提供了一个新的 Api startForgroundService() 来直接启动一个默认前台服务。

  • 由于指定了启动的是前台服务,因此其会在内部调用 ActiveServices.setServiceForegroundInnerLocked() Post 了一个延时消息,必须在 10s 内调用 Service#startForeground(),其内部 Remove 了该延时消息,否则该延时消息就会抛出 ANR。
  • 并且如果 Service#startForeground(int id, Notification notification)id == 0notification == null 都会抛出 Null Notification 异常,因此启动前台服务一定会创建一个通知提醒用户。

后台 Service 不能直接 Start 另一个后台 Service,这条规则受到 App 前后台策略的影响:

  • 处于前台的 App 可以自由启动前台或后台 Service。
  • 进入后台的 App 在一段时间窗内(数分钟)仍然可以自由启动创建前台或后台 Service,在时间窗结束后系统将会停止 App 的后台 Service。

但 Android 对 App 是否前台的判断,并非是简单地判断是否存在前台 Activity。Google 文档中指出,一个 App 如果满足以下几点,则被视为处于前台:

  • 具有可见 Activity
  • 具有前台 Service
  • 被另一个前台 App 关联(被另一个前台应用 Bind Service 或使用了 ContentProvider),例如:
    • IME
    • 壁纸 Service
    • 通知监听
    • 语音或文本服务

4.1 Service保活

随着高版本 Android 对后台策略逐步收紧,理论上已经没有 100% 保活的方式,API 26 以上 Service 保活的思路主要就是两个 Service 互相启动并绑定;

  • AService:
    • 首先由某个前台 Activity Start 一个 AService。
    • AService 的 onStartCommand() 返回 START_STICKY
    • AService Start 和 Bind BService。
    • AService 在 onDestroy() 中再次 Start 和 Bind BService。
  • BService:
    • BService 的 onStartCommand() 同样返回 START_STICKY
    • BService 被启动后,Start 和 Bind AService。
    • BService 在 onDestroy() 中再次 Start 和 Bind AService。

AService 和 BService 都需要在 onStartCommand() 中返回 START_STICKY,这样其中一个 Service 由于内存被系统杀死后,仍有机会被重启并继续拉活对方。

由于后台 Service 如果仅被 Start 而没有被 Bind,会在 App 置入后台 1 分钟后被 Kill,所以 Service 互相拉活时,需要同时 Start 和 Bind 对方。

由于高版本禁止从后台启动任何后台服务,所以在 AService 和 BService 互相 Start 时需要判断版本,API 26 以上需要 startForegroundService()

4.2 几乎不可见的Activity

主要思路是在 Activity#onCreate() 中将 Activity 设置为只有一个像素大小的透明的背景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
protected void onCreate(Bundle savedInstanceState) {
Window activityWindow = getWindow();
// 设置 Window 不可点击:
activityWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
// 设置 Window 不关心外界触摸事件:
activityWindow.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
// 设置 DecorView 为全屏状态
View decorView = activityWindow.getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
// 设置状态栏透明不可见:
activityWindow.setStatusBarColor(Color.TRANSPARENT);
// 设置 Window 位于左上角:
activityWindow.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams params = activityWindow.getAttributes();
// 设置 Window 相对 Gravity 横纵坐标偏移均为 0:
params.x = 0;
params.y = 0;
// 设置 Window 的宽和高均为 1 像素:
params.height = 1;
params.width = 1;
activityWindow.setAttributes(params);
}