本文最后更新于:2 天前
Service
是一种可在后台执行长时间运行操作而不提供界面的组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信(IPC)。例如,服务可在后台处理网络事务、播放音乐、执行文件 I/O 或与内容提供程序进行交互。
三种不同的服务类型
前台
前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。即使用户停止与应用的交互,前台服务仍会继续运行。
后台
后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。
注意:如果应用面向 API 级别 26 或更高版本,当应用本身未在前台运行时,系统会对运行后台服务施加限制。在诸如此类的大多数情况下,应用应改为使用计划作业。
绑定
当应用组件通过调用 bindService()
绑定到服务时,服务即处于绑定状态。绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
注意:服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应通过在服务内创建新线程来完成这项工作。
基础知识
如果要创建服务,需要先创建 Service
的子类(或使用它的一个现有子类)。以下是需要重写的最重要的回调方法:
onStartCommand()
当另一个组件请求启动服务时,系统会通过调用startService()
来调用此方法。执行此方法时,服务即会启动并可在后台无限期运行。因此在服务工作完成后,需要通过调用stopSelf()
或stopService()
来停止服务。(如果只想提供绑定,则无需实现此方法。)onBind()
当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用bindService()
来调用此方法。在此方法的实现中,必须通过返回IBinder
提供一个接口,以供客户端用来与服务进行通信。但是,如果并不希望允许绑定,则应返回 null。onCreate()
首次创建服务时,系统会(在调用onStartCommand()
或onBind()
之前)调用此方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法。onDestory()
当不再使用服务且准备将其销毁时,系统会调用此方法。服务应通过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。
使用清单文件声明服务
添加 <service>
元素作为 <application>
元素的子元素。android:name
为其唯一必须的属性,用于指定服务的类名。可通过添加 android:exported
属性并将其设置为 fasle
,确保服务仅本应用可适用。为了避免用户意外停止服务,需添加 android:description
属性,属性值为解释服务的作用及其提供的好处的短句。
创建启动服务
Service
这是适用于所有服务的基类。扩展此类时,必须创建用于执行所有服务工作的新线程,因为服务默认使用应用的主线程,这会降低应用正在运行的任何 Activity 的性能。IntentService
Service
的子类,使用工作线程逐一处理所有启动请求。如果不要求服务同时处理多个请求,此类为最佳选择,实现onHandleIntent()
,该方法会接收每个启动请求的 Intent,以便执行后台工作。
扩展 IntentService 类
实现 onHandleIntent()
方法即可,若要重写其他回调函数,确保调用超类实现。除 onHandleIntent()
方法外无需调用超类实现的唯一方法就是 onBind()
。
扩展 Service 类
onStartCommand()
方法的返回值必须是以下常量之一:
START_NOT_STICKY
如果系统在onStartCommand()
返回后终止服务,则除非有待传递的挂起 Intent,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。START_STICKY
如果系统在onStartCommand()
返回后终止服务,则其会重建服务并调用onStartCommand()
,但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会调用包含空 Intent 的onStartCommand()
。在此情况下,系统会传递这些 Intent。此常量适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。START_REDELIVER_INTENT
如果系统在onStartCommand()
返回后终止服务,则其会重建服务,并通过传递给服务的最后一个 Intent 调用onStartCommand()
。所有挂起 Intent 均依次传递。此常量适用于主动执行应立即恢复的作业(例如下载文件)的服务。
启动服务
将 Intent
传递给 startService()
或 startForegroundService()
,从 Activity 或其他应用组件启动服务。Android 系统会调用服务的 onStartCommand()
方法,如果服务尚未运行,则系统首先会调用 onCreate()
,然后调用 onStartCommand()
。
如果应用面向 API 级别 26 或更高版本,除非应用本身在前台运行,否则系统不会对使用或创建后台服务施加限制。如果应用需要创建前台服务,则需要调用 startForegroundService()
,此方法会创建后台服务,但它会向系统发出信号,表明服务会自行提升至前台。创建服务后,该服务必须在五秒钟内调用自己的 startForeground()
方法。
如果服务未提供绑定,则应用组件与服务间的唯一通信模式便是使用 startService()
传递的 Intent。如果希望服务返回结果,可以为广播创建一个 PendingIntent
,将其传递给启动服务的 Intent
中的服务。然后,服务就可以使用广播传递结果。
停止服务
除非必须回收内存资源,否则系统不会停止或销毁服务。服务必须通过调用 stopSelf()
自行停止运行,或由另一个组件通过调用 stopService()
来停止。
为了避免在第一个请求结束时停止服务会终止第二个请求的问题,可以使用 stopSelf(int)
,传入参数为启动请求的 ID,调用 stopSelf(int)
停止服务时,若 ID 不匹配(已有新的 onStartCommand()
调用),则服务不会停止。
在前台运行服务
前台服务必须为状态栏提供通知。
如果应用面向 Android 9(API28)或更高版本并使用前台服务,需要请求 FOREGROUND_SERVICE
权限(普通权限)。
如要请求让服务在前台运行,需要调用 startForeground()
。此方法有两个参数:唯一标识通知的整型数和用于状态栏的 Notifition。此通知必须具有 PRIORITY_LOW
或更高的优先级。
如要从前台移除服务,需要调用 stopForeground()
。此方法参数为布尔值,指示是否需要同时移除状态栏通知,此方法不会停止服务。
创建后台服务(IntentService)
- 创建
IntentService
子类 - 创建所需的回调方法
onHandleIntent()
- 在清单文件中定义
IntentService
。
IntentService
有一些限制:
- 无法直接与界面互动。要在界面中显示结果,需要将结果发送到
Activity
- 工作请求依序执行
- 在
IntentService
上运行的操作无法中断
将工作请求发送到后台服务(JobIntentService)
- JobIntentService
绑定服务
实现 onBind()
回调方法返回 IBinder
,收到 IBinder
后,客户端就可以通过该接口与服务进行交互。完成与服务的交互后,客户端可以通过调用 unbindService()
取消绑定。如果没有绑定到服务的客户端,系统会销毁该服务。
客户端通过调用 bindService()
绑定服务。调用时,必须提供 ServiceConnection
的实现,后者会监控与服务的连接。当创建客户端与服务之间的连接时,Android 系统会调用 ServiceConnection
上的 onServiceConnected()
。onServiceConnected()
方法包含 IBinder
参数,客户端随后使用该参数与绑定服务进行通信。
只有在第一个客户端绑定服务时,系统才会调用服务的 onBind()
方法来生成 IBinder
。然后,系统会将同一 IBinder
传递至绑定到相同服务的所有其他客户端,无需再次调用 onBind()
。
var mService: LocalService
val mConnection = object : ServiceConnection {
// Called when the connection with the service is established
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// Because we have bound to an explicit
// service that is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
val binder = service as LocalService.LocalBinder
mService = binder.getService()
mBound = true
}
// Called when the connection with the service disconnects unexpectedly
override fun onServiceDisconnected(className: ComponentName) {
Log.e(TAG, "onServiceDisconnected")
mBound = false
}
}
Intent(this, LocalService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
创建绑定服务
扩展 Binder 类
如果服务只是自有应用的后台工作线程,且无需跨进程工作,优先采用此方法。
- 在服务中,创建可执行以下某种操作的 Binder 实例:
- 包含客户端可调用的公共方法。
- 返回当前的
Service
实例,该实例中包含客户端可调用的公共方法。 - 返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法。
- 在
onBind()
回调方法返回此Binder
实例。 - 在客户端中,从
onServiceConnected()
回调方法接收Binder
,并使用提供的方法调用绑定服务。
class LocalService : Service() {
// Binder given to clients
private val binder = LocalBinder()
// Random number generator
private val mGenerator = Random()
/** method for clients */
val randomNumber: Int
get() = mGenerator.nextInt(100)
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
inner class LocalBinder : Binder() {
// Return this instance of LocalService so clients can call public methods
fun getService(): LocalService = this@LocalService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
}
class BindingActivity : Activity() {
private lateinit var mService: LocalService
private var mBound: Boolean = false
/** Defines callbacks for service binding, passed to bindService() */
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
val binder = service as LocalService.LocalBinder
mService = binder.getService()
mBound = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
mBound = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
}
override fun onStart() {
super.onStart()
// Bind to LocalService
Intent(this, LocalService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
unbindService(connection)
mBound = false
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
fun onButtonClick(v: View) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
val num: Int = mService.randomNumber
Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
}
}
}
使用 Messenger
如需让接口跨不同进程工作(一次接收一个请求),可以使用 Messenger
为服务创建接口。
- 服务实现一个
Handler
,由该类为每个客户端调用接收回调。 - 服务使用
Handler
来创建Messenger
对象(对Handler
的引用)。 Messenger
创建一个IBinder
,服务通过onBind()
使其返回客户端。- 客户端使用
IBinder
将Messenger
(其引用服务的Handler
)实例化,然后使用后者将Message
对象发送给服务。 - 服务在其
Handler
中(具体地讲,是在handleMessage()
方法中)接收每个Message
。
/** Command to the service to display a message */
private const val MSG_SAY_HELLO = 1
class MessengerService : Service() {
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
private lateinit var mMessenger: Messenger
/**
* Handler of incoming messages from clients.
*/
internal class IncomingHandler(
context: Context,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO ->
Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
else -> super.handleMessage(msg)
}
}
}
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
override fun onBind(intent: Intent): IBinder? {
Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
mMessenger = Messenger(IncomingHandler(this))
return mMessenger.binder
}
}
class ActivityMessenger : Activity() {
/** Messenger for communicating with the service. */
private var mService: Messenger? = null
/** Flag indicating whether we have called bind on the service. */
private var bound: Boolean = false
/**
* Class for interacting with the main interface of the service.
*/
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = Messenger(service)
bound = true
}
override fun onServiceDisconnected(className: ComponentName) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null
bound = false
}
}
fun sayHello(v: View) {
if (!bound) return
// Create and send a message to the service, using a supported 'what' value
val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
try {
mService?.send(msg)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
}
override fun onStart() {
super.onStart()
// Bind to the service
Intent(this, MessengerService::class.java).also { intent ->
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
// Unbind from the service
if (bound) {
unbindService(mConnection)
bound = false
}
}
}
使用 AIDL
如果想让服务同时处理多个请求,可以使用 AIDL。
管理绑定服务的生命周期
管理服务的生命周期
startService()
->stopSelf()
orstopService()
bindService()
->unbindService()
报告工作状态
-
LocalBroadcastManager
-
BroadCastReceiver
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!