android四大组件之一——Service
目录
一、Service概述
二、生命周期
三、权限
四、进程生命周期
五、本地Service举例
六、远程Messenger Service举例
一、Service概述
Service 是应用组件,代表一个应用的长时间后台运行的操作,没有交互界面,提供给其他应用一些功能。每个service类必须在清单文件AndroidManifest.xml
里做 <service>声明
. Services可以被启动通过Context.startService()
和Context.bindService()
.
services像其他应用对象一样,运行在宿主进程的主线程。这意味着,如果你的服务要执行任何CPU密集型(如MP3播放)或阻塞(如网络)操作,它应该生成自己的线程来完成这项工作。有在Processes and Threads中可以找到更多的信息。 JobIntentService
类作为标准实现类,它有自己的线程可以按部就班的处理事务。
Service
是 应用组件 长时间运行的操作。它不提供界面。一次 一项服务可能会继续运行一段时间,即使用户切换到另一项服务 应用。此外,组件可以绑定到服务以与其进行交互,甚至执行 进程间通信 (IPC)。例如,服务可以处理网络事务、 音乐、执行文件 I/O 或与内容提供程序互动,所有这一切都可以在后台进行。
关于Service类的许多困惑实际上都围绕着它不是什么:
- Service不是一个独立的进程。Service对象本身并不意味着它在自己的进程中运行;除非另有指定,否则它在其所属的应用程序相同的进程中运行。
- Service不是一个线程。它本身不是一种在主线程之外执行工作的手段(以避免应用程序无响应错误)。
Service实际上非常简单,提供了2个主要功能:
- 一个功能是允许应用程序在后台运行处理事情 (甚至不需要有交互界面).当请求系统定时去处理事务时,可以调用
Context.startService()
启动Service,一直运行Service,除非用户明确停止Service. - 一个功能是应用程序可以提供给其他应用一些功能,当为了与服务交互保持长期的连接时,可以调用
Context.bindService()
,绑定Service.
当Service组件被创建时,系统会实例化这个组件,并且会在主线程中回调onCreate()
等方法。Service是否可以用适当的操作实现这些操作,例如创建一个可以工作的辅助线程。
因为Service如此简单,所以你可以通过想用的简单的或者复杂的方式与它交互。把Service当作Java本地对象,创造直接的回调方法(详细说明请看本地Service示例)通过AIDL提供全部远程接口。
二、生命周期
系统启动Service有两种方式。通过调用Context.startService()
启动Service,然后系统检索到service后,创建它,并调用onCreate,然后通过客户端提供的参数回调onStartCommand(Intent, int, int)
方法。 service会一直运行,直到调用Context.stopService()
或stopSelf()
停止Service. 请注意,对Context.startService()的多次调用不会嵌套 (多次调用start会回调多次onStartCommand()方法), 所以无论启动多少次service,只要调用一次Context.stopService() 或者stopSelf(),Service就会被停止。无论怎样,services可以用 stopSelf(int)
方法确保只有在intents开始处理后,才能停止service。
对于已经启动的services, 它们还可以选择以另外两种主要的运行模式运行,依赖于onStartCommand()的返回值: START_STICKY,表示
services当被需要时,会被明确的启动或者停止。当使用START_NOT_STICKY
or START_REDELIVER_INTENT
于服务时,服务当处理任何指令发送它们时,服务应该一直保持运行。详情请参考有关文档。
客户端也可以用Context.bindService()
获取与service的持续连接。同样创建没有运行的服务,但是不回调onStartCommand()方法. 客户端可以收到从服务
方法返回的onBind(Intent)
IBinder
对象,允许客户端制造回调给service。service运行时长和连接建立时长一样。通常 IBinder被返回给在aidl中创建的复杂接口。
service可以被启动或者绑定。 can be both started and have connections bound to it. 在这种情况下,系统可以保持service运行只要它被启动或者被绑定通过 Context.BIND_AUTO_CREATE
标识. 一旦这两种情况都不成立后,service的onDestroy()
方法会被调用或者有效的中断。所有的清理工作也应该在onDestroy()方法返回之前.
三、权限
当一个服务在其清单文件(manifest)的<service>
标签中被声明时,可以强制实现对该服务的全局访问。通过上述操作,其他应用也需要声明对应的权限<uses-permission>
在自己的清单文件里,用以启动,停止,或绑定service.
从Build.VERSION_CODES.GINGERBREAD
(即Android 2.3姜饼版本)开始,当使用Context.startService(Intent)
方法启动服务时,您还可以在Intent上设置Intent.FLAG_GRANT_READ_URI_PERMISSION
和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION
标志。这将授予服务对Intent中特定URI的临时访问权限。该访问权限将一直保留,直到服务为该启动命令或后续命令调用了stopSelf(int)
方法,或者服务已经完全停止为止。
这适用于向那些没有请求保护服务的权限的其他应用授予访问权限,甚至当服务根本没有被导出时也同样有效。
另外,service可以用权限保护IPC调用,在执行调用实现之前需要调用ContextWrapper.checkCallingPermission(String)
方法。
查看Security and Permissions 文档,大体上详细讲述了权限和安全的内容。
四、进程生命周期
只要服务已被启动或有客户端绑定到该服务,Android系统就会尝试保持承载该服务的进程存活。当内存低时,需要杀死进程,保留service的进程根据下面可能性有更高的优先级:
-
当前service执行代码在
onCreate()
,onStartCommand()
, 或onDestroy()
方法里, 所在进程将会成为前台进程,以确保代码执行期间进程不被杀死。 -
服务已经被启动,所在进程被认为比其他可见进程优先级低,比其他任何不可见进程优先级高。因为一般只有一些对用户可见的进程除非在低内存下才会被杀死。然而,由于用户无法直接感知到后台服务的存在,在这种状态下,该服务被视为可被终止的有效候选对象,因此您应该为此做好准备。特别的是,对于长时间在后台运行的services会增加被killl掉的风险,且如果启动足够长的时间,确保被kill掉。
-
如果客户端绑定service, 宿主进程比任何进程优先级都要高。如果客户端对用户是可见的,那么service本身对用户也是可见的。客户端影响了service重要性的通过设置如下标识实现的:
Context.BIND_ABOVE_CLIENT
,Context.BIND_ALLOW_OOM_MANAGEMENT
,Context.BIND_WAIVE_PRIORITY
,Context.BIND_IMPORTANT
, andContext.BIND_ADJUST_WITH_ACTIVITY
. -
当被开启的service调用
startForeground(int, android.app.Notification)
将service切换到前台,系统会认为用户知道service运行着处理事务,所以即使在低内存时,用户也不会kill掉service。 (理论上,在当前前台应用程序面临极端内存压力的情况下,该服务仍有可能被终止,但在实际操作中,这通常不必担心。)
大多数时候,运行着的service,在内存高度紧缺的情况下,依旧可能被kill掉。如果被kill掉,系统会在稍后重启service。这带来的一个重要后果是,如果您在onStartCommand()
方法中安排异步执行工作或在其他线程中执行工作,那么您可能希望使用START_FLAG_REDELIVERY
标志,以便系统在您的服务在处理Intent时被终止的情况下重新传递该Intent,从而确保它不会丢失。
当然,与服务运行在同一进程中的其他应用程序组件(如Activity)可以提高整个进程的重要性,而不仅仅是服务本身的重要性。
五、组件与绑定Service的交互方式
1.扩展 Binder 类
如果您的服务仅对您自己的应用专用,并且在同一进程中运行 作为客户端(这很常见),请通过扩展 Binder
来创建接口 类别 并从中返回一个 onBind()
。客户端收到 Binder
后,可利用它直接访问 Binder
实现或 Service
中提供的公共方法。
如果服务只是您自有应用的后台工作器,应优先采用这种方式。只有在这并非创建接口的首选方式时, 如果其他应用或不同的进程使用您的服务,则会发生该错误。
如果只有本地应用使用您的服务,且无需 跨进程工作 那么您可以实现自己的 Binder
类,为您的客户端直接提供 访问服务中的公共方法。
注意:只有当客户端和服务处于同一应用和进程内(最常见的情况)时,此方式才有效。例如,这非常适合于 这个应用需要将一个 activity 绑定到自己在 背景。
以下为设置方式:
- 在您的服务中,创建可执行以下某种操作的
Binder
实例:- 包含客户端可调用的公共方法。
- 返回当前的
Service
实例,该实例中包含客户端可调用的公共方法。 - 返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法。
- 从
onBind()
回调方法返回此Binder
实例。 - 在客户端中,从
onServiceConnected()
回调方法接收Binder
,并使用提供的方法调用绑定服务。
注意:服务和客户端必须在同一应用内,这样客户端才能转换返回的对象并正确调用其 API。服务 和客户必须在同一进程中,因为此方法不会执行任何 是跨进程编组的
举例
例如,以下服务可让客户端通过 Binder
实现访问服务中的方法:
服务端代码(MyService.kt)和客户端代码(ServiceDemoActivity.kt)demo项目源码下载请见
2.使用 Messenger信使
如需让接口跨不同进程工作,您可以使用 Messenger
为服务创建接口。这样,服务 定义了一个 Handler
,用于响应不同类型的 Message
对象。
这部Handler
是 Messenger
的基础,后者随后可以共享 IBinder
与客户端连接起来,让客户端使用 Message
对象向服务发送命令。此外,客户端还可以定义一个 Messenger
它自己的,因此服务可以发回消息。
这是执行进程间通信 (IPC) 最为简单的方式,因为 Messenger
会在单个线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。
如果您需要让服务与远程进程通信,则可使用 Messenger
为您的服务提供接口。利用这种方法, 无需使用 AIDL 即可执行进程间通信 (IPC)。
为接口使用 Messenger
比使用 AIDL 更简单,因为 Messenger
会将所有服务调用加入队列。纯 AIDL 接口会同时向 服务,后者必须处理多线程处理。
对于大多数应用,服务不需要执行多线程处理,因此使用 Messenger
可让服务一次处理一个调用。如果重要 您的服务是多线程的,请使用 AIDL 来定义接口。
以下是对 Messenger
使用方式的摘要:
- 服务实现一个
Handler
,由其接收来自客户端的每个调用的回调。 - 服务使用
Handler
来创建Messenger
对象(该对象是对Handler
的引用)。 Messenger
创建一个IBinder
,服务通过onBind()
将其返回给客户端。- 客户端使用
IBinder
将Messenger
(它引用服务的Handler
)实例化,然后再用其将Message
对象发送给服务。 - 服务在其
Handler
中(具体而言,是在handleMessage()
方法中)接收每个Message
。
这样,客户端便没有调用服务的方法。相反,客户端会传递服务在其 Handler
中接收的消息(Message
对象)。
举例
下面这个简单的服务实例展示了如何使用 Messenger
接口。
如果您想让服务做出响应,还需在客户端中创建一个 Messenger
。当客户端收到 onServiceConnected()
回调时,会向服务发送一个 Message
,并在其 send()
方法的 replyTo
参数中加入客户端的 Messenger
。如需查看如何提供双向消息传递的示例,请看如下示例:
服务端代码(MessengerService.kt)和客户端代码(MessengerActivity.kt)demo项目源码下载请见
3.使用 AIDL
Android 接口定义语言 (AIDL) 可将对象分解为 操作系统可以识别这些基元并将其编组到各进程中以执行 IPC。之前采用 Messenger
的方法实际上是以 AIDL 为基础, 及其底层结构。
如上一部分所述,Messenger
会创建一个 所有客户端请求都在一个线程中完成,因此服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,可以直接使用 AIDL。在这种情况下,您的服务必须具备线程安全性,并且能够进行多线程处理。
如需直接使用 AIDL,请执行以下操作: 创建一个定义编程接口的 .aidl
文件。Android SDK 工具会利用该文件生成实现接口和处理 IPC 的抽象类,您随后可在服务内对该类进行扩展。
注意:对于大多数应用来说,AIDL 并不是 创建绑定服务,因为它可能需要多线程处理能力 可以使实现过程更加复杂。
六、远程Messenger Service举例
参考文章
绑定服务概览 | Background work | Android Developers