您的当前位置:首页正文

Android的四大组件之Service

来源:华拓网

一.基础知识

Service概述:

Service是Android系统中的四大组件之一,主要有两个应用场景:后台运行和跨进程访问。Service可以在后台执行长时间运行操作而不提供用户界面,除非系统必须回收内存资源,否则系统不会停止或销毁服务。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信(IPC)需要注意的是,Service是在主线程里执行操作的,可能会因为执行耗时操作而导致ANR

Service可以分为三种形式:

启动

当应用组件通过调用startService()启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无期限运行,即使启动服务的组件已被销毁也不受影响。已启动的服务通常是执行单一操作,而且不会将结果返回给调用方

绑定

当应用组件通过调用bindService()绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务端的接口,运行组件与服务进行交互,甚至是利用进程间通信(IPC)跨进程执行这些操作。多个组件可以同时绑定服务,服务指挥在组件与其绑定时运行,一旦该服务与所有组件的绑定取消,系统便会销毁它。

启动且绑定

既通过startService()启动了服务,也进行了绑定。此时需要同时实现以下回调方法:onstartCommand()onBind()。系统不会在所有客户端都取消绑定时销毁服务。为此,必须通过调用stopSelf()stopService()显式停止服务。

无论应用是出于启动状态还是绑定状态,或者出于启动且绑定状态,任何应用组件均可使用Activity那样通过Intent来使用服务(及时此服务来自另一应用),也可以通过清单文件将服务声明未私有服务,阻止其他应用访问

二.Service生命周期

要使用服务,必须继承Service,在子类重写某些回调方法,用来处理服务生命周期的某些关键方面并提供一种机制将组件绑定到服务

onStartCommand()

当组件通过startService()请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。在指定任务完成后,通过stopSelf()stopService()来停止服务。

onBind()

当一个组件想通过bindService()与服务绑定时,系统将调用此方法。在此方法中,必须通过返回IBinder提供一个接口,供客户端来与服务进行通信

onCreate()

首次创建服务时,系统将调用此方法来执行初始化操作(在调用onStartCommand()onBind()之前)。如果在启动或绑定之前Service已在运行,则不会调用此方法

onDestroy()

当服务不再使用且被销毁时,系统将调用此方法,这是服务接收的最后一个调用,在此方法应清理占用的资源

仅当内存过低必须回收系统资源以供前台Activity使用时,系统才会强制停止服务如果将服务声明为在前台运行,则它几乎永远不会终止。或者,如果服务已启动并要长时间运行,则系统会随着时间的推移降低服务在后台任务列表中的位置,而服务也将随之变得非常容易被终止。如果服务是启动服务,则必须将其设计为能够妥善处理系统对它的重启。 如果系统终止服务,那么一旦资源变得再次可用,系统便会重启服务(这还取决于 onStartCommand() 的返回值)

三.启动Service的两种方式

如同其他组件一样,想要使用Service,必须在清单文件中对其进行声明声明方式是添加service元素作为 < application >

元素的子元素,例如:

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:supportsRtl="true"

android:theme="@style/AppTheme">

StartService启动服务

通过调用startService()启动,服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。因此,服务应通过调用 stopSelf() 来自行停止运行,或者由另一个组件调用stopService()来停止

可以通过扩展两个类来创建启动服务:

Service

这是所有服务的父类。扩展此类时,如果要执行耗时操作,必须创建一个用于执行操作的新线程,因为默认情况下服务将运行于UI线程

IntentService

这是Service的子类,它使用工作线程逐一处理所有启动请求。如果应用不需要同时处理多个请求,这是最好的选择。IntentService只需实现构造函数与onHandleIntent()方法即可,onHandleIntent()方法会接收每个启动请求的 Intent。

1.继承Service

onCreate()方法只会在初始时调用一次,onStartCommand(Intent

intent, int flags, int

startId)方法会在每次点击时都被调用,点击“停止音乐”按钮,onDestroy()方法会被调用

当中,每次回调onStartCommand()方法时,参数“startId”的值都是递增的,startId用于唯一标识每次对Service发起的处理请求

如果服务同时处理多个onStartCommand()请求,则不应在处理完一个启动请求之后立即销毁服务,因为此时可能已经收到了新的启动请求,在第一个请求结束时停止服务会导致第二个请求被终止。为了避免这一问题,可以使用stopSelf(int)确保服务停止请求始终基于最新一次的启动请求。也就是说,如果调用stopSelf(int)方法的参数值与onStartCommand()接受到的最新的startId值不相符的话,stopSelf()方法就会失效,从而避免终止尚未处理的请求

如果服务没有提供绑定,则使用startService()传递的 Intent 是应用组件与服务之间唯一的通信模式。如果希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent (使用getBroadcast()),并通过启动服务的 Intent 传递给服务。然后,服务就可以使用广播传递结果

当中,onStartCommand()方法必须返回一个整数,用于描述系统应该如何应对服务被杀死的情况,返回值必须是以下常量之一:

START_NOT_STICKY

如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务

START_STICKY

如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)

START_REDELIVER_INTENT

如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务

2.继承IntentSerice

由于大多数启动服务都不必同时处理多个请求,因此使用 IntentService 类实现服务也许是最好的选择

IntentService 执行以下操作:

创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent

创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样就不必担心多线程问题

在处理完所有启动请求后停止服务,因此不必自己调用 stopSelf()方法

提供 onBind() 的默认实现(返回 null)

提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent()

因此,只需实现构造函数与 onHandleIntent() 方法即可

BindService绑定服务

应用组件(客户端)通过调用 bindService() 绑定到服务,绑定是异步的,系统随后调用服务的

onBind() 方法,该方法返回用于与服务交互的 IBinder。要接收 IBinder,客户端必须提供一个 ServiceConnection

实例用于监控与服务的连接,并将其传递给 bindService()。当 Android

系统创建了客户端与服务之间的连接时,会回调ServiceConnection对象的onServiceConnected()方法,向客户端传递用来与服务通信的

IBinder

多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind()

方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder

传递至其他绑定的客户端。当所有客户端都取消了与服务的绑定后,系统会将服务销毁(除非 startService() 也启动了该服务)

另外,只有 Activity、服务和内容提供者可以绑定到服务,无法从广播接收器绑定到服务

可以通过以下三种方法定义IBinder接口:

扩展 Binder 类

如果服务是供本应用专用,并且运行在与客户端相同的进程中,则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Service 中可用的公共方法

使用 Messenger

如需让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口。服务可以这种方式定义对应于不同类型 Message 对象的 Handler。此 Handler 是 Messenger 的基础,后者随后可与客户端分享一个 IBinder,从而让客户端能利用 Message 对象向服务发送命令。此外,客户端还可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,这样就不必对服务进行线程安全设计

使用 AIDL

AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 IPC。 之前采用 Messenger 的方法实际上是以 AIDL 作为其底层结构。 如上所述,Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。 不过,如果想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。如需直接使用 AIDL,必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展

在前台运行Service

前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须在状态栏提供通知,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知

要请求让服务运行于前台,要调用

startForeground()方法,两个参数分别是:唯一标识通知的int类型整数和Notification对象

NotificationCompat.Builder mBuilder =  newNotificationCompat.Builder(this);

 mBuilder.setSmallIcon(R.drawable.bird);

 mBuilder.setContentTitle("这是标题吧~叶应是叶");

 startForeground(1, mBuilder.build());