Android Service - tenji/ks GitHub Wiki

Android Service

一、Service 概述

Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Activity)已销毁也不受影响。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行,Service基本上分为两种形式:

  • 启动状态

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

  • 绑定状态

    当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。

二、Service 在清单文件中的声明

前面说过 Service 分为启动状态和绑定状态两种,但无论哪种具体的 Service 启动类型,都是通过继承 Service 基类自定义而来,也都需要在 AndroidManifest.xml 中声明,那么在分析这两种状态之前,我们先来了解一下 Service 在 AndroidManifest.xml 中的声明语法,其格式如下:

<service android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:isolatedProcess=["true" | "false"]
    android:label="string resource"
    android:name="string"
    android:permission="string"
    android:process="string >
    ...
</service>

三、Service 启动服务

  • onBind()

    当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。无论是启动状态还是绑定状态,此方法必须重写,但在启动状态的情况下直接返回 null。

  • onCreate()

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

  • onStartCommand()

    当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果自己实现此方法,则需要在服务工作完成后,通过调用 stopSelf() 或 stopService() 来停止服务。(在绑定状态下,无需实现此方法。)

  • onDestroy()

    当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。

四、Service 绑定服务

绑定服务是 Service 的另一种变形,当 Service 处于绑定状态时,其代表着客户端-服务器接口中的服务器。当其他组件(如 Activity)绑定到服务时(有时我们可能需要从 Activity 组建中去调用 Service 中的方法,此时 Activity 以绑定的方式挂靠到 Service 后,我们就可以轻松地调用到 Service 中的指定方法),组件(如 Activity)可以向 Service(也就是服务端)发送请求,或者调用 Service(服务端)的方法,此时被绑定的 Service(服务端)会接收信息并响应,甚至可以通过绑定服务进行执行进程间通信 (即 IPC,这个后面再单独分析)。与启动服务不同的是绑定服务的生命周期通常只在为其他应用组件(如 Activity)服务时处于活动状态,不会无限期在后台运行,也就是说宿主(如 Activity)解除绑定后,绑定服务就会被销毁。那么在提供绑定的服务时,该如何实现呢?实际上我们必须提供一个 IBinder 接口的实现类,该类用以提供客户端用来与服务进行交互的编程接口,该接口可以通过三种方法定义接口:

  • 扩展 Binder 类:

    如果服务是提供给自有应用专用的,并且 Service(服务端)与客户端相同的进程中运行(常见情况),则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Binder 实现中以及 Service 中可用的公共方法。如果我们的服务只是自有应用的后台工作线程,则优先采用这种方法。不采用该方式创建接口的唯一原因是,服务被其他应用或不同的进程调用。

  • 使用 Messenger:

    Messenger 可以翻译为信使,通过它可以在不同的进程中共传递 Message 对象(Handler 中的 Messager,因此 Handler 是 Messenger 的基础),在 Message 中可以存放我们需要传递的数据,然后在进程间传递。如果需要让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口,客户端就可利用 Message 对象向服务发送命令。同时客户端也可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,也就是说 Messenger 是以串行的方式处理客户端发来的消息,这样我们就不必对服务进行线程安全设计了。

  • 使用 AIDL:

    由于 Messenger 是以串行的方式处理客户端发来的消息,如果当前有大量消息同时发送到 Service(服务端),Service 仍然只能一个个处理,这也就是 Messenger 跨进程通信的缺点了,因此如果有大量并发请求,Messenger 就会显得力不从心了,这时 AIDL(Android 接口定义语言)就派上用场了,但实际上 Messenger 的跨进程方式其底层实现就是 AIDL,只不过 android 系统帮我们封装成透明的 Messenger 罢了。因此,如果我们想让服务同时处理多个请求,则应该使用 AIDL。在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。使用 AIDL 必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展。

以上3种实现方式,我们可以根据需求自由的选择,但需要注意的是大多数应用“都不会”使用 AIDL 来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL 并不适合大多数应用,本篇中也不打算阐述如何使用 AIDL(后面会另开一篇分析AIDL),接下来我们分别针对扩展 Binder 类和 Messenger 的使用进行分析。

4.1 扩展 Binder 类

4.2 使用 Messenger

前面了解了如何使用 IBinder 应用内同一进程的通信后,我们接着来了解服务与远程进程(即不同进程间)通信,而不同进程间的通信,最简单的方式就是使用 Messenger 服务提供通信接口,利用此方式,我们无需使用 AIDL 便可执行进程间通信 (IPC)。以下是 Messenger 使用的主要步骤:

  1. 服务实现一个 Handler,由其接收来自客户端的每个调用的回调;
  2. Handler 用于创建 Messenger 对象(对 Handler 的引用);
  3. Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端;
  4. 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用 Messenger 将 Message 对象发送给服务;
  5. 服务在其 Handler 中(在 handleMessage() 方法中)接收每个 Message;

4.3 关于绑定服务的注意点

待更新...

五、关于启动服务与绑定服务间的转换问题

通过前面对两种服务状态的分析,相信大家已对Service的两种状态有了比较清晰的了解,那么现在我们就来分析一下当启动状态和绑定状态同时存在时,又会是怎么的场景?

虽然服务的状态有启动和绑定两种,但实际上一个服务可以同时是这两种状态,也就是说,它既可以是启动服务(以无限期运行),也可以是绑定服务。有点需要注意的是Android系统仅会为一个 Service 创建一个实例对象,所以不管是启动服务还是绑定服务,操作的是同一个Service实例,而且由于绑定服务或者启动服务执行顺序问题将会出现以下两种情况:

  • 先绑定服务后启动服务

    如果当前 Service 实例先以绑定状态运行,然后再以启动状态运行,那么绑定服务将会转为启动服务运行,这时如果之前绑定的宿主(Activity)被销毁了,也不会影响服务的运行,服务还是会一直运行下去,指定收到调用停止服务或者内存不足时才会销毁该服务。

  • 先启动服务后绑定服务

    如果当前 Service 实例先以启动状态运行,然后再以绑定状态运行,当前启动服务并不会转为绑定服务,但是还是会与宿主绑定,只是即使宿主解除绑定后,服务依然按启动服务的生命周期在后台运行,直到有 Context 调用了 stopService() 或是服务本身调用了 stopSelf() 方法抑或内存不足时才会销毁服务。

以上两种情况显示出启动服务的优先级确实比绑定服务高一些。不过无论 Service 是处于启动状态还是绑定状态,或处于启动并且绑定状态,我们都可以像使用 Activity 那样通过调用 Intent 来使用服务(即使此服务来自另一应用)。 当然,我们也可以通过清单文件将服务声明为私有服务,阻止其他应用访问。最后这里有点需要特殊说明一下的,由于服务在其托管进程的主线程中运行(UI 线程),它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。 这意味着,如果服务将执行任何耗时事件或阻止性操作(例如 MP3 播放或联网)时,则应在服务内创建新线程来完成这项工作,简而言之,耗时操作应该另起线程执行。只有通过使用单独的线程,才可以降低发生“应用无响应”(ANR) 错误的风险,这样应用的主线程才能专注于用户与 Activity 之间的交互, 以达到更好的用户体验。

六、前台服务以及通知发送

待更新...

七、服务 Service 与线程 Thread 的区别

7.1 两者概念的迥异

  • Thread 是程序执行的最小单元,它是分配 CPU 的基本单位,android 系统中 UI 线程也是线程的一种,当然 Thread 还可以用于执行一些耗时异步的操作;
  • Service 是 Android 的一种机制,服务是运行在主线程上的,它是由系统进程托管。它与其他组件之间的通信类似于 client 和 server,是一种轻量级的 IPC 通信,这种通信的载体是 binder,它是在 linux 层交换信息的一种 IPC,而所谓的 Service 后台任务只不过是指没有 UI 的组件罢了。

7.2 两者的执行任务迥异

  • 在 android 系统中,线程一般指的是工作线程(即后台线程),而主线程是一种特殊的工作线程,它负责将事件分派给相应的用户界面小工具,如绘图事件及事件响应,因此为了保证应用 UI 的响应能力主线程上不可执行耗时操作。如果执行的操作不能很快完成,则应确保它们在单独的工作线程执行;
  • Service 则是 android 系统中的组件,一般情况下它运行于主线程中,因此在 Service 中是不可以执行耗时操作的,否则系统会报 ANR 异常,之所以称 Service 为后台服务,大部分原因是它本身没有 UI,用户无法感知(当然也可以利用某些手段让用户知道),但如果需要让 Service 执行耗时任务,可在 Service 中开启单独线程去执行。

7.3 两者使用场景

  • 当要执行耗时的网络或者数据库查询以及其他阻塞 UI 线程或密集使用 CPU 的任务时,都应该使用工作线程(Thread),这样才能保证UI线程不被占用而影响用户体验;
  • 在应用程序中,如果需要长时间的在后台运行,而且不需要交互的情况下,使用服务。比如播放音乐,通过 Service + Notification 方式在后台执行同时在通知栏显示着。

7.4 两者的最佳使用方式

在大部分情况下,Thread 和 Service 都会结合着使用,比如下载文件,一般会通过 Service 在后台执行 + Notification 在通知栏显示 + Thread 异步下载,再如应用程序会维持一个 Service 来从网络中获取推送服务。在 Android 官方看来也是如此,所以官网提供了一个 Thread 与 Service 的结合来方便我们执行后台耗时任务,它就是 IntentService,当然 IntentService 并不适用于所有的场景,但它的优点是使用方便、代码简洁,不需要我们创建 Service 实例并同时也创建线程,某些场景下还是非常赞的!由于 IntentService 是单个 Worker Thread,所以任务需要排队,因此不适合大多数的多任务情况。

7.5 两者的真正关系

没有关系。

八、管理服务生命周期

关于 Service 生命周期方法的执行顺序,前面我们已分析得差不多了,这里重新给出一张执行的流程图:

其中左图显示了使用 startService() 所创建的服务的生命周期,右图显示了使用 bindService() 所创建的服务的生命周期。通过图中的生命周期方法,我们可以监控Service的整体执行过程,包括创建,运行,销毁。从执行流程图来看,服务的生命周期比 Activity 的生命周期要简单得多。但是,我们必须密切关注如何创建和销毁服务,因为服务可以在用户没有意识到的情况下运行于后台。管理服务的生命周期(从创建到销毁)有以下两种情况:

  • 启动服务

    该服务在其他组件调用 startService() 时创建,然后无限期运行,且必须通过调用 stopSelf() 来自行停止运行。此外,其他组件也可以通过调用 stopService() 来停止服务。服务停止后,系统会将其销毁。

  • 绑定服务

    该服务在另一个组件(客户端)调用 bindService() 时创建。然后,客户端通过 IBinder 接口与服务进行通信。客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。 (服务不必自行停止运行)

虽然可以通过以上两种情况管理服务的生命周期,但是我们还必须考虑另外一种情况,也就是启动服务与绑定服务的结合体,也就是说,我们可以绑定到已经使用 startService() 启动的服务。例如,可以通过使用 Intent(标识要播放的音乐)调用 startService() 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 bindService() 绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 stopService() 或 stopSelf() 不会真正停止服务。因此在这种情况下我们需要特别注意。

九、Android 5.0 以上的隐式启动问题

待更新...

十、如何保证服务不被杀死

待更新...

参考链接

⚠️ **GitHub.com Fallback** ⚠️