Service的相关知识虽然简单,但是也比较琐碎,其衍生知识也比较多。本篇从Service的生命周期、运行和使用方式的角度简单介绍了Service。而在Service中关于IPC方面的知识放在Android进程间通信的部分介绍。
Service是一个在后台执行的应用组件,用于在后台进行长期操作,例如进行网络事务,播放背景音乐等等。在官网文档中:
Service是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。
Service的使用方式分为两种:非绑定启动和绑定启动
非绑定启动:当应用组件(如 Activity)通过调用startService方法启动Service时,Service即处于“启动”状态。一旦启动,Service即可在后台无限期运行,即使启动Service的组件已被销毁也不受影响。 已启动的Service通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后,Service会自行停止运行。
绑定启动:当应用组件通过调用bindService方法绑定到Service时,Service即处于“绑定”状态。绑定Service提供了一个客户端-服务器接口,允许组件与Service进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定Service才会运行。 多个组件可以同时绑定到该Service,但全部取消绑定后,该Service即会被销毁。
Service可以同时使用两种方式运行,它既可以是非绑定Service(以无限期运行),也允许绑Service。不同的运行方式所使用的回调方法也不相同,但都可以被其他组件使用(私有的除外)。
另外,我们需要注意的是,Service默认情况下运行在UI线程中,只负责与其他组件进行交互,耗时操作应另开线程完成。
Service的生命周期与运行
与Activity相同,Service也有自己的生命周期和回调方法,由于运行模式的不同,回调方法也不一样。
onCreate:首次创建Service时,系统将调用此方法来执行一次性设置程序,如果Service已在运行,则不会调用此方法。
onDestroy:当Service不再使用且将被销毁时,系统将调用此方法。Service应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是Service接收的最后一个调用。
如果我们使用startService方法以非绑定的方式启动Service,则系统会回调onStartCommend方法,Service会一直运行,直到它自己调用stopSelf方法或其他组件调用stopService方法停止它。如果我们使用bindService方法来启动Service,则系统会回调onBind方法,Service只会在该组件与其绑定时运行。一旦该Service与所有客户端之间的绑定全部取消,系统便会销毁它。而解绑的时候时调用onUnBind方法
onStartCommend:通过非绑定方式启动Service时会调用该方法,一旦执行此方法,Service即会启动并可在后台无限期运行,直到被停止。
onBind:当另一个组件想通过调用bindService方法与Service绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,需要通过返回IBinder接口,以供客户端用来与Service进行通信。但如果不允许绑定,则应返回 null。
与其他组件一样,系统当内存不足时,也会杀死那些优先级低的组件。系统只有在内存过低,需要运行与用户交互的activity时,才会停止后台Service。Service的优先级可以分为如下几类:
前台运行:通过startForeground方法将当前Service与前台状态栏中的通知绑定,从而保持该Service与用户处于交互状态,因此该Service优先级最高,一般不会被杀死。通过stopForeground方法可以解除Service的前台运行状态,但是不会停止服务。
与activity绑定:Service可以与activity绑定在一起,那些与获取到用户焦点的activity绑定在一起的Service通常具有较高的优先级,也不太会被系统杀死。
后台运行:随着Service运行的时间增加,系统会随着时间的推移降低Service在后台任务列表中的位置,而Service也将随之变得非常容易被终止。对于非绑定型的Service,通常会很容易被终止,因此需要妥善处理其重启工作。
当系统杀死service后,可以有不同的处理方式,具体放在onStartCommend方法的返回值部分讨论。
Service的使用
我们通常会通过继承Service类和IntentService类的方式来使用Service。
继承Service: 继承service类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。
继承IntentService: IntentService使用工作线程逐一处理所有启动请求。如果没有并发的需求时,这是最好的选择。IntentService创建了一个工作队列,用于将接收到的 Intent 逐一传递给我onHandleIntent方法中,对所有请求的intent进行处理。当所有请求任务都完成后,则自动停止该service。
onStartCommend方法的返回值
在非绑定型的service中,我们通常会onStartCommend方法中开启多线程来处理intent请求,该方法返回了一个整数值,用于描述系统应该如何在Service终止的情况下继续运行Service:
START_NOT_STICKY:如果当onStartCommand方法返回后,系统杀死了service,除非又有intent请求传递进来,否则将不会重新启动service。该选项可以很安全地确保当应用程序重启那些未完成的工作,而并不需要运行service时,避免service的启动运行。
START_STICKY:如果当onStartCommand方法返回后,系统杀死了service,那么将会重新启动service,并执行onStartCommand方法,但是重新传递进来的intent并不是上一个intent了,而是使用null来代替(除非有新的intent请求)。该选项适用于类似于媒体播放器这种service,不执行任何命令,只是一直在运行等待任务。
START_REDELIVER_INTENT:与START_STICKY相似,如果当onStartCommand方法返回后,系统杀死了service,那么将会重新启动service,并执行onStartCommand方法,重新传递进来的intent与上一个相同,随后而来的intent请求将会依次执行。该选项适用于类似下载任务的service,一直处于活跃状态,一旦挂掉就需要立即恢复。
绑定型service的使用
利用bindService方法可让组件(例如 Activity)绑定到Service、发送请求、接收响应,甚至执行进程间通信 (IPC)。 绑定Service通常只在为其他应用组件服务时处于活动状态,不会无限期在后台运行。
请求服务的组件和Service组成了C/S的交互模式。
客户端通过bindService方法请求服务并绑定到Service服务端,同时该组件应提供一个ServiceConnection对象来监控与服务端的连接。
服务端需要实现onBind方法需要返回一个IBinder接口对象,该接口定义了客户端用来与服务进行交互的编程接口。Service在建立连接时,会回调ServiceConnection对象的onServiceConnectted方法,将IBinder接口给客户端,用以让客户端能够与Service通信。
多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的onBind方法来检索IBinder。系统随后无需再次调用onBind方法,便可将同一IBinder传递至任何其他绑定的客户端。
当最后一个客户端取消与Service的绑定时,系统会将Service销毁(除非之前就通过非绑定型也启动了该服务)。
注:所有客户端都应在适当的时间(例如当 Activity 暂停时)取消绑定。
绑定型Service与客户端通信
服务端的Service在onBind方法中返回了一个IBinder接口对象给客户端,该接口定义了客户端用来与服务进行交互的编程接口。可以有如下三种方式实现该接口对象:
继承Binder类:如果该Service只为本App提供提供服务,且无需跨进程通信(IPC),则应该使用继承Binder接口的方式来实现IBinder对象。客户端收到Service返回的Binder对象后,利用它可以直接调用Service的公有方法。当客户端和Service不在同一进程或非同一App调用时无法使用这种方法。
使用Messenger:使用Messenger可以在不同进程间传递message对象,从而完成进程间通信的工作。Messenger的底层实现是AIDL,但它会在单一线程中创建包含所有请求的队列,不必对Service进行线程安全设计,因此这是一种执行进程间通信的轻量级方法。
使用AIDL:AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行跨进程通信。利用AIDL可以让Service具有多线程处理能力,但是需要保证线程安全。
官网文档 注:大多数应用“都不会”使用 AIDL 来创建绑定Service,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL 并不适合大多数应用。