android四大组件之一——Service

目录

​一、Service概述

二、Service分类

1.前台服务

2.后台服务

3.绑定服务

三、Service的两种启动方式

1.start启动模式 

2.bind绑定模式

四、权限

五、Service生命周期

六、组件与绑定Service的通信方式

1.扩展 Binder 类

2.Messenger信使

3.AIDL

七、总结

场景使用区别

八、源码下载


一、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分类

Service的三种类型如下:

1.前台服务

前台服务与后台服务不同的是,前台服务一般用于执行一些用户能注意到的操作。例如:音频应用会使用前台服务来播放音频曲目,位置信息提醒,通知栏显示导航信息等场景后可以使用前台服务。前台服务必须显示通知,即使用户停止与应用交互,前台服务仍会继续运行。因为它是用户可以感知到存在的,所以前台服务的优先级比较高,等同与一个处于前台的 Activity 的优先级,几乎不太可能被系统杀掉。

前台服务用于执行用户可以注意到的操作。前台服务会显示状态栏通知。Android 9.0及之后,要求必须申请如下权限才能使用前台服务:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

如果在前台服务中要实现用户的位置,或者要使用相机等,还需要申请一些更具体的权限,官网有详细说明,这里就不展开说了。

注意:在Android 12以上运行前台服务,系统会等待 10 秒,然后才会显示与前台服务关联的通知.

前台服务demo源码请见:章节八、源码下载

2.后台服务

后台服务一般用于执行用户不会直接注意到的操作。例如,如果应用在后台使用某个服务来压缩其存储空间,则此服务通常是后台服务。一般来说,可以通过startService(intent)方法来启动一个后台服务。

3.绑定服务

当应用组件通过 bindService() 绑定到服务时,服务即处于绑定状态(此时可以将service看作是服务器,绑定的组件看作是客户端),绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接受结果,甚至是利用进程间通信跨进程执行这些操作,仅当与另一个应用组件绑定时,绑定服务才会运行,多个组件可同时绑定到同一个服务,全部取消绑定后,该服务会被销毁。这种service的形式是比较常用的。

注意:只有 Activity、Service和内容提供程序可以绑定到服务,无法从广播接收器绑定到服务。

三、Service的两种启动方式

系统启动Service有两种方式:startService和bindService

1.start启动模式 

通过调用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 于服务时,服务当处理任何指令发送它们时,服务应该一直保持运行。详情请参考有关文档。

start启动模式demo源码下载请见:章节八、源码下载

2.bind绑定模式

客户端也可以用Context.bindService()获取与service的持续连接。同样创建没有运行的服务,但是不回调onStartCommand()方法. 客户端可以收到从服务  onBind(Intent)方法返回的IBinder 对象,允许客户端制造回调给service。service运行时长和连接建立时长一样。通常 IBinder被返回给在aidl中创建的复杂接口。

service可以被启动或者绑定。在这种情况下,系统可以保持service运行只要它被启动或者被绑定通过 Context.BIND_AUTO_CREATE 标识. 一旦这两种情况都不成立后,service的onDestroy() 方法会被调用或者有效的中断。所有的清理工作也应该在onDestroy()方法返回之前.

注意,当调用unbindService(connection)时ServiceConnection中的onServiceDisconnected并没有被回调,这是应为这个方法只有在Service被不正常kill之后才会回调,通过这个回调是为了让用户知道服务被kill了,然后可以重新启动服务。

bind启动模式demo源码下载请见:章节八、源码下载

四、权限

当一个服务在其清单文件(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 文档,大体上详细讲述了权限和安全的内容。

五、Service生命周期

onCreate()

首次创建服务时,系统会在调用 onStartCommand()或onBind() 之前调用onCreate()方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法。

onStartCommand()

内部其实调用了服务的onStart,所以onStart()这个生命周期已被弃用。

每次执行startService(intent)时,都会回调Service的onStartCommand生命周期。在执行bindService(intent)时,是不会回调Service的onStartCommand的

如果系统终止了服务,那么会在资源可用时立即重启服务。这取决于你的onStartCommand()的返回值。

onDestroy()

作为后台服务/前台服务时,当我们手动调用stopService(intent)或stopSelf()方法的时候,服务就停止了。然后回调onDestroy()。

作为绑定服务时,当我们调用unbindService(connection)时,服务就停止了。然后回调onDestroy()。

服务应通过实现onDestroy()方法来清理任何资源,如线程、注册的监听器、接收器等。

onBind()

Service的成员方法。返回一个IBinder对象。

当我们把服务作为一个绑定服务的时候,才会用到这个回调方法。如果Service是后台服务的形式,一般是不需要用到这个方法的。在Service是绑定的形式时,需要实现这个方法。

作为绑定服务时,当我们调用bindService()时,回调完onCreate()方法,就会马上回调onBind()方法。

onUnbind()

同理,当服务作为绑定服务时,当我们调用unbindService(connection)时,就会回调onUnbind()方法。

onStartCommand返回值
当调用startService(intent)方法启动Service时,会回调Service的onStartCommand生命周期。

如果面临资源匮乏时,系统可能会销毁掉当前运行的Service,然后待内存充足的时候可以重新创建Service,Service被强制销毁并再次重建的行为,依赖于Service中onStartCommand方法的返回值,下面我来列举几个它的常用的返回值:

  • START_NOT_STICKY:如果onStartCommand返回该值,表示当Service被强杀后,不会重新创建该Service。
  • START_STICKY:当Service被强杀后,系统仍会将该Service设置为运行状态,但是不再保存onStartCommand方法传入的intent对象,当系统资源充足时,会尝试再次重新创建该Service,并执行onStartCommand回调方法,但是onStartCommand回调方法的Intent参数为null。这也是onStartCommand的默认的返回值。
  • START_REDELIVER_INTENT:该返回值与START_STICKY类似,不同的是,系统会保留Service被强杀前的最后一次传入onStartCommand方法中的Intent对象。

只要服务已被启动或有客户端绑定到该服务,Android系统就会尝试保持承载该服务的进程存活。当内存低时,需要杀死进程,保留service的进程根据下面可能性有更高的优先级:

大多数时候,运行着的service,在内存高度紧缺的情况下,依旧可能被kill掉。如果被kill掉,系统会在稍后重启service。这带来的一个重要后果是,如果您在onStartCommand()方法中安排异步执行工作或在其他线程中执行工作,那么您可能希望使用START_FLAG_REDELIVERY标志,以便系统在您的服务在处理Intent时被终止的情况下重新传递该Intent,从而确保它不会丢失。

当然,与服务运行在同一进程中的其他应用程序组件(如Activity)可以提高整个进程的重要性,而不仅仅是服务本身的重要性。

六、组件与绑定Service的通信方式

对用于service中,一般使用bindService()启动服务。这是一种比startService更复杂的启动方式,同时使用这种方式启动的service也能完成更多的事情,比如其他组件可向其发送请求,接受来自它的响应,甚至通过它来进行IPC等等。我们通常将绑定它的组件称为客户端,而称它为服务端。 如果要创建一个支持绑定的service,我们必须要重写它的onBind()方法。这个方法会返回一个IBinder对象,它是客户端用来和服务器进行交互的接口。而要得到IBinder接口,我们通常有三种方式:继承Binder类,使用Messenger类,使用AIDL。

跨进程通信方式有如下三种:

  • 扩展Binder类
  • Messenger信使
  • AIDL

后面两种可以跨进程通信,是基于Binder机制的通信方式。第一种我们多用于service直接的通信,但是当sevice被设为远程服务时(设为:remote),我们就要用后面两种方式来进行通信了。

Android系统中,如果组件与服务通信是在同一进程,就使用第一种方式;如果是跨进程通信,使用第二种和第三种,两者不同在于,Messenger不能处理多线程并发请求。​

1.扩展 Binder 类

直观来说,Binder是Android中的一个类,它集成了IBinder接口。

从IPC角度来说,Binder是Android中的一种跨进程通信方式;

Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;

从AndroidFramework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁;

从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

如果我们的服务仅供本应用使用,无需跨进程,则可以实现自有 Binder 类,让客户端通过 Binder 类直接访问 Binder 或 Service 中提供的公共方法

如果您的服务仅对您自己的应用专用,并且在同一进程中运行 作为客户端(这很常见),请通过扩展 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(Android Interface Definition Language),它是基于Binder机制的。

Android 接口定义语言 (AIDL) 可将对象分解为 操作系统可以识别这些基元并将其编组到各进程中以执行 IPC。之前采用 Messenger 的方法实际上是以 AIDL 为基础, 及其底层结构。

如上一部分所述,Messenger 会创建一个 所有客户端请求都在一个线程中完成,因此服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,可以直接使用 AIDL。在这种情况下,您的服务必须具备线程安全性,并且能够进行多线程处理。

如需直接使用 AIDL,请执行以下操作: 创建一个定义编程接口的 .aidl 文件。Android SDK 工具会利用该文件生成实现接口和处理 IPC 的抽象类,您随后可在服务内对该类进行扩展。

注意:对于大多数应用来说,AIDL 并不是 创建绑定服务,因为它可能需要多线程处理能力 可以使实现过程更加复杂。

注意:必须使用 Java 编程语言构建 .aidl 文件。

默认情况下,AIDL 支持下列数据类型:

所有基本数据类型(如 int、long、char、boolean 等)

  • String
  • CharSequence
  • List:List 中所有元素必须是以上列出的数据类型或者我们声明的由 AIDL 生成的其他接口或 Parcelable 类型。
  • Map:Map 中所有元素必须是以上列出的数据类型或者我们声明的由 AIDL 生成的其他接口或 Parcelable 类型。

注意:即使在与接口相同的包内定义了上方未列出的其他类型,也必须要通过 import 导入这些类型。

AIDL通信步骤

  • 创建一个.aidl文件,在其中定义接口和方法。

将该文件放置在src/main/aidl/<package_name>/目录下。

  • 在Service中创建一个继承自IMyService.Stub的内部类,并重写定义在AIDL接口中的方法。

在onBind方法中返回该内部类的实例作为Binder对象。

  • 将AIDL文件复制到客户端项目中。
  • 使用bindService方法绑定服务,并通过ServiceConnection回调获取Binder代理对象。
  • 通过该代理对象调用服务端定义的方法。

首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;其次,由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。

举例

服务端代码(RemoteService.kt, IRemoteService)和客户端代码(ClientActivity.kt)demo项目源码下载请见:章节八、源码下载

七、总结

场景使用区别

那么,他们的使用场景分别是什么呢?

如果我们无需跨进程实现绑定服务和同一进程组件之间的通信,则使用 Binder 类即可
如果我们需要跨进程实现绑定服务和其他进程组件之间的通信,且不需要进行多线程处理时,则使用 Messenger
如果我们需要跨进程实现绑定服务和其他进程组件之间的通信,且需要进行多线程处理,则使用 AIDL.

首先,在实现的难度上,肯定是Messenger要简单的多——至少不需要写AIDL文件了(虽然如果认真的究其本质,会发现它的底层实现还是AIDL)。另外,使用Messenger还有一个显著的好处是它会把所有的请求排入队列,因此你几乎可以不用担心多线程可能会带来的问题。

但是这样说来,难道AIDL进行IPC就一无是处了么?当然不是,如果是那样的话它早就被淘汰了。一方面是如果项目中有并发处理问题的需求,或者会有大量的并发请求,这个时候Messenger就不适用了——它的特性让它只能串行的解决请求。另外,我们在使用Messenger的时候只能通过Message来传递信息实现交互,但是在有些时候也许我们需要直接跨进程调用服务端的方法,这个时候又怎么办呢?只能使用AIDL。

八、源码下载

IPC进程间通信demo、客户端(Activity)与服务端(Service)间通信demo,Messenger通信demo,下载源码请见:

https://download.youkuaiyun.com/download/github_27263697/90277464

参考文章

绑定服务概览  |  Background work  |  Android Developers

Android跨进程通信Binder、Messenger、AIDL汇总_安卓 amessage 和 binder-优快云博客

【Android】IPC 之 AIDL、Messenger 、Binder 浅析_安卓 amessage 和 binder-优快云博客

Binder机制原理和AIDL使用Binder机制概述 Binder是Android系统中用于不同进程间通信(IPC)的 - 掘金

​​​​​ Android 之IPC详解_android ipc-优快云博客

【Android】四大组件之 Service_service类型-优快云博客

Android Process 详解_android:process-优快云博客

Service  |  Android Developers

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闲暇部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值