Android Service 知识总结

2,057 阅读28分钟

这是我参与更文挑战的第8天,活动详情查看: 更文挑战

本文基于 Android 9.0.0 的源代码

framework/base/core/java/andorid/app/Service.java

简介

引用官方定义:

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

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

Service是一个可以在后台执行长时间操作而不使用用户界面的应用组件。那么问题来了,既然它不使用用户界面,那么它怎么知道应该什么时候开始执行什么操作呢?答案是——它可以与其他的引用组件形成一些联系,从而可以根据其传来的信息在合适的时候执行合适的操作。

一般来讲,这种联系分为两种:startService()以及bindService()。这两种联系都可以使得一个Service开始运行,但是在其他方面有着诸多不同。

启动service的方式停止service的方式service与启动它的组件之间的通信方式service的生命周期
startService在其他组件中调用startService()方法后,服务即处于启动状态service中调用stopSelf()方法,或者其他组件调用stopService()方法后,service将停止运行没有提供默认的通信方式,启动service后该service就处于独立运行状态一旦启动,service即可在后台无限期运行,即使启动service的组件已被销毁也不受其影响,直到其被停止
bindService在其他组件中调用bindService()方法后,服务即处于启动状态所有与service绑定的组件都被销毁,或者它们都调用了unbindService()方法后,service将停止运行可以通过 ServiceConnection进行通信,组件可以与service进行交互、发送请求、获取结果,甚至是利用IPC跨进程执行这些操作当所有与其绑定的组件都取消绑定(可能是组件被销毁也有可能是其调用了unbindService()方法)后,service将停止

注: 1.表格中的“其他组件”不包括Broadcast receiver,其不能bindService,但是可以startService的

2.startService()与bindService()并不冲突,同一个service可能既有组件调用了startService()启动它,又有组件与它进行了绑定。当同一个service与其他组件同时存在这两种联系时,其生命周期会发生变化,必须从两种方法的角度看service均停止才能真正停止。

创建Service

创建一个Service一般有下面2个步骤

  • 创建一个类继承自Service(或它的子类,如IntentService),重写里面的一些键的回调方法,如onStartCommand()onBind()
  • 在Manifests文件里面为其声明,并根据需要配置一些其他属性。

讲道理,这一切跟新建一个Activity非常的像。

  • onCreate() 在每个Service的生命周期中这个方法会且仅会调用一次,并且它的调用在onStartCommand()以及onBind()之前,我们可以在这个方法中进行一些一次性的初始化工作。
  • onStartCommand() 当其他组件通过startService()方法启动Service时,此方法将会被调用。
  • onBind() 当其他组件通过bindService()方法与Service相绑定之后,此方法将会被调用。这个方法有一个IBinder的返回值,这意味着在重写它的时候必须返回一个IBinder对象,它是用来支撑其他组件与Service之间的通信的——另外,如果你不想让这个Service被其他组件所绑定,可以通过在这个方法返回一个null值来实现。
  • onDestroy() 这是Service一生中调用的最后一个方法,当这个方法被调用之后,Service就会被销毁。所以我们应当在这个方法里面进行一些资源的清理,比如注册的一些监听器什么的。

在Manifests文件里进行声明的时候,只有android:name属性是必须要有的,其他的属性都可以没有。但是有的时候适当的配置可以让我们的开发进行地更加顺利,所以了解一下注册一个Service可以声明哪些属性也是很有必要的。

<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>

具体含义可参考官网

  • android:enabled : 如果为true,则这个Service可以被系统实例化,如果为false,则不行。默认为true
  • android:exported : 如果为true,则其他应用的组件也可以调用这个Service并且可以与它进行互动,如果为false,则只有与Service同一个应用或者相同user ID的应用可以开启或绑定此Service。它的默认值取决于Service是否有intent filters。如果一个filter都没有,就意味着只有指定了Service的准确的类名才能调用,也就是说这个Service只能应用内部使用——其他的应用不知道它的类名。这种情况下exported的默认值就为false。反之,只要有了一个filter,就意味着Service是考虑到外界使用的情况的,这时exported的默认值就为true
  • android:icon : 一个象征着这个Service的icon
  • android:isolatedProcess : 如果设置为true,这个Service将运行在一个从系统中其他部分分离出来的特殊进程中,我们只能通过Service API来与它进行交流。默认为false。
  • android:label : 显示给用户的这个Service的名字。如果不设置,将会默认使用的label属性。
  • android:name : 这个Service的路径名,例如“com.lingdage.demo.ServiceDemo”。这个属性是唯一一个必须填的属性。
  • android:permission : 其他组件必须具有所填的权限才能启动这个Service
  • android:process : Service运行的进程的name。默认启动的Service是运行在主进程中的。

Demo

public class ServiceDemo extends Service {

    private static final String TAG = "ServiceDome";
    
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        //只在service创建的时候调用一次,可以在此进行一些一次性的初始化操作
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        //当其他组件调用startService()方法时,此方法将会被调用
        //在这里进行这个service主要的操作
        return super.onStartCommand(intent, flags, startId);
    }
    
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        //当其他组件调用bindService()方法时,此方法将会被调用
        //如果不想让这个service被绑定,在此返回null即可
        return null;
    }
    
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");
        //service调用的最后一个方法
        //在此进行资源的回收
        super.onDestroy();
    }
}

注意:每个Service必须在manifest中 通过来声明

<service android:name="com.example.servicetest.ServiceDemo" > 
  ... 
</service>

现在我们通过继承Service的方式定义了我们自己的ServiceDemo类,并且在manifest中声明了我们的ServiceDemo,接下来我们应该启动我们自己的服务.

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

  • START_NOT_STICKY

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

  • START_STICKY

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

  • START_REDELIVER_INTENT

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

启动Service

另一个组件通过调用startService()方法,就可以启动一个特定的Service,并且这将导致Service中的onStartCommand()方法被调用。在调用startService()方法的时候,其他组件需要在方法中传递一个Intent参数,然后Service将会在onStartCommand()中接收这个Intent,并获取一些数据。比如此时某个Activity要将一些数据存入数据库中,我就可以通过Intent把数据传入Service,然后让Service去进行连接数据库,存储数据等操作,而此时用户可以执行其他的任何操作——甚至包括销毁那个Activity——这并不会影响Service存储数据这件事。

当一个Service通过这种方式启动之后,它的生命周期就已经不受启动它的组件影响了,它可以在后台无限期的运行下去,只要Service自身没有调用stopSelf()并且其他的组件没有调用针对它的stopService()

另外,如果确定了使用这种方式启动Service并且不希望这个Service被绑定的话,那么也许除了传统的创建一个类继承Service之外我们有一个更好的选择——IntentService

相比Service,IntentService要简单许多。但是要注意的是,如果你有让Service同时处理多个请求的需求,这个时候就只能去继承Service了。这个时候就要自己去处理工作线

demo

我们通过一个Intent对象,并调用startService()方法来启动ServiceDemo

Intent startIntent = new Intent(this, ServiceDemo.class);  
startService(startIntent);

注意,假如我们是通过点击Button执行上面的代码,那么第一次点击的时候回执行其中的onCreate()onStartCommand()方法,但是当我们第二次点击的时候就只会执行onStartCommand()方法了.

为什么会这样呢? 这是由于onCreate()方法只会在Service第一次被创建的时候调用,如果当前Service已经被创建过了(第一次点击创建了ServiceDemo),不管怎样调用startService()方法,onCreate()方法都不会再执行。

启动了之后,当我们想停止服务的时候该怎么做呢?

我们也是通过一个Intent对象,并调用stopService()方法来停止ServiceDemo

Intent stopIntent = new Intent(this, ServiceDemo.class);
stopService(stopIntent);

停止Service

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

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

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

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

创建绑定服务

绑定服务允许应用组件通过调用 bindService() 与其绑定,以便创建长期连接(通常不允许组件通过调用startService()启动它)。

如需与 Activity 和其他应用组件中的服务进行交互,或者需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。

要创建绑定服务,必须实现 onBind() 回调方法以返回 IBinder,用于定义与服务通信的接口。然后,其他应用组件可以调用 bindService() 来检索该接口,并开始对服务调用方法。服务只用于与其绑定的应用组件,因此如果没有组件绑定到服务,则系统会销毁服务(您不必按通过 onStartCommand() 启动的服务那样来停止绑定服务)。

要创建绑定服务,首先必须定义指定客户端如何与服务通信的接口。 服务与客户端之间的这个接口必须是 IBinder 的实现,并且服务必须从 onBind() 回调方法返回它。一旦客户端收到 IBinder,即可开始通过该接口与服务进行交互。

多个客户端可以同时绑定到服务。客户端完成与服务的交互后,会调用 unbindService() 取消绑定。一旦没有客户端绑定到该服务,系统就会销毁它。

bindService这是一种比startService更复杂的启动方式,同时使用这种方式启动的service也能完成更多的事情,比如其他组件可向其发送请求,接受来自它的响应,甚至通过它来进行IPC等等。我们通常将绑定它的组件成为客户端,而称它为服务器。

如果要创建一个支持绑定的service,我们必须要重写它的onBind()方法。这个方法会返回一个IBinder对象,它是客户端用来和服务器进行交互的接口。而要得到IBinder接口,我们通常有三种方式

  • 继承Binder类
  • 使用Messenger类(AIDL的简化版)
  • 使用AIDL

这一块比较复杂,我们新开一篇讲解。Android Bound Service 知识总结

在前台运行服务

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

例如,应该将通过服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。

要请求让服务运行于前台,请调用 startForeground()。此方法采用两个参数:唯一标识通知的整型数和状态栏的Notification。例如:

Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
        System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
        getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

**注意:**提供给 startForeground() 的整型 ID 不得为 0。

要从前台移除服务,请调用stopForeground()。此方法采用一个布尔值,指示是否也移除状态栏通知。 此方法不会停止服务。 但是,如果您在服务正在前台运行时将其停止,则通知也会被移除。

管理服务生命周期

服务的生命周期比 Activity 的生命周期要简单得多。但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户没有意识到的情况下运行于后台。

服务生命周期(从创建到销毁)可以遵循两条不同的路径:

  • 启动服务

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

  • 绑定服务

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

这两条路径并非完全独立。也就是说,您可以绑定到已经使用 startService() 启动的服务。例如,可以通过使用Intent(标识要播放的音乐)调用 startService() 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 bindService() 绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 stopService()stopSelf() 不会实际停止服务。

实现生命周期回调

Activity 类似,服务也拥有生命周期回调方法,您可以实现这些方法来监控服务状态的变化并适时执行工作。 以下框架服务展示了每种生命周期方法:

public class ExampleService extends Service {
    int mStartMode;       // indicates how to behave if the service is killed
    IBinder mBinder;      // interface for clients that bind
    boolean mAllowRebind; // indicates whether onRebind should be used

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

**注:**与 Activity 生命周期回调方法不同,您需要调用这些回调方法的超类实现。

image.png

通过实现这些方法,您可以监控服务生命周期的两个嵌套循环:

  • 服务的整个生命周期从调用 onCreate() 开始起,到onDestroy()返回时结束。与 Activity 类似,服务也在onCreate()中完成初始设置,并在onDestroy()中释放所有剩余资源。例如,音乐播放服务可以在onCreate()中创建用于播放音乐的线程,然后在onDestroy()中停止该线程。

    无论服务是通过 startService() 还是 bindService() 创建,都会为所有服务调用 onCreate()onDestroy() 方法。

  • 服务的有效生命周期从调用onStartCommand()onBind()方法开始。每种方法均有Intent对象,该对象分别传递到startService()bindService()

    对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 onUnbind() 返回时结束。

**注:**尽管启动服务是通过调用 stopSelf()stopService() 来停止,但是该服务并无相应的回调(没有 onStop() 回调)。因此,除非服务绑定到客户端,否则在服务停止时,系统会将其销毁 — onDestroy() 是接收到的唯一回调。

其他知识点

Service和Thread的关系

答案:ServiceThread之间没有任何关系!

  • 两者概念的迥异

    • Thread 是程序执行的最小单元,它是分配CPU的基本单位,Android系统中UI线程也是线程的一种,当然Thread还可以用于执行一些耗时异步的操作。

    • ServiceAndroid的一种机制,服务是运行在主线程上的,它是由系统进程托管。它与其他组件之间的通信类似于client和server,是一种轻量级的IPC通信,这种通信的载体是Binder,它是在linux层交换信息的一种IPC,而所谓的Service后台任务只不过是指没有UI的组件罢了。

  • 两者的执行任务迥异

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

    • 当要执行耗时的网络或者数据库查询以及其他阻塞UI线程或密集使用CPU的任务时,都应该使用工作线程Thread,这样才能保证UI线程不被占用而影响用户体验。

    • 在应用程序中,如果需要长时间的在后台运行,而且不需要交互的情况下,使用服务。比如播放音乐,通过Service+Notification方式在后台执行同时在通知栏显示着。

  • 两者的最佳使用方式 在大部分情况下,ThreadService都会结合着使用,比如下载文件,一般会通过Service在后台执行+Notification在通知栏显示+Thread异步下载,再如应用程序会维持一个Service来从网络中获取推送服务。在Android官方看来也是如此,所以官网提供了一个ThreadService的结合来方便我们执行后台耗时任务,它就是IntentService,(深入了解IntentService,可以看IntentService),当然IntentService并不适用于所有的场景,但它的优点是使用方便、代码简洁,不需要我们创建Service实例并同时也创建线程,某些场景下还是非常赞的!由于IntentService是单个worker thread,所以任务需要排队,因此不适合大多数的多任务情况。还有为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联(通过绑定),然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的ServiceBinder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。

Service的种类

按运行地点分类

类别区别优点缺点应用
本地服务(Local)该服务依附在主进程上服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。主进程被Kill后,服务便会终止。非常常见的应用如:音乐播放服务。
远程服务(Remote)该服务是独立的进程服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。一些提供系统服务的Service,这种Service是常驻的。

按运行类型分类

类别区别应用
前台服务会在通知一栏显示 ONGOING 的 Notification,当服务被终止的时候,通知一栏的 Notification 也会消失,这样对于用户有一定的通知作用。常见的如音乐播放服务。
后台服务默认的服务即为后台服务,即不会在通知一栏显示 ONGOING 的 Notification。当服务被终止的时候,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步,邮件同步等。

有同学可能会问,后台服务我们可以自己创建ONGOINGNotification 这样就成为前台服务吗?答案是否定的,前台服务是在做了上述工作之后需要调用 startForeground( android 2.0 及其以后版本 )或 setForeground(android 2.0 以前的版本)使服务成为 前台服务。这样做的好处在于,当服务被外部强制终止掉的时候,ONGOINGNotification任然会移除掉。

#### 按使用方式分类

类别区别
startService 启动的服务主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService
bindService 启动的服务该方法启动的服务要进行通信。停止服务使用unbindService
startService 同时也 bindService 启动的服务停止服务应同时使用stepService与unbindService

特别注意: 1、你应当知道在调用 bindService 绑定到Service的时候,你就应当保证在某处调用 unbindService 解除绑定(尽管 Activityfinish 的时候绑定会自动解除,并且Service会自动停止); 2、你应当注意 使用startService启动服务之后,一定要使用 stopService停止服务,不管你是否使用bindService; 3、同时使用 startServicebindService 要注意到,Service 的终止,需要unbindServicestopService同时调用,才能终止 Service,不管 startServicebindService 的调用顺序,如果先调用 unbindService 此时服务不会自动终止,再调用 stopService 之后服务才会停止,如果先调用stopService 此时服务也不会终止,而再调用 unbindService 或者 之前调用 bindServiceContext不存在了(如Activityfinish 的时候)之后服务才会自动停止; 4、当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context不存在了),对应服务的生命周期与上述相同。 5、在 sdk 2.0 及其以后的版本中,对应的 onStart 已经被否决变为了 onStartCommand,不过之前的 onStart 任然有效。这意味着,如果你开发的应用程序用的 sdk 为 2.0 及其以后的版本,那么你应当使用onStartCommand而不是 onStart

这两种启动方式生命周期可以参考Android Service本地服务详解

onStartCommand详解

第一次调用startService方法时,onCreate方法、onStartCommand``方法将依次被调用,而多次调用startService时,只有onStartCommand方法被调用,最后我们调用stopService方法停止服务时onDestory方法被回调,这就是启动状态下Service的执行周期。接着我们重新回过头来进一步分析onStartCommand(Intent intent, int flags, int startId),这个方法有3个传入参数,它们的含义如下:

  • intent :启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service

  • flags:表示启动请求时是否有额外数据,可选值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY,0代表没有,它们具体含义如下:

    • START_FLAG_REDELIVERY 这个值代表了onStartCommand方法的返回值为 START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf方法停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),此时Intent时有值的。
    • START_FLAG_RETRY 该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。
  • startId : 指明当前服务的唯一ID,与stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根据ID停止服务。


另外,我们注意到onStartCommand()的返回值是一个很奇怪的值START_STICKY,这是个什么呢?或者说这个方法的返回值是用来干嘛的呢?事实上,它的返回值是用来指定系统对当前线程的行为的。它的返回值必须是以下常量之一:

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

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

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

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

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

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

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

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

参考

Android中的Service:默默的奉献者 (1)

Android Developers/Docs/指南/服务

全面了解 Service

关于Android Service真正的完全详解,你需要知道的一切