万字长文带你走进Android service(服务)的理解与实战


服务概括

Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。服务的运行不依赖于任何操作界面,当用户界面切换到后台或用户打开了另一个应用程序,服务仍然可以保持正常运行。

与其他应用程序对象一样,服务在其托管进程的主线程中运行。这意味着,如果您的服务要执行任何CPU密集型(如MP3回放)或阻塞(如网络)操作,它应该生成自己的线程来完成这些工作。也就是说,我们创建服务时,要在服务内部手动创建子线程,否则就会出现程序阻塞的情况。JobIntentService类可以作为Service的标准实现,它有自己的线程,在线程中调度要完成的工作。

服务不是一个单独的进程。Service对象本身并不意味着它在自己的进程中运行;除非另有说明,否则它与它所属的应用程序运行在同一个进程中。这就意味着,当某个应用程序的进程被杀掉时,所有依赖于该进程的服务也会停止运行。

注意:服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应通过在服务内创建新线程来完成这项工作。通过使用单独的线程,您可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。

服务提供两个主要的特性:

  • 如果应用程序想要在后台执行一些任务,那么service就会是很好的使用方式,应用程序会在服务里定义好它想要做的事情,然后告诉给系统。即使用户没有直接和应用程序交互。这对应于对Context.startService() 的调用,它要求系统为服务安排工作,直到服务或其他人显式地停止它。
  • 应用程序为其他应用程序提供部分服务和功能,这对应于对Context.bindService() 的调用,它允许建立到服务的长期连接,以便与之交互。这也是进程之间的通信。

当实际创建Service组件时,出于上述任何一个原因,系统实际所做的就是实例化该组件,并在主线程上调用它的onCreate()和任何其他适当的回调。由服务使用适当的行为来实现这些功能,例如创建一个辅助线程,它在其中完成工作。注意,因为Service本身非常简单,您可以随心所欲地使与它的交互变得简单或复杂:无论是将其视为您可以对其进行直接方法调用的本地Java对象,如下面提到的本地service示例,还是使用AIDL提供完整的远程接口。

服务生命周期

系统可以运行服务有两个原因。如果有人调用Context.startService(),那么系统将检索服务(创建它并在需要时调用它的onCreate()方法),然后用客户端提供的参数调用它的onStartCommand(Intent, int, int)方法。此时,服务将继续运行,直到调用Context.stopService()或stopSelf()。注意,对Context.startService()的多次调用不会嵌套(尽管它们会导致对onStartCommand()的多次对应调用),因此无论它启动了多少次,只要调用Context.stopService()或stopSelf(),服务就会停止;然而,服务可以使用它们的stopSelf(int)方法来确保服务在已启动的意图被处理之前不会停止。

对于已经启动的服务,根据onStartCommand()返回的值,有两种额外的主要操作模式可以决定它们是否运行:

  • START_STICKY 用于在需要时显式启动和停止的服务:这种模式适用于显式启动和停止以运行任意时间段的内容,例如执行背景音乐回放的服务。从**onStartCommand(Intent, int, int)**返回的常量:如果该服务的进程在启动时被杀死(从onStartCommand(Intent, int, int)返回后),那么将其保持在启动状态,但不保留此传递的意图。稍后,系统将尝试重新创建服务。因为它处于启动状态,它将保证在创建新的服务实例后调用onStartCommand(Intent, int, int);如果没有任何挂起的启动命令要交付给服务,它将被调用一个空意图对象,因此您必须注意检查这一点。
  • START_NOT_STICKY 从onStartCommand(Intent, int, int)返回的常量:如果该服务的进程在启动时被杀死(从onStartCommand(Intent, int, int)返回后),并且没有新的启动意图交付给它,那么将服务从启动状态中取出,直到未来显式调用Context.startService(Intent)才重新创建。该服务将不会接收onStartCommand(android.content. conf)。Intent, int, int)调用null Intent,因为如果没有挂起的Intent要交付,它将不会重新启动。这种模式对于那些由于启动而想要做一些工作,但在内存压力下可以停止,并在稍后显式重新启动自己以做更多工作的事情是有意义的。这样的服务的一个例子就是轮询来自服务器的数据:它可以安排一个闹钟每N分钟轮询一次,让闹钟启动它的服务。当它的onStartCommand(Intent, int, int)从警报被调用时,它会在N分钟后安排一个新的警报,并生成一个线程来进行网络连接。如果它的进程在执行该检查时被杀死,服务将不会重新启动,直到警报响起。
  • START_REDELIVER_INTENT 用于在处理发送给它们的任何命令时应该保持运行的服务。从onStartCommand(Intent, int, int)返回的常量:如果这个服务的进程在启动时被杀死(从onStartCommand(Intent, int, int)返回后),那么它将被安排重新启动,并通过onStartCommand(Intent, int, int)再次重新传递给它。这个Intent将一直被调度重新交付,直到服务调用stopSelf(int)并将开始ID提供给onStartCommand(Intent, int, int)。该服务将不会接收onStartCommand(android.content. conf)。Intent, int, int)调用null Intent,因为它只会在未完成处理时重新启动

客户端还可以使用Context.bindService()来获取到服务的持久连接。这同样创建服务,如果它还没有运行(调用onCreate()而这样做),但不调用onStartCommand()。客户端将接收服务从其onBind(Intent)方法返回的IBinder对象,允许客户端随后对服务进行回调。只要建立了连接,服务就会一直运行(不管客户端是否保留了对服务IBinder的引用)。通常返回的IBinder用于用aidl编写的复杂接口。

服务既可以启动,也可以有绑定到它的连接。在这种情况下,只要服务处于启动状态,或者存在一个或多个与它的上下文连接,系统就会通过Context.BIND_AUTO_CREATE 标志保持服务运行。如果这两种情况都不存在,则调用服务的onDestroy()方法,并有效地终止服务。所有的清理(停止线程,取消注册接收器)应该在从onDestroy()返回时完成。

当调用了 startService()方法后,又去调用 stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了 bindService()方法后又去调用 unbindservice()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了 startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据 Android系统的机制,个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用 stopService()和 unbindService()方法onDestroy()方法才会执行。

服务类型

1. 前台
前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。即使用户停止与应用的交互,前台服务仍会继续运行。
如喜马拉雅app,当播放一个故事后,将应用切换到后台进程后,故事还能继续播放,同时还能创建一个通知,这个就是前台服务,如下所示:
在这里插入图片描述
当进程杀掉后,前台服务的通知也会消失。

2. 后台
后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。当应用本身未在前台运行时,系统会对运行后台服务施加限制。也就是说后台服务是和内存有关的。每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,如果用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响会尤为明显。 为了提升用户体验,Android 8.0(API 级别 26)对应用在后台运行时可以执行的操作施加了限制。
在后台中运行的 Service 会消耗设备资源,这可能会降低用户体验。 为了缓解这一问题,系统对这些 Service 施加了一些限制。

系统可以区分前台和后台应用。 (用于 Service 限制目的的后台定义与内存管理使用的定义不同;一个应用按照内存管理的定义可能处于后台,但按照能够启动 Service 的定义又处于前台。)如果满足以下任意条件,应用将被视为处于前台:

  • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
  • 具有前台 Service。
  • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个 Service,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的 Service,那么该应用处于前台:
    • IME
    • 壁纸 Service
    • 通知侦听器
    • 语音或文本 Service
      如果以上条件均不满足,应用将被视为处于后台。

处于前台时,应用可以自由创建和运行前台与后台 Service。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用 Service。 在该时间窗结束后,应用将被视为处于空闲状态。 此时,系统将停止应用的后台 Service,就像应用已经调用 Service 的 Service.stopSelf() 方法一样。

在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动 Service,并且其后台 Service 也可以运行。 处理对用户可见的任务时,应用将被置于白名单中。

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

服务类型的特点及场景

服务类别 特点 优点 缺点 应用场景
本地服务(LocalService) 1. 远行在主线程 2.主进程被终止后,服务也会终止。 1.节约资源;2.通信方便: 由于在同一进程因此不需要IPC和AIDL。 限制性大: 主进程被终止后,服务也会终止。 (最常用)需要依附某个进程的服务,如音乐播放
远程服务(BinderService) 1. 运行在独立进程;2.服务常驻在后台,不受其他Activity影响 灵活:服务常驻在后台,不受其他Activity影响 1.消耗资源:单独进程2.使用AIDL进行IPC通信复杂 系统级别服务,多个应用之间跨进程通信
前台服务 在通知栏显示通知(用户可以看到) 服务使用时需要让用户知道并进行相关操作,如音乐插放服务。(服务被终止的时候,通知栏的通知也会消失)
后台服务 处于后台的服务(用户无法看到) 服务使用时不需要让用户知道并进行相关操作,如天气更新,日期同步(服务被终止的时候,用户是无法知道的)
可通信的后台服务 1. 用bindService启动 2.调用者退出后,随着调用者销毁 该后台服务需要进行通信
不可通信的后台服务 1. 用startService启动 2.调用者退出后service仍然存在 该后台服务不进行任何通信

服务与线程

上文中已有描述,这里做一个总结:

简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件,因此,只有在需要服务时才应创建服务。

如果您必须在主线程之外执行操作,但只在用户与您的应用交互时执行此操作,则应创建新线程。例如,如果您只是想在 Activity 运行的同时播放一些音乐,则可在 onCreate() 中创建线程,在 onStart() 中启动线程运行,然后在 onStop() 中停止线程。您还可考虑使用 AsyncTaskHandlerThread,而非传统的 Thread 类。

请记住,如果您确实要使用服务,则默认情况下,它仍会在应用的主线程中运行,因此,如果服务执行的是密集型或阻止性操作,则您仍应在服务内创建新线程。


创建服务与使用服务

服务的创建

如要创建服务,必须创建 Service 的子类(或使用它的一个现有子类)。在实现中,必须重写一些回调方法,从而处理服务生命周期的某些关键方面,并提供一种机制将组件绑定到服务(如适用)。以下是重写的最重要的回调方法:

1. onStartCommand()
当另一个组件(如 Activity)请求启动服务时,系统会通过调用 startService() 来调用此方法。执行此方法时,服务即会启动并可在后台无限期运行。如果您实现此方法,则在服务工作完成后,您需负责通过调用 stopSelf() 或 stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。)
2. onBind()
当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用 bindService() 来调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,以供客户端用来与服务进行通信。请务必实现此方法;但是,如果您并不希望允许绑定,则应返回 null。
3. onCreate()
首次创建服务时,系统会(在调用 onStartCommand() 或 onBind() 之前)调用此方法来执行一次性设置程序。如果服务已在运行,则不会调用此方法。
4. onDestroy()
当不再使用服务且准备将其销毁时,系统会调用此方法。服务应通过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。

如果组件通过调用 startService() 启动服务(这会引起对 onStartCommand() 的调用),则服务会一直运行,直到其使用 stopSelf() 自行停止运行,或由其他组件通过调用 stopService() 将其停止为止。

如果组件通过调用 bindService() 来创建服务,且未调用 onStartCommand(),则服务只会在该组件与其绑定时运行。当该服务与其所有组件取消绑定后,系统便会将其销毁。

只有在内存过低且必须回收系统资源以供拥有用户焦点的 Activity 使用时,Android 系统才会停止服务。如果将服务绑定到拥有用户焦点的 Activity,则它其不太可能会终止;如果将服务声明为在前台运行,则其几乎永远不会终止。如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升—如果服务是启动服务,则您必须将其设计为能够妥善处理系统执行的重启。如果系统终止服务,则其会在资源可用时立即重启服务,但这还取决于从 onStartCommand() 返回的值。

创建启动服务

启动的服务是由其他一些 Android 组件启动的服务, ((例如活动) )启动,在后台持续运行,直到有一些明确告知服务停止。 与绑定服务不同,启动的服务没有直接绑定到它的任何客户端。 因此,设计启动的服务非常重要,以便根据需要正常重启这些服务。

启动服务由另一个组件通过调用 startService() 启动,这会导致调用服务的onStartCommand() 方法。

服务启动后,其生命周期即独立于启动它的组件。即使系统已销毁启动服务的组件,该服务仍可在后台无限期地运行。因此,服务应在其工作完成时通过调用 stopSelf() 来自行停止运行,或者由另一个组件通过调用 stopService() 来将其停止

应用组件(如 Activity)可通过调用 startService() 方法并传递 Intent 对象(指定服务并包含待使用服务的所有数据)来启动服务。服务会在 onStartCommand() 方法接收此 Intent

例如,假设某 Activity 需要将一些数据保存到在线数据库中。该 Activity 可以启动一个协同服务,并通过向 startService() 传递一个 Intent,为该服务提供要保存的数据。服务会通过 onStartCommand() 接收 Intent,连接到互联网并执行数据库事务。事务完成后,服务将自行停止并销毁。

注意:默认情况下,服务与服务声明所在的应用运行于同一进程,并且运行于该应用的主线程中。如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。为避免影响应用性能,请在服务内启动新线程。

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

  • Service
    这是适用于所有服务的基类。扩展此类时,您必须创建用于执行所有服务工作的新线程,因为服务默认使用应用的主线程,这会降低应用正在运行的任何 Activity 的性能。
  • IntentService
    这是 Service 的子类,其使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,此类为最佳选择。实现 onHandleIntent(),该方法会接收每个启动请求的 Intent,以便您执行后台工作。

这里先着重说一下IntentService目前的情况。 IntentService是Service组件类的扩展,用于按需处理异步请求(表示为Intents)。客户端通过Context.startService(Intent)调用发送请求;服务在需要时启动,使用工作线程依次处理每个Intent,并在工作结束时停止自己。

这种“工作队列处理器”模式通常用于从应用程序主线程卸载任务。IntentService类的存在是为了简化这个模式并处理机制。要使用它,扩展IntentService并实现onHandleIntent(android.content.Intent)。IntentService将接收intent,启动工作线程,并在适当的时候停止服务。

所有请求都在一个工作线程上处理——它们可能需要很长时间(并且不会阻塞应用程序的主循环),但一次只处理一个请求。

然而,在官网中明确指出:这个类在API级别30中已弃用。IntentService受Android 8.0 (API级别26)施加的所有后台执行限制。考虑改用WorkManager。 ,这里顺带提一下:

WorkManager是持久工作的推荐库。预定的工作保证在满足其约束之后的某个时间执行。WorkManager允许观察工作状态,并能够创建复杂的工作链。

WorkManager根据以下标准使用底层作业调度服务:

  • 使用JobScheduler API 23+
  • 使用API 14-22的自定义AlarmManager + BroadcastReceiver实现
    所有工作都必须在ListenableWorker类中完成。对于大多数开发人员,建议使用一个简单的实现Worker作为起点。有了可选的依赖项,你还可以使用CoroutineWorker或RxWorker。所有后台工作最多有十分钟的时间来完成执行。超过这个时间后,工人将被示意停止工作。

WorkManager支持两种类型的工作:OneTimeWorkRequest和PeriodicWorkRequest。可以使用WorkManager进行请求排队。

IntentService 使用

由于大多数启动服务无需同时处理多个请求(实际上,这种多线程情况可能很危险),因此最佳选择是利用 IntentService 类实现服务。

IntentService 类会执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
  • 创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
  • 在处理完所有启动请求后自动停止服务,因此您永远不必调用 stopSelf()。
  • 提供 onBind() 的默认实现(返回 null)。
  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。
    如要完成客户端提供的工作,请实现 onHandleIntent()。不过,您还需为服务提供小型构造函数。

以下是 IntentService 的实现示例:

/**
 * 用于在单独的处理程序线程上处理服务中的异步任务请求。
 */
public class MyIntentService extends IntentService {
    
    

    // TODO: Rename actions, choose action names that describe tasks that this
    // IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
    private static final String ACTION_FOO = "com.study_android_exact.action.FOO";
    private static final String ACTION_BAZ = "com.study_android_exact.action.BAZ";

    // TODO: Rename parameters
    private static final String EXTRA_PARAM1 = "com.study_android_exact.extra.PARAM1";
    private static final String EXTRA_PARAM2 = "com.study_android_exact.extra.PARAM2";

    public MyIntentService() {
    
    
        super("MyIntentService");
    }

    /**
     * 启动此服务以使用给定参数执行动作Foo。如果服务已经在执行某个任务,则此操作将被排队。
     * @see IntentService
     */
    // TODO: Customize helper method
    public static void startActionFoo(Context context, String param1, String param2) {
    
    
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_FOO);
        intent.putExtra(EXTRA_PARAM1, param1);
        intent.putExtra(EXTRA_PARAM2, param2);
        context.startService(intent);
    }

    /**
     * 启动此服务以使用给定参数执行动作Baz。如果服务已经在执行某个任务,则此操作将被排队。
     * @see IntentService
     */
    // TODO: Customize helper method
    public static void startActionBaz(Context context, String param1, String param2) {
    
    
        Intent intent = new Intent(context, MyIntentService.class);
        intent.setAction(ACTION_BAZ);
        intent.putExtra(EXTRA_PARAM1, param1);
        intent.putExtra(EXTRA_PARAM2, param2);
        context.startService(intent);
    }

    /**
     * IntentService从默认的工作线程调用这个方法,它的意图是启动服务。当该方法返回时,IntentService会适当地停止服务。
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {
    
    
        if (intent != null) {
    
    
            final String action = intent.getAction();
                if (ACTION_FOO.equals(action)) {
    
    
                    final String param1 = intent.getStringExtra(EXTRA_PARAM1);
                    final String param2 = intent.getStringExtra(EXTRA_PARAM2);
                    handleActionFoo(param1, param2);
                } else if (ACTION_BAZ.equals(action)) {
    
    
                    final String param1 = intent.getStringExtra(EXTRA_PARAM1);
                    final String param2 = intent.getStringExtra(EXTRA_PARAM2);
                    handleActionBaz(param1, param2);
                }
        }
    }

    /**
     * 在提供的后台线程中使用提供的参数处理动作Foo。
     */
    private void handleActionFoo(String param1, String param2) {
    
    
        // TODO:  在这里处理一些工作,比如文件下载,这里只是睡眠5秒
        try{
    
    
            Thread.sleep(5000);
        }catch (InterruptedException e){
    
    
                  // Restore interrupt status.
            Thread.currentThread().interrupt();
        }
    }

    /**
     *  在提供的后台线程中使用提供的参数处理动作Baz。
     */
    private void handleActionBaz(String param1, String param2) {
    
    
        // TODO:  在这里处理一些工作,比如文件下载,这里只是睡眠5秒
        try{
    
    
            Thread.sleep(5000);
        }catch (InterruptedException e){
    
    
            // Restore interrupt status.
            Thread.currentThread().interrupt();
        }
    }
}

您只需要一个构造函数和一个 onHandleIntent() 实现即可。

如果您还决定重写其他回调方法(如 onCreate()、onStartCommand() 或 onDestroy()),请确保调用超类实现,以便 IntentService 能够妥善处理工作线程的生命周期。

例如,onStartCommand() 必须返回默认实现,即如何将 Intent 传递给 onHandleIntent():

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    
    
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}

除 onHandleIntent() 之外,您无需从中调用超类的唯一方法就是 onBind()。只有在服务允许绑定时,您才需实现该方法。

在下一部分中,您将了解如何在扩展 Service 基类时实现同类服务,此类包含更多代码,但如需同时处理多个启动请求,则更适合使用该基类。

扩展Service类

借助 IntentService,可以非常轻松地实现启动服务。但是,若要求服务执行多线程(而非通过工作队列处理启动请求),则可通过扩展 Service 类来处理每个 Intent。

为进行比较,以下示例代码展示了 Service 类的实现,对于每个启动请求,其均使用工作线程来执行作业,且每次仅处理一个请求。下面是一个示例:

public class MyService extends Service {
    
    

    public MyService() {
    
    
    }

    private Looper serviceLooper;
    private ServiceHandler serviceHandler;

    // 从当前线程接收消息的处理程序
    private final class ServiceHandler extends Handler {
    
    
        public ServiceHandler(Looper looper) {
    
    
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
    
    
            try {
    
    
                Thread.sleep(5000);
                Log.i("Myservice","当前进程编号"+ Thread.currentThread().getName()+" ·····正在处理任务");
            } catch (InterruptedException e) {
    
    
                Thread.currentThread().interrupt();
            }
          //服务处理完成后,使用startId停止服务,这样我们就不会在处理另一个作业时停止服务
            stopSelf(msg.arg1);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
    
    
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
    
    
        Log.i("Myservice","onCreate");
        //启动运行该服务的线程。因为默认情况下服务通常运行在进程的主线程中,我们不希望阻塞主线程。所以创建了一个单独的线程,
        // 我们还将其设置为后台优先级,这样cpu密集型工作就不会破坏我们的UI。
        HandlerThread thread = new HandlerThread("ServiceStartArguments",
                Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        // Get the HandlerThread's Looper and use it for our Handler
        serviceLooper = thread.getLooper();
        serviceHandler = new ServiceHandler(serviceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    
    
        Log.i("Myservice","onStartCommand");
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

//对于每个开始请求,发送一个消息来开始一个作业,并传递启动ID,这样我们就知道当我们完成作业时我们正在停止哪个请求
        Message msg = serviceHandler.obtainMessage();
        msg.arg1 = startId;
        //在当前线程下执行服务的任务。
        serviceHandler.sendMessage(msg);

        // If we get killed, after returning from here, restart
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
    
    
        super.onDestroy();
        Log.i("Myservice","onDestroy");
    }
}

可以看到,这里重写了 onCreate()onStartCommand()onDestroy() 这 3个方法,它们是每个服务中最常用到的 3 个方法了。其中 onCreate() 方法会在服务创建的时候调用onstartCommand() 方法会在每次服务启动的时候调用,onDestroy() 方法会在服务销毁的时候调用。
通常情况下,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在onstartCommand()方法里。而当服务销毁时,我们又应该在 onDestroy()方法中去回收那些不再使用的资源。

相较于使用 IntentService,此示例需要执行更多工作。比如,我们需要手动进行服务的停止,而IntentService却可以自己处理

但是,由于 onStartCommand() 的每个调用均有您自己处理,因此您可以同时执行多个请求。此示例并未这样做,但若您希望如此,则可为每个请求创建新线程,然后立即运行这些线程(而非等待上一个请求完成)。

请注意,onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应如何在系统终止服务的情况下继续运行服务。IntentService 的默认实现会为您处理此情况,但您可以对其进行修改。从 onStartCommand() 返回的值必须是以下常量之一:

  • START_NOT_STICKY
    如果系统在 onStartCommand() 返回后终止服务,则除非有待传递的挂起 Intent,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  • START_STICKY
    如果系统在 onStartCommand() 返回后终止服务,则其会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会调用包含空 Intent 的 onStartCommand()。在此情况下,系统会传递这些 Intent。此常量适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。
  • START_REDELIVER_INTENT
    如果系统在 onStartCommand() 返回后终止服务,则其会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。所有挂起 Intent 均依次传递。此常量适用于主动执行应立即恢复的作业(例如下载文件)的服务。
在清单中声明服务

服务类写好了,需要在清单文件中声明:

<application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Study_Android_Exact"
        tools:targetApi="31">
        <service
            android:name=".MyIntentService"
            android:exported="false"></service>
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true" />

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
启动服务

您可以通过将 Intent 传递给 startService()startForegroundService(),从 Activity 或其他应用组件启动服务。Android 系统会调用服务的 onStartCommand() 方法,并向其传递 Intent,从而指定要启动的服务。
下面创建一个启动示例:

  1. 首先创建一个布局文件,给布局文件添加,启动服务的按钮
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/startService"
        android:text="测试启动服务" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/stopService"
        android:text="测试停止服务"/>
</LinearLayout>
  1. 在MainActivity中创建事件如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_service_layout);
        Button startServiceButton = (Button) findViewById(R.id.startService);
        Button stopServiceButton = (Button) findViewById(R.id.stopService);
        startServiceButton.setOnClickListener(this);
        stopServiceButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()){
    
    
            case R.id.startService:
                Intent startIntent = new Intent(this, MyService.class);
                //启动一个普通后台服务
                startService(startIntent);
              
                break;

            case R.id.stopService:
                Intent stopIntent = new Intent(this, MyService.class);
                stopService(stopIntent);
                break;

            default:
                break;
        }


    }
}

上述案例中,在Activity中通过显示的intent调用了startService() 方法并会立即返回,Android 系统会调用服务的 onStartCommand() 方法。如果服务尚未运行,则系统首先会调用 onCreate(),然后调用 onStartCommand()。这说明onCreate()方法只在创建服务之初调用一次。

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

多个服务启动请求会导致多次对服务的 onStartCommand() 进行相应的调用。但是,如要停止服务,只需一个服务停止请求(使用 stopSelf() 或 stopService())即可。

这是上述案例中的启动日志截图,如下所示:
在这里插入图片描述
然而:从 Android 8.0 (API 级别 26) 开始,Android 应用程序不再能够在后台自由运行。 在前台时,应用可以不受限制地启动和运行服务。 当应用程序进入后台时,Android 将授予应用一定的时间启动和使用服务。 该时间过后,应用将不再启动任何服务,并且启动的任何服务都将终止。 此时,应用无法执行任何工作。 故在日志中又回出现如下图所示的日志:
在这里插入图片描述
在不做任何操作的情况下,启动服务超过一分钟后系统就会自动将服务销毁。官方也在后台限制中给出了答案:

启动前台服务。

- 调用startForegroundService启动服务
- 在服务的onStartCommand中调用startForeground (5s内调用,否则会导致ANR),并设置Nofitication

鉴于以上案例,如果想查看自己的服务是否启动,可以从模拟器的运行服务列表中查看。当然得是在系统不自动销毁服务的前提下。首先在模拟器中开启开发人员选项即: Developer Options :

settings->About emulated device->build number

重复多次点击build number 直到出现 你已经是developer 类似的提示即可。
在这里插入图片描述
随后在settings 中搜索Developer Options 并点击打开->Running services
在这里插入图片描述
此时会看到当前应用进程的服务 MyService
在这里插入图片描述
到此启动服务就演示完了,通过此案例,我们可以看到从调用启动服务到服务销毁的整个服务的生命周期,并能清楚的知道其中的调用链路。下面将介绍一下停止服务。

注意:如果您的应用面向 API 级别 26 或更高版本,除非应用本身在前台运行,否则系统不会对使用或创建后台服务施加限制。如果应用需要创建前台服务,则其应调用 startForegroundService()。此方法会创建后台服务,但它会向系统发出信号,表明服务会将自行提升至前台。创建服务后,该服务必须在五秒内调用自己的 startForeground() 方法。

停止服务

启动服务必须管理自己的生命周期。换言之,除非必须回收内存资源,否则系统不会停止或销毁服务,并且服务在 onStartCommand() 返回后仍会继续运行。服务必须通过调用 stopSelf() 自行停止运行,或由另一个组件通过调用 stopService() 来停止它。

一旦请求使用 stopSelf()stopService() 来停止服务,系统便会尽快销毁服务。

如果服务同时处理多个对 onStartCommand() 的请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已收到新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为避免此问题,您可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。换言之,在调用 stopSelf(int) 时,您需传递与停止请求 ID 相对应的启动请求 ID(传递给 onStartCommand() 的 startId)。此外,如果服务在您能够调用 stopSelf(int) 之前收到新启动请求,则 ID 不匹配,服务也不会停止。

注意:为避免浪费系统资源和消耗电池电量,请确保应用在工作完成之后停止其服务。如有必要,其他组件可通过调用 stopService() 来停止服务。即使为服务启用绑定,如果服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务。


总结

本文先介绍到这里,主要介绍了服务中的一些特性,比如服务的应用场景、服务的整体概述、服务的启动与销毁,并通过案例演示了服务的生命周期。下一篇文章将介绍服务的其他特性,如:前台服务、服务与其他组件的交互过程、发送通知及绑定服务等。再下一文章中介绍Android中的进程与线程,紧接着介绍binder机制与原理,逐渐深入。

猜你喜欢

转载自blog.csdn.net/superzhang6666/article/details/129424926