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

明天又要出差一天。。。
今天整理的这篇文章大部分内容来自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,那如何开启它呢?

//上面说了,不要用隐式Intent
Intent 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,其活跃状态便结束了,接着便等待系统回收。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值