当前位置: 首页 > article >正文

android四大组件之一——Service

目录

一、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有两种方式。通过调用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_CLIENTContext.BIND_ALLOW_OOM_MANAGEMENTContext.BIND_WAIVE_PRIORITYContext.BIND_IMPORTANT, and Context.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的通信方式

  对用于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 绑定到自己在 背景。

以下为设置方式:

  1. 在您的服务中,创建可执行以下某种操作的 Binder 实例:
    • 包含客户端可调用的公共方法。
    • 返回当前的 Service 实例,该实例中包含客户端可调用的公共方法。
    • 返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法。
  2. 从 onBind() 回调方法返回此 Binder 实例。
  3. 在客户端中,从 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 使用方式的摘要:

  1. 服务实现一个 Handler,由其接收来自客户端的每个调用的回调。
  2. 服务使用 Handler 来创建 Messenger 对象(该对象是对 Handler 的引用)。
  3. Messenger 创建一个 IBinder,服务通过 onBind() 将其返回给客户端。
  4. 客户端使用 IBinder 将 Messenger(它引用服务的 Handler)实例化,然后再用其将 Message 对象发送给服务。
  5. 服务在其 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通信步骤

  1. 定义AIDL接口

    • 创建一个.aidl文件,在其中定义接口和方法。
    • 将该文件放置在src/main/aidl/<package_name>/目录下。
  2. 实现AIDL接口

    • 在Service中创建一个继承自IMyService.Stub的内部类,并重写定义在AIDL接口中的方法。
    • onBind方法中返回该内部类的实例作为Binder对象。
  3. 客户端绑定服务

    • 将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。

参考文章

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

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

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

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

​​​​​ Android 之IPC详解_android ipc-CSDN博客


http://www.kler.cn/a/501832.html

相关文章:

  • MyBatis(一)
  • 阿里云存储图像bug修复
  • 4. scala高阶之隐式转换与泛型
  • vue3+vite+ts集成第三方js
  • 【文件锁】多进程线程安全访问文件demo
  • 【初识扫盲】逆概率加权
  • Windows 10 ARM工控主板连接I2S音频芯片
  • 32_Redis分片集群原理
  • 《零基础Go语言算法实战》【题目 2-26】goroutine 的执行效率问题
  • HDFS 的API的操作
  • 【Rust】函数
  • 【网络协议】EIGRP - 第二部分
  • 使用Deepseek搭建类Cursor编辑器
  • SQL语言的计算机基础
  • LeetCode:216.组合总和III
  • 基于单片机的书写坐姿规范提醒器的设计(论文+源码)
  • 自动化机械臂视觉跟踪和手眼校准
  • Docker Swarm、Kubernetes 和 LVS 的功能对比
  • Go语言如何实现高性能缓存服务
  • 青少年编程与数学 02-006 前端开发框架VUE 24课题、UI表单