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 | <manifest ... > |
android:name 属性是唯一必须声明的属性,用来指定服务类名。
注意:为了确保应用的安全性,我们应使用显式 Intent 启动或绑定 Service,且不应为其声明 Intent 过滤器。另外,还可以通过添加android:exported属性并将其设置为false,以确保此服务仅仅在本应用中被调用。这样可以防止第三方应用启动了我们的服务。
启动服务
在其他组件中,我们通过 Intent 来指定需要启动的服务,调用 startService()。这样系统会自动调用 onStartCommand() 方法,并将定义好的 Intent 传递过去。
注意:切勿手动调用 onStartCommand() 方法。
1 | Intent intent = new Intent(this, HelloService.class); |
还有一点需要注意的是,当我们需要启动的 Service 还未运行,则会调用 onCreate() -> onStartCommand(),否者直接调用 onStartCommand() 方法。
对于非绑定的服务,我们想要与之通信,startService() 中传递的 Intent 是唯一的通信模式。但是,我们可以通过为广播创建一个 PendingIntent(使用 getBroadcast()),并通过启动服务的方法将这个 Intent 传递给服务。这样,在 Service 中,就可以通过广播来传递信息了。
Tip : 多次启动同一个服务,会调用多次的onStartCommand()方法,但是,如果我们想要停止服务,只需一个停止请求即可(stopSelf()或者stopService())
停止服务
服务需要自己管理自己的生命周期。换而言之,除非系统需要回收内存资源,我们的服务是不大可能会被停止和销毁的。因此,当我们不需要某个 Service 时,应调用 stopSelf() 来停止服务,亦或者是在另一个组件中通过调用 stopService() 来停止服务。
那么要如何避免上述情况的发生呢?
我们可以通过 stopSelf(int) 来确保请求停止的服务是最近一个被启动的服务。换句话说就是,stopService(int) 中传递的 id 和 onStartCommand() 中的 startId 是相等的,这样如果 id 不匹配,服务也就不会被停止了。
绑定服务
关于绑定服务的相关知识,我们将会在下一篇博文 《Android 中的绑定服务》 详细论述。
Service 中向用户发送通知
运行中的 Service 可以使用 Toast 通知 或 状态栏通知 来与用户交互。
首先,Toast 通知 就没什么好说的了,Android 开发者应该是非常熟悉了。而 状态栏通知 通常出现在 Service 所做的工作(比如文件下载)且用户可以马上对其操作(比如终止下载,下载完成后打开文件),这种情况下,状态栏是最佳的选择。
前台运行的服务
前台运行的服务是一种优先级非常高的服务,是一种用户能够意识到的服务,即使是在系统内存不足时,系统也不会考虑将其终止。前台服务必须在状态栏中提供通知,状态栏位于 “正在进行” 标题下方,这意味着除非服务停止或者前台删除,否者我们是不能清除通知的。
运用前台服务最常见的例子就是音乐播放器。状态栏中的通知一般都是正在播放的歌曲,用户点击过后又会启动相应的 Activity 来操作播放器。
通过调用 statForeground() 方法来让服务运行于前台。此方法需要传入两个参数:
- 唯一标识通知的整型数;
- 状态栏的 Notificaiton
1 | Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text), |
注意:传入 startForeground() 中的整数型 ID 不能为 0.
想要删除前台服务,通过调用 stopForeground(),参数中我们需要传入一个 boolean 值,来决定是否需要删除状态栏通知。
Service 生命周期
Service 中回调的示例代码:
1 | public class ExampleService extends Service { |

上图中分别是通过 startService() 和 bindService() 来创建 Service 的生命周期。
外话:使用服务还是使用线程?
即使用户未与用户交互,它仍然可以在后台运行。我们应该仅在必要时创建 Service。如果需要在主线程外执行工作,而这种操作又是在用户与应用交互时才需要,这个时候我们应该使用线程,而不是 Service.比如说你只是想在 Activity 运行时播放音乐,就没有必要用 Service 了。
本文参考: