Android 中的 Service

Service 作为 Android 四大组件之一,在 Android 开发中有这不可替代的作用。今天这篇博文,我们就来详细了解一下 Android 的 Service

前言

什么是服务?

Service 与 Activity 不同,它是一个不需要交互界面,且能够在后台长时间运行的应用组件。通常情况下,Service 能够由其他应用组件来启动,即使启动者被销毁了,它依然能够在后台继续运行。

它有什么作用?

我们可以将 Service 与其他组件进行绑定,两者进行通信,也可以执行进程间的通信(IPC).通过 Service,我们可以处理网络事务、播放音乐、执行 I/O 操作或者与提供内容的程序交互。注意,所有的一切,都是在后台执行的。

我们可以通过下面两种方式来启动一个服务:

  • 1.startService() : 这种方式,即使启动者别销毁了,Service 依旧可以在后台运行,且不会反悔结果给调用者。
  • 2.bindServcie() : 这种方式,使得 Service 出于绑定状态。该服务提供了一个接口,允许我们与 Service 进行交互(发送请求,获取结果),甚至是用进程通信(IPC)跨进程交互。另外,多个组件可以同时绑定到该服务,同时如果你想要停止该服务,你需要解绑所有的绑定,该服务才能够被销毁。

注意1:上面的两种方式可以同时存在,也就是说,你可让一个服务以 startService() 的方式在后台无限期的运行,也可以让该 Service 绑定到某一个组件上。

注意2:服务默认情况下是在主进程中运行的,它既不创建自己的线程,也不在单独的进程中运行,当然,你另行指定的话就另说了。也就是说,如果你想在 Service 中执行一些耗时操作的话,请在单独开一个子线程,以降低 ANR 的风险。

如何创建 Service?

Step1: 创建 Service 子类,重写一些回调方法

  • onStartCommand()

当另一个组件通过调用 startService() 启动服务时,系统会调用此方法。实现此方法后,你需要通过调用 stopSelf() 或者是 stopService() 来停止服务。如果你只是想提供绑定服务,则无需实现此方法。

  • onBind()

当另一个组件通过调用 bindService() 绑定服务时,系统将调用此方法。在此方法中,你需要返回一个 IBinder,实现里面的相关方法,以供调用者与服务进行通信。当然,如果你不是绑定服务,返回 null 即可。

  • onCreate()

首次创建 Service 时,系统会调用此方法。如果 Service 已在运行,则不会调用此方法。

  • onCreate()

当服务不再使用被销毁时,系统将调用此方法。你需要在服务中清理掉服务中用到的所有资源,如线程,接受器等。

Step2: 在清单文件中声明服务

代码如下:

1
2
3
4
5
6
7
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>

android:name 属性是唯一必须声明的属性,用来指定服务类名。

注意:为了确保应用的安全性,我们应使用显式 Intent 启动或绑定 Service,且不应为其声明 Intent 过滤器。另外,还可以通过添加 android:exported 属性并将其设置为 false,以确保此服务仅仅在本应用中被调用。这样可以防止第三方应用启动了我们的服务。

启动服务

在其他组件中,我们通过 Intent 来指定需要启动的服务,调用 startService()。这样系统会自动调用 onStartCommand() 方法,并将定义好的 Intent 传递过去。

注意:切勿手动调用 onStartCommand() 方法。
1
2
Intent intent = new Intent(this, HelloService.class);
startService(intent);

还有一点需要注意的是,当我们需要启动的 Service 还未运行,则会调用 onCreate() -> onStartCommand(),否者直接调用 onStartCommand() 方法。

对于非绑定的服务,我们想要与之通信,startService() 中传递的 Intent 是唯一的通信模式。但是,我们可以通过为广播创建一个 PendingIntent(使用 getBroadcast()),并通过启动服务的方法将这个 Intent 传递给服务。这样,在 Service 中,就可以通过广播来传递信息了。

Tip : 多次启动同一个服务,会调用多次的 onStartCommand() 方法,但是,如果我们想要停止服务,只需一个停止请求即可(stopSelf() 或者 stopService()

停止服务

服务需要自己管理自己的生命周期。换而言之,除非系统需要回收内存资源,我们的服务是不大可能会被停止和销毁的。因此,当我们不需要某个 Service 时,应调用 stopSelf() 来停止服务,亦或者是在另一个组件中通过调用 stopService() 来停止服务。

注意,如果 Service 同时处理多个 onStartCommand() 请求时,这种情况下,我们不应处理完一个请求后立刻停止服务,因为这个时候,Service 可能接受到了新的请求,如果我们停止了服务,则第二个请求将不被执行。

那么要如何避免上述情况的发生呢?

我们可以通过 stopSelf(int) 来确保请求停止的服务是最近一个被启动的服务。换句话说就是,stopService(int) 中传递的 id 和 onStartCommand() 中的 startId 是相等的,这样如果 id 不匹配,服务也就不会被停止了。

Tip : 为了避免浪费系统资源和消耗电量,我们应该保证工作完成后立即停止服务。

绑定服务

关于绑定服务的相关知识,我们将会在下一篇博文 《Android 中的绑定服务》 详细论述。

Service 中向用户发送通知

运行中的 Service 可以使用 Toast 通知状态栏通知 来与用户交互。

首先,Toast 通知 就没什么好说的了,Android 开发者应该是非常熟悉了。而 状态栏通知 通常出现在 Service 所做的工作(比如文件下载)且用户可以马上对其操作(比如终止下载,下载完成后打开文件),这种情况下,状态栏是最佳的选择。

前台运行的服务

前台运行的服务是一种优先级非常高的服务,是一种用户能够意识到的服务,即使是在系统内存不足时,系统也不会考虑将其终止。前台服务必须在状态栏中提供通知,状态栏位于 “正在进行” 标题下方,这意味着除非服务停止或者前台删除,否者我们是不能清除通知的。

运用前台服务最常见的例子就是音乐播放器。状态栏中的通知一般都是正在播放的歌曲,用户点击过后又会启动相应的 Activity 来操作播放器。

通过调用 statForeground() 方法来让服务运行于前台。此方法需要传入两个参数:

  • 唯一标识通知的整型数;
  • 状态栏的 Notificaiton
1
2
3
4
5
6
7
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
注意:传入 startForeground() 中的整数型 ID 不能为 0.

想要删除前台服务,通过调用 stopForeground(),参数中我们需要传入一个 boolean 值,来决定是否需要删除状态栏通知。

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
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used

@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
我们需要知道的是,与 Activity 不同,我们不需要再回调方法中调用父类的实现。

上图中分别是通过 startService()bindService() 来创建 Service 的生命周期。

外话:使用服务还是使用线程?

即使用户未与用户交互,它仍然可以在后台运行。我们应该仅在必要时创建 Service。如果需要在主线程外执行工作,而这种操作又是在用户与应用交互时才需要,这个时候我们应该使用线程,而不是 Service.比如说你只是想在 Activity 运行时播放音乐,就没有必要用 Service 了。

本文参考:

文章有帮助到您?不妨打赏博主一碗拉面或一杯咖啡的小费吧 :-D!