Android四大组件之Service第一章:service及其生命周期

来源:互联网 发布:清风算法辅助软件 编辑:程序博客网 时间:2024/05/22 04:31

明天又要出差一天。。。
今天整理的这篇文章大部分内容来自Google的官方指南。官方文档比市面上大部分文章都要齐全,当然也更权威。但是就不如那些文章那么浅显易懂了,比如郭霖,鸿洋的文章等。大家结合着看吧。

以下内容基于7.0系统

简介

服务是一个在后台进行耗时操作的应用程序组件,它并不提供UI。服务可由另一个应用程序组件启动,并且在后台持续运行,即使用户切换至其他的应用程序。应用程序组件可以与服务进行绑定以便与之交互,甚至进行IPC。

服务分为三种类型:

  1. Scheduled(定时服务)

    使用JobScheduler(5.0及以上)注册Job,指明对网络和时间的要求,系统将会自行执行这些任务。

  2. Started(启动的服务)

    一旦一个服务通过startService()启动后,这个服务便可以不停地运行下去,即使启动它的组件被摧毁。started service一般用来执行单一,且不需要给调用者返回结果的操作。如下载或上传一个文件到云。当这种Service的任务完成后,需要停止该服务。

  3. Bound(绑定的服务)

    一旦一个服务通过bindService()与一个应用程序组件绑定后,这个组件便可以与之交互,例如发送请求,接收结果甚至IPC。bound service的生命周期取决于它所绑定的组件的生命周期。多个组件可同时与一个服务绑定,一旦它们全部解绑,服务将会被系统回收。

启动的服务和绑定的服务看起来好像不相关,实际上一个服务可以既是started也是bound的。

任何一个组件,甚至外部程序的组件都可以通过一个Intent来使用一个服务,就如同通过Intent启动一个activity一样。但如果你不想你的服务被外部程序所用,你可以在Manifest文件中将其声明为private。

使用服务还是后台线程?

明白这个问题首先应该清楚Service和Thread的定位。

首先Service是系统组件,Thread是程序执行的最小单位。就大部分场景而言,我们要讨论的问题实际上是在Activity中创建Thread呢还是在Activity中创建Service然后再去Service中创建Thread。

对于前者,在Activity中创建Thread时,将不得不在onDestroy()方法中关闭线程,否则当Activity销毁后Thread还在后台跑,会出现内存泄露和僵尸线程。因此,Activity中Thread的生命周期是和Activity绑定的,也就是说当Activity退出后Thread中的工作也无法执行了。而且Thread无法和Activity进行方便的交互,同时该Thread无法被其他的组件调用。

而后者则不会出现这种情况。首先,started service有自己的生命周期,即使Activity被销毁,也不影响Service的运行,实际上Service的优先级要高于后台挂起的Activity。而bound service虽然生命周期与绑定的组件有关,但是其提供了方便交互的接口。这都是Service优于在Activity中直接创建Thread的地方。

因此,使用服务还是使用线程这个问题的答案取决于具体的场景需求。

这一个小节官方解释的也不是特别清楚,我加入了较多自己的见解,参考了这篇文章,感谢原作者。

经过demo试验,发现如果不再onDestroy中关闭线程,那么即使Activity被销毁,只要其进程还没来得及被系统销毁,那么线程中的任务还是会得到执行。但这无疑是不安全且可能造成内存泄露的。

几个重要方法和服务须知

  1. onStartCommand()

    如果是通过onStart()方法启动的服务,服务在创建之后会调用该方法。started service在完成工作之后需要调用stopService()或者stopSelf()来停止。注意,不是在服务的onDestroy()方法中调用停止服务,而是在onStartCommand()方法中的逻辑完成之后,返回之前。

    如果是单绑定的服务,则不需要实现该方法。因为反正不会调用。

  2. onBind()

    通过onBind()方法绑定的服务会调用该方法。为了使服务和与之绑定的组件之间进行交互,必须要在onBind()方法中return一个IBinder对象。

    如果不需要绑定服务,return null就行。

  3. onCreate()

    系统执行该方法来进行服务的一次性初始化工作。一次性的意思是:如果一个服务已经在running了,那么这个方法不会再被调用。

  4. onDestroy()

    当一个服务被回收的时候会调用该方法,在该方法中应该进行一些资源回收工作,比如线程,监听,广播接收器等。

当系统内存很低并且需要为当前获得用户焦点的活动腾资源时,系统会强制停止一个服务。但如果该服务是和获得用户焦点的Activity相绑定的,那它被杀掉的可能性会低很多。如果该服务是一个前台服务,那基本上不太可能被杀掉。当服务started之后,并且进行长时间的耗时操作时,系统会随着时间的推移降低其在后台任务中的优先级,那么服务就极有可能被干掉,因此如果启动了一个started服务,必须想好如何让它优雅地面对系统对其的重启。因为当系统干掉一个服务后,它会在资源变得不那么紧张时重启该服务,当然重启的模式也取决于onStartCommand()方法的返回值。

注意:只有started service才有重启这一说,bound service的生命取决于其绑定的组件。

在Manifest中声明一个服务

所有的服务都必须在Manifest文件中声明。(好像广播就不是,静态注册的需要,动态注册的广播不需要在Manifest中声明)

<manifest ... >  ...  <application ... >      <!--service标签有很多属性,具体的去看文档-->      <service android:name=".ExampleService" />      ...  </application></manifest>

不要使用隐式Intent,而要使用显式Intent来启动服务,并且不要给服务指定标签。之所以不用隐式Intent启动服务是因为你不知道除了你想要启动的服务,还有没有别的服务也能响应这个隐式Intent,而服务又是没有UI的,用户根本不知道是否启用了对应的服务,还是意外启动了别的服务。API21以上,在bingService()中传入一个隐式Intent会抛出异常。

如果不希望本服务被其他程序所启动,将其exported属性置false即可。

虽然服务并不提供UI,但用户是可以看到有哪些服务在运行的,最好在服务的description属性对该服务进行一些描述,以免被用户视为可疑服务而将其杀掉。

创建一个started service

所谓started service即通过startService()方法启动的服务,通过该方式启动的服务在完成onCreate()方法后会立即调用onStartCommand()方法。

当一个服务被启动后,它就有了独立的生命周期(不依赖与启动它的组件)。启动服务是在startService()方法中可以传入一个Intent参数用来携带数据,服务会在onStartCommand()方法中收到该Intent。当服务完成工作后,应当停止自身,然后等待系统回收。

注意:服务运行在其声明的程序所在的进程,并且默认运行在主线程中。因此如果服务中进行的是密集型操作或可能阻塞的操作,则有可能影响UI的响应。这时候最好开一个新线程。访问网络或进行IO读写更不用说了,在主线程中出现这类代码直接抛异常。

通常来讲,有两种方法来实现一个started service

  1. 继承IntentService

    大部分的服务不需要能够同时处理多个请求,因此使用IntentService会比较简单。

    IntentService会:

    • 自动创建子线程,在onHandlIntent()方法中处理传到onStartCommand()中的Intent。
    • 创建一个队列来保存传入的Intent,每次只派发一个Intent对象到onHandleIntent()方法中,即每次只运行一个任务,没有多线程问题。
    • 在所有的Intent处理完之后自动停止服务。
    • 对onBind()方法进行了默认实现:return null。因为不需要绑定嘛。
    • 对onStartCommand()方法进行了默认实现:将Intent传到一个工作队列中,然后一个个传给onHandleIntent()方法。

    例:

    public class HelloIntentService extends IntentService { /**  * A constructor is required, and must call the super IntentService(String)  * constructor with a name for the worker thread.  */ public HelloIntentService() {     super("HelloIntentService"); } /**  * The IntentService calls this method from the default worker thread with  * the intent that started the service. When this method returns, IntentService  * stops the service, as appropriate.  */ @Override protected void onHandleIntent(Intent intent) {     // Normally we would do some work here, like download a file.     // For our sample, we just sleep for 5 seconds.     try {         Thread.sleep(5000);     } catch (InterruptedException e) {         // Restore interrupt status.         Thread.currentThread().interrupt();     } }}

    就是这么简单,只需要提供一个构造方法,然后复写onHandleIntent()来执行业务逻辑即可。IntentService其实就是Service的一个子类,如果你要重写Service的其他方法,一定要super一下父类的该方法,不然IntentService中的一些代码就被冲掉了。

  2. 继承Service类

    这种方式比较复杂,代码量也更多,但是如果需要同时handle多个start request,即同时handle多个Intent的话,则只能选择该方式。

    下面这段代码的和上面的功能一样,使用Handler强行实现了一个队列。按照继承Service来创建服务的场景来看,应该在onStartCommand()方法中针对每个Intent都开一个线程,才能实现同时处理多个start requests。

    public class HelloService extends Service { private Looper mServiceLooper; private ServiceHandler mServiceHandler; // Handler that receives messages from the thread private final class ServiceHandler extends Handler {     public ServiceHandler(Looper looper) {         super(looper);     }     @Override     public void handleMessage(Message msg) {         // Normally we would do some work here, like download a file.         // For our sample, we just sleep for 5 seconds.         try {             Thread.sleep(5000);         } catch (InterruptedException e) {             // Restore interrupt status.             Thread.currentThread().interrupt();         }         // Stop the service using the startId, so that we don't stop         // the service in the middle of handling another job         stopSelf(msg.arg1);     } } @Override public void onCreate() {   // Start up the thread running the service.  Note that we create a   // separate thread because the service normally runs in the process's   // main thread, which we don't want to block.  We also make it   // background priority so CPU-intensive work will not disrupt our UI.   HandlerThread thread = new HandlerThread("ServiceStartArguments",           Process.THREAD_PRIORITY_BACKGROUND);   thread.start();   // Get the HandlerThread's Looper and use it for our Handler   mServiceLooper = thread.getLooper();   mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) {     Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();     // For each start request, send a message to start a job and deliver the     // start ID so we know which request we're stopping when we finish the job     Message msg = mServiceHandler.obtainMessage();     msg.arg1 = startId;//在这里将startId传过去,原因下面的部分有介绍。     mServiceHandler.sendMessage(msg);     // If we get killed, after returning from here, restart     return START_STICKY; } @Override public IBinder onBind(Intent intent) {     // We don't provide binding, so return null     return null; } @Override public void onDestroy() {   Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); }}

    我个人认为上面这段代码纯粹为了展现IntentService的优越性而刻意在Service中实现了一个消息队列(使用Handler的机制)。直接继承Service的优势在于能够创建多个线程同时处理requests。

started service的重启

如果查看过API的话,会发现onStartCommand()方法有一个int类型的返回值,这个返回值决定了started service被系统干掉后将以什么样的方式重启。该返回值必须是以下三种的其中之一:

  • START_NOT_STICKY

    如果系统干掉了该服务,在资源足够的情况下不会立即重启它,除非有一个Intent被传过来了。这种方式可以避免服务在不需要的时候也在后台跑。

  • START_STICKY

    如果系统干掉了该服务,那么在资源可用时重启它,并且调用onStartCommand()方法,但是不要把上一个Intent传来了。实际上,系统会在调用onStartCommand()的时候传一个null进去。这种模式适合媒体播放类的服务,这类服务在一直在后台运行,并且等着分配工作。相比上一种模式,这种模式可以减去等待服务启动的时间,响应上会更快。

  • START_REDELIVER_INTENT

    如果系统干掉了该服务,那么在资源可用时重启它,并且调用onStartCommand()方法,并且把上一个Intent传进来。其他Intent则依次等候或者传进来按照相应逻辑处理。这种模式比较适合下载类服务。当一个后台下载服务由于长期运行被系统干掉的时候,它可能还没下载完,那么当其重启的时候,应当能自动开始之前的下载。这个时候接收之前的Intent,并对下载进度进行判断之后就可以实现断点续传了。

开启一个服务

上面说了这么多,都是在创建一个started service,那如何开启它呢?

//上面说了,不要用隐式IntentIntent intent = new Intent(this, HelloService.class);startService(intent);

started service和开启它的组件之间的唯一纽带就是startService()传g进去的Intent参数。如果想让服务在完成工作后返回一个结果,那么可以在Intent中封装一个PendingIntent,那么当服务完成工作后可以使用这个PendingIntent对象创建一个广播来传递结果。

PendingIntent与广播???

对同一个服务的多次启动请求会调用多次onStartCommand()方法,但是停止该服务只需要调用一次stop方法。

停止一个服务

started service需要自己管理生命周期,系统不会去管它的生命周期,只会在它stopSelf()或者被其他组件stopService()之后去销毁它。

如果服务需要处理多个request,那什么时候调用stopSelf()才是合适的呢?因为总有可能在处理完一个request之后准备停止服务的时候,服务中还在handle另一个request,这时候停止服务会漏掉request。这个时候可以在onStop(int)方法中传入onStartCommand(Intent,int,int)方法参数中的第三个参数:startId,这个参数我没看到在哪里可以指定,应该是系统分配的。这样就能保证一定会在最后一个request被处理完之后才真正调用stopSelf()。

创建一个绑定服务

绑定服务比较复杂,另开章节介绍

给用户发通知

  • 使用Toast
  • 使用Status Bar

不多做介绍

前台服务

前台服务是一种告诉用户『我』在运行的服务,这种服务几乎不太可能被系统干掉。但同时这个服务也必须在状态栏显示一个图标并且显示一条通知。除非该前台服务被停止,否则该通知不会消失。

使用范例:

Intent notificationIntent = new Intent(this, ExampleActivity.class);PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);Notification notification = new Notification.Builder(this)    .setContentTitle(getText(R.string.notification_title))    .setContentText(getText(R.string.notification_message))    .setSmallIcon(R.drawable.icon)    .setContentIntent(pendingIntent)    .setTicker(getText(R.string.ticker_text))    .build();startForeground(ONGOING_NOTIFICATION_ID, notification);

注意:startForeground()方法传入的id不能为0。

要移除这个通知,使用stopForeground()方法,该方法并不会停止这个服务,只是将其从前台移除。如果直接调用stopService之类的方法,那么会既移除这个通知也会停止这个服务。

管理服务的生命周期

服务的生命周期比较简单,但是因为服务不可见,所以处理起来也要格外小心。

服务的生命周期分两路:

  • A started service

    这种服务的生命周期从别的组件调用startService()方法那一刻开始,直到stopSelf()或者stopService()执行。

  • A bound service

    这种服务的生命周期取决于其所绑定的组件的生命周期。一旦和任何一个组件绑定,这个服务的生命周期开始;一旦和最后一个组件解绑,这个服务的生命周期结束。

注意:

以上两条生命周期路线并不是完全分开的,因为一个started service完全可以提供绑定服务。比如可以通过startService()方法启动一个服务,那这个时候该服务是一个started service。当如果这个服务提供了绑定机制,在其已经启动的情况下,另一个组件再去绑定它,那么它又成了一个bound service。这个时候它的生命周期的结束已经不仅仅取决于stopService()或stopSelf()的调用了,还要看其上是否有组件与之绑定。如果所有的绑定组件都已解绑,这个时候stopService()或stopSelf()才能真正停止这个服务。

服务的生命周期方法

Just show me the code!

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的生命周期方法,不需要在这里使用super调用父类的实现,因为父类的实现是空的。

服务的生命周期图(来自官网)

和Activity一样,Service的生命周期始于onCreate()方法终于onDestroy()方法。在onCreate()中可以进行一些初始化工作,比如开启一个线程播放音乐。在onDestroy()中进行资源回收工作,如关闭线程。

一旦一个服务调用了onStartCommand()方法或者onBind()方法,其就处于活跃状态了,

started service直到onDestroy()方法调用才退出活跃状态。也就是说即使onStartCommand()方法返回,started service也处于活跃状态。而bound service则不同,一旦bound service的onUnbind()方法return,其活跃状态便结束了,接着便等待系统回收。

原创粉丝点击